summaryrefslogtreecommitdiff
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/NativeLibrary.kt15
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt8
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt23
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt10
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt1
-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/SubmenuSetting.kt4
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt53
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt53
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt45
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt113
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt5
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt32
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt46
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsSearchFragment.kt16
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt16
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/EmulationViewModel.kt44
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt44
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeSetting.kt6
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt28
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt57
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt27
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt64
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt23
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconUtils.kt11
-rw-r--r--src/android/app/src/main/jni/config.cpp2
-rw-r--r--src/android/app/src/main/jni/native.cpp20
-rw-r--r--src/android/app/src/main/res/drawable/shortcut.xml11
-rw-r--r--src/android/app/src/main/res/navigation/emulation_navigation.xml2
-rw-r--r--src/android/app/src/main/res/navigation/home_navigation.xml2
-rw-r--r--src/android/app/src/main/res/navigation/settings_navigation.xml2
-rw-r--r--src/android/app/src/main/res/values/dimens.xml1
-rw-r--r--src/common/fs/fs.cpp15
-rw-r--r--src/common/logging/filter.cpp2
-rw-r--r--src/common/logging/types.h2
-rw-r--r--src/common/polyfill_thread.h20
-rw-r--r--src/common/settings.h2
-rw-r--r--src/common/settings_common.h10
-rw-r--r--src/common/settings_setting.h33
-rw-r--r--src/core/CMakeLists.txt9
-rw-r--r--src/core/core.cpp11
-rw-r--r--src/core/core.h6
-rw-r--r--src/core/crypto/key_manager.cpp8
-rw-r--r--src/core/file_sys/ips_layer.cpp4
-rw-r--r--src/core/file_sys/patch_manager.cpp4
-rw-r--r--src/core/file_sys/patch_manager.h2
-rw-r--r--src/core/hid/hid_core.cpp8
-rw-r--r--src/core/hid/hid_core.h7
-rw-r--r--src/core/hle/kernel/k_process.cpp5
-rw-r--r--src/core/hle/kernel/k_process.h8
-rw-r--r--src/core/hle/kernel/svc/svc_debug_string.cpp2
-rw-r--r--src/core/hle/kernel/svc/svc_exception.cpp6
-rw-r--r--src/core/hle/service/am/am.cpp14
-rw-r--r--src/core/hle/service/am/am.h1
-rw-r--r--src/core/hle/service/hid/controllers/npad.cpp41
-rw-r--r--src/core/hle/service/hid/controllers/npad.h20
-rw-r--r--src/core/hle/service/hid/hid.cpp2
-rw-r--r--src/core/hle/service/nfc/common/device.cpp14
-rw-r--r--src/core/hle/service/ngc/ngc.cpp150
-rw-r--r--src/core/hle/service/ngc/ngc.h (renamed from src/core/hle/service/ngct/ngct.h)4
-rw-r--r--src/core/hle/service/ngct/ngct.cpp62
-rw-r--r--src/core/hle/service/service.cpp4
-rw-r--r--src/core/hle/service/sockets/bsd.cpp2
-rw-r--r--src/core/loader/deconstructed_rom_directory.cpp8
-rw-r--r--src/core/loader/deconstructed_rom_directory.h4
-rw-r--r--src/core/loader/kip.cpp3
-rw-r--r--src/core/loader/nro.cpp3
-rw-r--r--src/core/loader/nso.cpp5
-rw-r--r--src/core/loader/nsp.cpp3
-rw-r--r--src/core/memory/cheat_engine.cpp2
-rw-r--r--src/core/tools/renderdoc.cpp55
-rw-r--r--src/core/tools/renderdoc.h22
-rw-r--r--src/shader_recompiler/backend/spirv/emit_spirv_image.cpp4
-rw-r--r--src/shader_recompiler/backend/spirv/spirv_emit_context.cpp3
-rw-r--r--src/shader_recompiler/backend/spirv/spirv_emit_context.h1
-rw-r--r--src/video_core/texture_cache/texture_cache.h15
-rw-r--r--src/yuzu/configuration/configure_debug.cpp3
-rw-r--r--src/yuzu/configuration/configure_debug.ui99
-rw-r--r--src/yuzu/configuration/configure_ui.cpp7
-rw-r--r--src/yuzu/configuration/shared_widget.cpp171
-rw-r--r--src/yuzu/configuration/shared_widget.h21
-rw-r--r--src/yuzu/hotkeys.h4
-rw-r--r--src/yuzu/main.cpp6
-rw-r--r--src/yuzu_cmd/config.cpp2
-rw-r--r--src/yuzu_cmd/yuzu.cpp5
85 files changed, 1120 insertions, 599 deletions
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt
index c8706d7a6..21f67f32a 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt
@@ -308,21 +308,6 @@ object NativeLibrary {
308 external fun isPaused(): Boolean 308 external fun isPaused(): Boolean
309 309
310 /** 310 /**
311 * Mutes emulation sound
312 */
313 external fun muteAudio(): Boolean
314
315 /**
316 * Unmutes emulation sound
317 */
318 external fun unmuteAudio(): Boolean
319
320 /**
321 * Returns true if emulation audio is muted.
322 */
323 external fun isMuted(): Boolean
324
325 /**
326 * Returns the performance stats for the current game 311 * Returns the performance stats for the current game
327 */ 312 */
328 external fun getPerfStats(): DoubleArray 313 external fun getPerfStats(): DoubleArray
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
index bbd328c71..d4ae39661 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
@@ -332,7 +332,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
332 pictureInPictureActions.add(pauseRemoteAction) 332 pictureInPictureActions.add(pauseRemoteAction)
333 } 333 }
334 334
335 if (NativeLibrary.isMuted()) { 335 if (BooleanSetting.AUDIO_MUTED.boolean) {
336 val unmuteIcon = Icon.createWithResource( 336 val unmuteIcon = Icon.createWithResource(
337 this@EmulationActivity, 337 this@EmulationActivity,
338 R.drawable.ic_pip_unmute 338 R.drawable.ic_pip_unmute
@@ -389,9 +389,9 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
389 if (!NativeLibrary.isPaused()) NativeLibrary.pauseEmulation() 389 if (!NativeLibrary.isPaused()) NativeLibrary.pauseEmulation()
390 } 390 }
391 if (intent.action == actionUnmute) { 391 if (intent.action == actionUnmute) {
392 if (NativeLibrary.isMuted()) NativeLibrary.unmuteAudio() 392 if (BooleanSetting.AUDIO_MUTED.boolean) BooleanSetting.AUDIO_MUTED.setBoolean(false)
393 } else if (intent.action == actionMute) { 393 } else if (intent.action == actionMute) {
394 if (!NativeLibrary.isMuted()) NativeLibrary.muteAudio() 394 if (!BooleanSetting.AUDIO_MUTED.boolean) BooleanSetting.AUDIO_MUTED.setBoolean(true)
395 } 395 }
396 buildPictureInPictureParams() 396 buildPictureInPictureParams()
397 } 397 }
@@ -417,7 +417,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
417 } catch (ignored: Exception) { 417 } catch (ignored: Exception) {
418 } 418 }
419 // Always resume audio, since there is no UI button 419 // Always resume audio, since there is no UI button
420 if (NativeLibrary.isMuted()) NativeLibrary.unmuteAudio() 420 if (BooleanSetting.AUDIO_MUTED.boolean) BooleanSetting.AUDIO_MUTED.setBoolean(false)
421 } 421 }
422 } 422 }
423 423
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt
index 0013e8512..f9f88a1d2 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt
@@ -4,7 +4,8 @@
4package org.yuzu.yuzu_emu.adapters 4package org.yuzu.yuzu_emu.adapters
5 5
6import android.content.Intent 6import android.content.Intent
7import android.graphics.drawable.BitmapDrawable 7import android.graphics.Bitmap
8import android.graphics.drawable.LayerDrawable
8import android.net.Uri 9import android.net.Uri
9import android.text.TextUtils 10import android.text.TextUtils
10import android.view.LayoutInflater 11import android.view.LayoutInflater
@@ -15,7 +16,10 @@ import android.widget.Toast
15import androidx.appcompat.app.AppCompatActivity 16import androidx.appcompat.app.AppCompatActivity
16import androidx.core.content.pm.ShortcutInfoCompat 17import androidx.core.content.pm.ShortcutInfoCompat
17import androidx.core.content.pm.ShortcutManagerCompat 18import androidx.core.content.pm.ShortcutManagerCompat
19import androidx.core.content.res.ResourcesCompat
18import androidx.core.graphics.drawable.IconCompat 20import androidx.core.graphics.drawable.IconCompat
21import androidx.core.graphics.drawable.toBitmap
22import androidx.core.graphics.drawable.toDrawable
19import androidx.documentfile.provider.DocumentFile 23import androidx.documentfile.provider.DocumentFile
20import androidx.lifecycle.ViewModelProvider 24import androidx.lifecycle.ViewModelProvider
21import androidx.navigation.findNavController 25import androidx.navigation.findNavController
@@ -87,11 +91,24 @@ class GameAdapter(private val activity: AppCompatActivity) :
87 action = Intent.ACTION_VIEW 91 action = Intent.ACTION_VIEW
88 data = Uri.parse(holder.game.path) 92 data = Uri.parse(holder.game.path)
89 } 93 }
94
95 val layerDrawable = ResourcesCompat.getDrawable(
96 YuzuApplication.appContext.resources,
97 R.drawable.shortcut,
98 null
99 ) as LayerDrawable
100 layerDrawable.setDrawableByLayerId(
101 R.id.shortcut_foreground,
102 GameIconUtils.getGameIcon(holder.game).toDrawable(YuzuApplication.appContext.resources)
103 )
104 val inset = YuzuApplication.appContext.resources
105 .getDimensionPixelSize(R.dimen.icon_inset)
106 layerDrawable.setLayerInset(1, inset, inset, inset, inset)
90 val shortcut = ShortcutInfoCompat.Builder(YuzuApplication.appContext, holder.game.path) 107 val shortcut = ShortcutInfoCompat.Builder(YuzuApplication.appContext, holder.game.path)
91 .setShortLabel(holder.game.title) 108 .setShortLabel(holder.game.title)
92 .setIcon( 109 .setIcon(
93 IconCompat.createWithBitmap( 110 IconCompat.createWithAdaptiveBitmap(
94 (holder.binding.imageGameScreen.drawable as BitmapDrawable).bitmap 111 layerDrawable.toBitmap(config = Bitmap.Config.ARGB_8888)
95 ) 112 )
96 ) 113 )
97 .setIntent(openIntent) 114 .setIntent(openIntent)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt
index 8d87d3bd7..1675627a1 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt
@@ -10,8 +10,12 @@ import android.view.ViewGroup
10import androidx.appcompat.app.AppCompatActivity 10import androidx.appcompat.app.AppCompatActivity
11import androidx.core.content.ContextCompat 11import androidx.core.content.ContextCompat
12import androidx.core.content.res.ResourcesCompat 12import androidx.core.content.res.ResourcesCompat
13import androidx.lifecycle.Lifecycle
13import androidx.lifecycle.LifecycleOwner 14import androidx.lifecycle.LifecycleOwner
15import androidx.lifecycle.lifecycleScope
16import androidx.lifecycle.repeatOnLifecycle
14import androidx.recyclerview.widget.RecyclerView 17import androidx.recyclerview.widget.RecyclerView
18import kotlinx.coroutines.launch
15import org.yuzu.yuzu_emu.R 19import org.yuzu.yuzu_emu.R
16import org.yuzu.yuzu_emu.databinding.CardHomeOptionBinding 20import org.yuzu.yuzu_emu.databinding.CardHomeOptionBinding
17import org.yuzu.yuzu_emu.fragments.MessageDialogFragment 21import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
@@ -86,7 +90,11 @@ class HomeSettingAdapter(
86 binding.optionIcon.alpha = 0.5f 90 binding.optionIcon.alpha = 0.5f
87 } 91 }
88 92
89 option.details.observe(viewLifecycle) { updateOptionDetails(it) } 93 viewLifecycle.lifecycleScope.launch {
94 viewLifecycle.repeatOnLifecycle(Lifecycle.State.CREATED) {
95 option.details.collect { updateOptionDetails(it) }
96 }
97 }
90 binding.optionDetail.postDelayed( 98 binding.optionDetail.postDelayed(
91 { 99 {
92 binding.optionDetail.ellipsize = TextUtils.TruncateAt.MARQUEE 100 binding.optionDetail.ellipsize = TextUtils.TruncateAt.MARQUEE
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt
index e0c0538c7..8476ce867 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt
@@ -10,6 +10,7 @@ enum class BooleanSetting(
10 override val category: Settings.Category, 10 override val category: Settings.Category,
11 override val androidDefault: Boolean? = null 11 override val androidDefault: Boolean? = null
12) : AbstractBooleanSetting { 12) : AbstractBooleanSetting {
13 AUDIO_MUTED("audio_muted", Settings.Category.Audio),
13 CPU_DEBUG_MODE("cpu_debug_mode", Settings.Category.Cpu), 14 CPU_DEBUG_MODE("cpu_debug_mode", Settings.Category.Cpu),
14 FASTMEM("cpuopt_fastmem", Settings.Category.Cpu), 15 FASTMEM("cpuopt_fastmem", Settings.Category.Cpu),
15 FASTMEM_EXCLUSIVES("cpuopt_fastmem_exclusives", Settings.Category.Cpu), 16 FASTMEM_EXCLUSIVES("cpuopt_fastmem_exclusives", Settings.Category.Cpu),
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 0702236e8..08e2a973d 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
@@ -80,6 +80,17 @@ object Settings {
80 const val SECTION_THEME = "Theme" 80 const val SECTION_THEME = "Theme"
81 const val SECTION_DEBUG = "Debug" 81 const val SECTION_DEBUG = "Debug"
82 82
83 enum class MenuTag(val titleId: Int) {
84 SECTION_ROOT(R.string.advanced_settings),
85 SECTION_GENERAL(R.string.preferences_general),
86 SECTION_SYSTEM(R.string.preferences_system),
87 SECTION_RENDERER(R.string.preferences_graphics),
88 SECTION_AUDIO(R.string.preferences_audio),
89 SECTION_CPU(R.string.cpu),
90 SECTION_THEME(R.string.preferences_theme),
91 SECTION_DEBUG(R.string.preferences_debug);
92 }
93
83 const val PREF_MEMORY_WARNING_SHOWN = "MemoryWarningShown" 94 const val PREF_MEMORY_WARNING_SHOWN = "MemoryWarningShown"
84 95
85 const val PREF_OVERLAY_VERSION = "OverlayVersion" 96 const val PREF_OVERLAY_VERSION = "OverlayVersion"
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt
index 91c273964..b343e527e 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt
@@ -3,10 +3,12 @@
3 3
4package org.yuzu.yuzu_emu.features.settings.model.view 4package org.yuzu.yuzu_emu.features.settings.model.view
5 5
6import org.yuzu.yuzu_emu.features.settings.model.Settings
7
6class SubmenuSetting( 8class SubmenuSetting(
7 titleId: Int, 9 titleId: Int,
8 descriptionId: Int, 10 descriptionId: Int,
9 val menuKey: String 11 val menuKey: Settings.MenuTag
10) : SettingsItem(emptySetting, titleId, descriptionId) { 12) : SettingsItem(emptySetting, titleId, descriptionId) {
11 override val type = TYPE_SUBMENU 13 override val type = TYPE_SUBMENU
12} 14}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt
index 908c01265..4d2f2f604 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt
@@ -13,9 +13,14 @@ import androidx.appcompat.app.AppCompatActivity
13import androidx.core.view.ViewCompat 13import androidx.core.view.ViewCompat
14import androidx.core.view.WindowCompat 14import androidx.core.view.WindowCompat
15import androidx.core.view.WindowInsetsCompat 15import androidx.core.view.WindowInsetsCompat
16import androidx.lifecycle.Lifecycle
17import androidx.lifecycle.lifecycleScope
18import androidx.lifecycle.repeatOnLifecycle
16import androidx.navigation.fragment.NavHostFragment 19import androidx.navigation.fragment.NavHostFragment
17import androidx.navigation.navArgs 20import androidx.navigation.navArgs
18import com.google.android.material.color.MaterialColors 21import com.google.android.material.color.MaterialColors
22import kotlinx.coroutines.flow.collectLatest
23import kotlinx.coroutines.launch
19import java.io.IOException 24import java.io.IOException
20import org.yuzu.yuzu_emu.R 25import org.yuzu.yuzu_emu.R
21import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding 26import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding
@@ -66,25 +71,39 @@ class SettingsActivity : AppCompatActivity() {
66 ) 71 )
67 } 72 }
68 73
69 settingsViewModel.shouldRecreate.observe(this) { 74 lifecycleScope.apply {
70 if (it) { 75 launch {
71 settingsViewModel.setShouldRecreate(false) 76 repeatOnLifecycle(Lifecycle.State.CREATED) {
72 recreate() 77 settingsViewModel.shouldRecreate.collectLatest {
78 if (it) {
79 settingsViewModel.setShouldRecreate(false)
80 recreate()
81 }
82 }
83 }
73 } 84 }
74 } 85 launch {
75 settingsViewModel.shouldNavigateBack.observe(this) { 86 repeatOnLifecycle(Lifecycle.State.CREATED) {
76 if (it) { 87 settingsViewModel.shouldNavigateBack.collectLatest {
77 settingsViewModel.setShouldNavigateBack(false) 88 if (it) {
78 navigateBack() 89 settingsViewModel.setShouldNavigateBack(false)
90 navigateBack()
91 }
92 }
93 }
79 } 94 }
80 } 95 launch {
81 settingsViewModel.shouldShowResetSettingsDialog.observe(this) { 96 repeatOnLifecycle(Lifecycle.State.CREATED) {
82 if (it) { 97 settingsViewModel.shouldShowResetSettingsDialog.collectLatest {
83 settingsViewModel.setShouldShowResetSettingsDialog(false) 98 if (it) {
84 ResetSettingsDialogFragment().show( 99 settingsViewModel.setShouldShowResetSettingsDialog(false)
85 supportFragmentManager, 100 ResetSettingsDialogFragment().show(
86 ResetSettingsDialogFragment.TAG 101 supportFragmentManager,
87 ) 102 ResetSettingsDialogFragment.TAG
103 )
104 }
105 }
106 }
88 } 107 }
89 } 108 }
90 109
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt
index bc319714c..70d8ec14b 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt
@@ -3,6 +3,7 @@
3 3
4package org.yuzu.yuzu_emu.features.settings.ui 4package org.yuzu.yuzu_emu.features.settings.ui
5 5
6import android.annotation.SuppressLint
6import android.os.Bundle 7import android.os.Bundle
7import android.view.LayoutInflater 8import android.view.LayoutInflater
8import android.view.View 9import android.view.View
@@ -13,14 +14,19 @@ import androidx.core.view.WindowInsetsCompat
13import androidx.core.view.updatePadding 14import androidx.core.view.updatePadding
14import androidx.fragment.app.Fragment 15import androidx.fragment.app.Fragment
15import androidx.fragment.app.activityViewModels 16import androidx.fragment.app.activityViewModels
17import androidx.lifecycle.Lifecycle
18import androidx.lifecycle.lifecycleScope
19import androidx.lifecycle.repeatOnLifecycle
16import androidx.navigation.findNavController 20import androidx.navigation.findNavController
17import androidx.navigation.fragment.navArgs 21import androidx.navigation.fragment.navArgs
18import androidx.recyclerview.widget.LinearLayoutManager 22import androidx.recyclerview.widget.LinearLayoutManager
19import com.google.android.material.divider.MaterialDividerItemDecoration 23import com.google.android.material.divider.MaterialDividerItemDecoration
20import com.google.android.material.transition.MaterialSharedAxis 24import com.google.android.material.transition.MaterialSharedAxis
25import kotlinx.coroutines.flow.collectLatest
26import kotlinx.coroutines.launch
21import org.yuzu.yuzu_emu.R 27import org.yuzu.yuzu_emu.R
22import org.yuzu.yuzu_emu.databinding.FragmentSettingsBinding 28import org.yuzu.yuzu_emu.databinding.FragmentSettingsBinding
23import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile 29import org.yuzu.yuzu_emu.features.settings.model.Settings
24import org.yuzu.yuzu_emu.model.SettingsViewModel 30import org.yuzu.yuzu_emu.model.SettingsViewModel
25 31
26class SettingsFragment : Fragment() { 32class SettingsFragment : Fragment() {
@@ -51,15 +57,17 @@ class SettingsFragment : Fragment() {
51 return binding.root 57 return binding.root
52 } 58 }
53 59
60 // This is using the correct scope, lint is just acting up
61 @SuppressLint("UnsafeRepeatOnLifecycleDetector")
54 override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 62 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
55 settingsAdapter = SettingsAdapter(this, requireContext()) 63 settingsAdapter = SettingsAdapter(this, requireContext())
56 presenter = SettingsFragmentPresenter( 64 presenter = SettingsFragmentPresenter(
57 settingsViewModel, 65 settingsViewModel,
58 settingsAdapter!!, 66 settingsAdapter!!,
59 args.menuTag, 67 args.menuTag
60 args.game?.gameId ?: ""
61 ) 68 )
62 69
70 binding.toolbarSettingsLayout.title = getString(args.menuTag.titleId)
63 val dividerDecoration = MaterialDividerItemDecoration( 71 val dividerDecoration = MaterialDividerItemDecoration(
64 requireContext(), 72 requireContext(),
65 LinearLayoutManager.VERTICAL 73 LinearLayoutManager.VERTICAL
@@ -75,28 +83,31 @@ class SettingsFragment : Fragment() {
75 settingsViewModel.setShouldNavigateBack(true) 83 settingsViewModel.setShouldNavigateBack(true)
76 } 84 }
77 85
78 settingsViewModel.toolbarTitle.observe(viewLifecycleOwner) { 86 viewLifecycleOwner.lifecycleScope.apply {
79 if (it.isNotEmpty()) binding.toolbarSettingsLayout.title = it 87 launch {
80 } 88 repeatOnLifecycle(Lifecycle.State.CREATED) {
81 89 settingsViewModel.shouldReloadSettingsList.collectLatest {
82 settingsViewModel.shouldReloadSettingsList.observe(viewLifecycleOwner) { 90 if (it) {
83 if (it) { 91 settingsViewModel.setShouldReloadSettingsList(false)
84 settingsViewModel.setShouldReloadSettingsList(false) 92 presenter.loadSettingsList()
85 presenter.loadSettingsList() 93 }
94 }
95 }
86 } 96 }
87 } 97 launch {
88 98 settingsViewModel.isUsingSearch.collectLatest {
89 settingsViewModel.isUsingSearch.observe(viewLifecycleOwner) { 99 if (it) {
90 if (it) { 100 reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true)
91 reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true) 101 exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false)
92 exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false) 102 } else {
93 } else { 103 reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
94 reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false) 104 exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
95 exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) 105 }
106 }
96 } 107 }
97 } 108 }
98 109
99 if (args.menuTag == SettingsFile.FILE_NAME_CONFIG) { 110 if (args.menuTag == Settings.MenuTag.SECTION_ROOT) {
100 binding.toolbarSettings.inflateMenu(R.menu.menu_settings) 111 binding.toolbarSettings.inflateMenu(R.menu.menu_settings)
101 binding.toolbarSettings.setOnMenuItemClickListener { 112 binding.toolbarSettings.setOnMenuItemClickListener {
102 when (it.itemId) { 113 when (it.itemId) {
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 22a529b1b..766414a6c 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
@@ -6,7 +6,6 @@ package org.yuzu.yuzu_emu.features.settings.ui
6import android.content.Context 6import android.content.Context
7import android.content.SharedPreferences 7import android.content.SharedPreferences
8import android.os.Build 8import android.os.Build
9import android.text.TextUtils
10import android.widget.Toast 9import android.widget.Toast
11import androidx.preference.PreferenceManager 10import androidx.preference.PreferenceManager
12import org.yuzu.yuzu_emu.R 11import org.yuzu.yuzu_emu.R
@@ -20,15 +19,13 @@ import org.yuzu.yuzu_emu.features.settings.model.LongSetting
20import org.yuzu.yuzu_emu.features.settings.model.Settings 19import org.yuzu.yuzu_emu.features.settings.model.Settings
21import org.yuzu.yuzu_emu.features.settings.model.ShortSetting 20import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
22import org.yuzu.yuzu_emu.features.settings.model.view.* 21import org.yuzu.yuzu_emu.features.settings.model.view.*
23import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
24import org.yuzu.yuzu_emu.model.SettingsViewModel 22import org.yuzu.yuzu_emu.model.SettingsViewModel
25import org.yuzu.yuzu_emu.utils.NativeConfig 23import org.yuzu.yuzu_emu.utils.NativeConfig
26 24
27class SettingsFragmentPresenter( 25class SettingsFragmentPresenter(
28 private val settingsViewModel: SettingsViewModel, 26 private val settingsViewModel: SettingsViewModel,
29 private val adapter: SettingsAdapter, 27 private val adapter: SettingsAdapter,
30 private var menuTag: String, 28 private var menuTag: Settings.MenuTag
31 private var gameId: String
32) { 29) {
33 private var settingsList = ArrayList<SettingsItem>() 30 private var settingsList = ArrayList<SettingsItem>()
34 31
@@ -53,24 +50,15 @@ class SettingsFragmentPresenter(
53 } 50 }
54 51
55 fun loadSettingsList() { 52 fun loadSettingsList() {
56 if (!TextUtils.isEmpty(gameId)) {
57 settingsViewModel.setToolbarTitle(
58 context.getString(
59 R.string.advanced_settings_game,
60 gameId
61 )
62 )
63 }
64
65 val sl = ArrayList<SettingsItem>() 53 val sl = ArrayList<SettingsItem>()
66 when (menuTag) { 54 when (menuTag) {
67 SettingsFile.FILE_NAME_CONFIG -> addConfigSettings(sl) 55 Settings.MenuTag.SECTION_ROOT -> addConfigSettings(sl)
68 Settings.SECTION_GENERAL -> addGeneralSettings(sl) 56 Settings.MenuTag.SECTION_GENERAL -> addGeneralSettings(sl)
69 Settings.SECTION_SYSTEM -> addSystemSettings(sl) 57 Settings.MenuTag.SECTION_SYSTEM -> addSystemSettings(sl)
70 Settings.SECTION_RENDERER -> addGraphicsSettings(sl) 58 Settings.MenuTag.SECTION_RENDERER -> addGraphicsSettings(sl)
71 Settings.SECTION_AUDIO -> addAudioSettings(sl) 59 Settings.MenuTag.SECTION_AUDIO -> addAudioSettings(sl)
72 Settings.SECTION_THEME -> addThemeSettings(sl) 60 Settings.MenuTag.SECTION_THEME -> addThemeSettings(sl)
73 Settings.SECTION_DEBUG -> addDebugSettings(sl) 61 Settings.MenuTag.SECTION_DEBUG -> addDebugSettings(sl)
74 else -> { 62 else -> {
75 val context = YuzuApplication.appContext 63 val context = YuzuApplication.appContext
76 Toast.makeText( 64 Toast.makeText(
@@ -86,13 +74,12 @@ class SettingsFragmentPresenter(
86 } 74 }
87 75
88 private fun addConfigSettings(sl: ArrayList<SettingsItem>) { 76 private fun addConfigSettings(sl: ArrayList<SettingsItem>) {
89 settingsViewModel.setToolbarTitle(context.getString(R.string.advanced_settings))
90 sl.apply { 77 sl.apply {
91 add(SubmenuSetting(R.string.preferences_general, 0, Settings.SECTION_GENERAL)) 78 add(SubmenuSetting(R.string.preferences_general, 0, Settings.MenuTag.SECTION_GENERAL))
92 add(SubmenuSetting(R.string.preferences_system, 0, Settings.SECTION_SYSTEM)) 79 add(SubmenuSetting(R.string.preferences_system, 0, Settings.MenuTag.SECTION_SYSTEM))
93 add(SubmenuSetting(R.string.preferences_graphics, 0, Settings.SECTION_RENDERER)) 80 add(SubmenuSetting(R.string.preferences_graphics, 0, Settings.MenuTag.SECTION_RENDERER))
94 add(SubmenuSetting(R.string.preferences_audio, 0, Settings.SECTION_AUDIO)) 81 add(SubmenuSetting(R.string.preferences_audio, 0, Settings.MenuTag.SECTION_AUDIO))
95 add(SubmenuSetting(R.string.preferences_debug, 0, Settings.SECTION_DEBUG)) 82 add(SubmenuSetting(R.string.preferences_debug, 0, Settings.MenuTag.SECTION_DEBUG))
96 add( 83 add(
97 RunnableSetting(R.string.reset_to_default, 0, false) { 84 RunnableSetting(R.string.reset_to_default, 0, false) {
98 settingsViewModel.setShouldShowResetSettingsDialog(true) 85 settingsViewModel.setShouldShowResetSettingsDialog(true)
@@ -102,7 +89,6 @@ class SettingsFragmentPresenter(
102 } 89 }
103 90
104 private fun addGeneralSettings(sl: ArrayList<SettingsItem>) { 91 private fun addGeneralSettings(sl: ArrayList<SettingsItem>) {
105 settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_general))
106 sl.apply { 92 sl.apply {
107 add(BooleanSetting.RENDERER_USE_SPEED_LIMIT.key) 93 add(BooleanSetting.RENDERER_USE_SPEED_LIMIT.key)
108 add(ShortSetting.RENDERER_SPEED_LIMIT.key) 94 add(ShortSetting.RENDERER_SPEED_LIMIT.key)
@@ -112,7 +98,6 @@ class SettingsFragmentPresenter(
112 } 98 }
113 99
114 private fun addSystemSettings(sl: ArrayList<SettingsItem>) { 100 private fun addSystemSettings(sl: ArrayList<SettingsItem>) {
115 settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_system))
116 sl.apply { 101 sl.apply {
117 add(BooleanSetting.USE_DOCKED_MODE.key) 102 add(BooleanSetting.USE_DOCKED_MODE.key)
118 add(IntSetting.REGION_INDEX.key) 103 add(IntSetting.REGION_INDEX.key)
@@ -123,7 +108,6 @@ class SettingsFragmentPresenter(
123 } 108 }
124 109
125 private fun addGraphicsSettings(sl: ArrayList<SettingsItem>) { 110 private fun addGraphicsSettings(sl: ArrayList<SettingsItem>) {
126 settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_graphics))
127 sl.apply { 111 sl.apply {
128 add(IntSetting.RENDERER_ACCURACY.key) 112 add(IntSetting.RENDERER_ACCURACY.key)
129 add(IntSetting.RENDERER_RESOLUTION.key) 113 add(IntSetting.RENDERER_RESOLUTION.key)
@@ -140,7 +124,6 @@ class SettingsFragmentPresenter(
140 } 124 }
141 125
142 private fun addAudioSettings(sl: ArrayList<SettingsItem>) { 126 private fun addAudioSettings(sl: ArrayList<SettingsItem>) {
143 settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_audio))
144 sl.apply { 127 sl.apply {
145 add(IntSetting.AUDIO_OUTPUT_ENGINE.key) 128 add(IntSetting.AUDIO_OUTPUT_ENGINE.key)
146 add(ByteSetting.AUDIO_VOLUME.key) 129 add(ByteSetting.AUDIO_VOLUME.key)
@@ -148,7 +131,6 @@ class SettingsFragmentPresenter(
148 } 131 }
149 132
150 private fun addThemeSettings(sl: ArrayList<SettingsItem>) { 133 private fun addThemeSettings(sl: ArrayList<SettingsItem>) {
151 settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_theme))
152 sl.apply { 134 sl.apply {
153 val theme: AbstractIntSetting = object : AbstractIntSetting { 135 val theme: AbstractIntSetting = object : AbstractIntSetting {
154 override val int: Int 136 override val int: Int
@@ -261,7 +243,6 @@ class SettingsFragmentPresenter(
261 } 243 }
262 244
263 private fun addDebugSettings(sl: ArrayList<SettingsItem>) { 245 private fun addDebugSettings(sl: ArrayList<SettingsItem>) {
264 settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_debug))
265 sl.apply { 246 sl.apply {
266 add(HeaderSetting(R.string.gpu)) 247 add(HeaderSetting(R.string.gpu))
267 add(IntSetting.RENDERER_BACKEND.key) 248 add(IntSetting.RENDERER_BACKEND.key)
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 944ae652e..3e6c157c7 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
@@ -39,6 +39,7 @@ import androidx.window.layout.WindowLayoutInfo
39import com.google.android.material.dialog.MaterialAlertDialogBuilder 39import com.google.android.material.dialog.MaterialAlertDialogBuilder
40import com.google.android.material.slider.Slider 40import com.google.android.material.slider.Slider
41import kotlinx.coroutines.Dispatchers 41import kotlinx.coroutines.Dispatchers
42import kotlinx.coroutines.flow.collectLatest
42import kotlinx.coroutines.launch 43import kotlinx.coroutines.launch
43import org.yuzu.yuzu_emu.HomeNavigationDirections 44import org.yuzu.yuzu_emu.HomeNavigationDirections
44import org.yuzu.yuzu_emu.NativeLibrary 45import org.yuzu.yuzu_emu.NativeLibrary
@@ -49,7 +50,6 @@ import org.yuzu.yuzu_emu.databinding.DialogOverlayAdjustBinding
49import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding 50import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding
50import org.yuzu.yuzu_emu.features.settings.model.IntSetting 51import org.yuzu.yuzu_emu.features.settings.model.IntSetting
51import org.yuzu.yuzu_emu.features.settings.model.Settings 52import org.yuzu.yuzu_emu.features.settings.model.Settings
52import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
53import org.yuzu.yuzu_emu.model.Game 53import org.yuzu.yuzu_emu.model.Game
54import org.yuzu.yuzu_emu.model.EmulationViewModel 54import org.yuzu.yuzu_emu.model.EmulationViewModel
55import org.yuzu.yuzu_emu.overlay.InputOverlay 55import org.yuzu.yuzu_emu.overlay.InputOverlay
@@ -129,6 +129,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
129 return binding.root 129 return binding.root
130 } 130 }
131 131
132 // This is using the correct scope, lint is just acting up
133 @SuppressLint("UnsafeRepeatOnLifecycleDetector")
132 override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 134 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
133 binding.surfaceEmulation.holder.addCallback(this) 135 binding.surfaceEmulation.holder.addCallback(this)
134 binding.showFpsText.setTextColor(Color.YELLOW) 136 binding.showFpsText.setTextColor(Color.YELLOW)
@@ -163,7 +165,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
163 R.id.menu_settings -> { 165 R.id.menu_settings -> {
164 val action = HomeNavigationDirections.actionGlobalSettingsActivity( 166 val action = HomeNavigationDirections.actionGlobalSettingsActivity(
165 null, 167 null,
166 SettingsFile.FILE_NAME_CONFIG 168 Settings.MenuTag.SECTION_ROOT
167 ) 169 )
168 binding.root.findNavController().navigate(action) 170 binding.root.findNavController().navigate(action)
169 true 171 true
@@ -205,59 +207,80 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
205 } 207 }
206 ) 208 )
207 209
208 viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Main) {
209 lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
210 WindowInfoTracker.getOrCreate(requireContext())
211 .windowLayoutInfo(requireActivity())
212 .collect { updateFoldableLayout(requireActivity() as EmulationActivity, it) }
213 }
214 }
215
216 GameIconUtils.loadGameIcon(game, binding.loadingImage) 210 GameIconUtils.loadGameIcon(game, binding.loadingImage)
217 binding.loadingTitle.text = game.title 211 binding.loadingTitle.text = game.title
218 binding.loadingTitle.isSelected = true 212 binding.loadingTitle.isSelected = true
219 binding.loadingText.isSelected = true 213 binding.loadingText.isSelected = true
220 214
221 emulationViewModel.shaderProgress.observe(viewLifecycleOwner) { 215 viewLifecycleOwner.lifecycleScope.apply {
222 if (it > 0 && it != emulationViewModel.totalShaders.value!!) { 216 launch {
223 binding.loadingProgressIndicator.isIndeterminate = false 217 repeatOnLifecycle(Lifecycle.State.STARTED) {
224 218 WindowInfoTracker.getOrCreate(requireContext())
225 if (it < binding.loadingProgressIndicator.max) { 219 .windowLayoutInfo(requireActivity())
226 binding.loadingProgressIndicator.progress = it 220 .collect {
221 updateFoldableLayout(requireActivity() as EmulationActivity, it)
222 }
227 } 223 }
228 } 224 }
225 launch {
226 repeatOnLifecycle(Lifecycle.State.CREATED) {
227 emulationViewModel.shaderProgress.collectLatest {
228 if (it > 0 && it != emulationViewModel.totalShaders.value) {
229 binding.loadingProgressIndicator.isIndeterminate = false
230
231 if (it < binding.loadingProgressIndicator.max) {
232 binding.loadingProgressIndicator.progress = it
233 }
234 }
229 235
230 if (it == emulationViewModel.totalShaders.value!!) { 236 if (it == emulationViewModel.totalShaders.value) {
231 binding.loadingText.setText(R.string.loading) 237 binding.loadingText.setText(R.string.loading)
232 binding.loadingProgressIndicator.isIndeterminate = true 238 binding.loadingProgressIndicator.isIndeterminate = true
239 }
240 }
241 }
233 } 242 }
234 } 243 launch {
235 emulationViewModel.totalShaders.observe(viewLifecycleOwner) { 244 repeatOnLifecycle(Lifecycle.State.CREATED) {
236 binding.loadingProgressIndicator.max = it 245 emulationViewModel.totalShaders.collectLatest {
237 } 246 binding.loadingProgressIndicator.max = it
238 emulationViewModel.shaderMessage.observe(viewLifecycleOwner) { 247 }
239 if (it.isNotEmpty()) { 248 }
240 binding.loadingText.text = it
241 } 249 }
242 } 250 launch {
243 251 repeatOnLifecycle(Lifecycle.State.CREATED) {
244 emulationViewModel.emulationStarted.observe(viewLifecycleOwner) { started -> 252 emulationViewModel.shaderMessage.collectLatest {
245 if (started) { 253 if (it.isNotEmpty()) {
246 binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED) 254 binding.loadingText.text = it
247 ViewUtils.showView(binding.surfaceInputOverlay) 255 }
248 ViewUtils.hideView(binding.loadingIndicator) 256 }
249 257 }
250 // Setup overlay
251 updateShowFpsOverlay()
252 } 258 }
253 } 259 launch {
260 repeatOnLifecycle(Lifecycle.State.CREATED) {
261 emulationViewModel.emulationStarted.collectLatest {
262 if (it) {
263 binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
264 ViewUtils.showView(binding.surfaceInputOverlay)
265 ViewUtils.hideView(binding.loadingIndicator)
254 266
255 emulationViewModel.isEmulationStopping.observe(viewLifecycleOwner) { 267 // Setup overlay
256 if (it) { 268 updateShowFpsOverlay()
257 binding.loadingText.setText(R.string.shutting_down) 269 }
258 ViewUtils.showView(binding.loadingIndicator) 270 }
259 ViewUtils.hideView(binding.inputContainer) 271 }
260 ViewUtils.hideView(binding.showFpsText) 272 }
273 launch {
274 repeatOnLifecycle(Lifecycle.State.CREATED) {
275 emulationViewModel.isEmulationStopping.collectLatest {
276 if (it) {
277 binding.loadingText.setText(R.string.shutting_down)
278 ViewUtils.showView(binding.loadingIndicator)
279 ViewUtils.hideView(binding.inputContainer)
280 ViewUtils.hideView(binding.showFpsText)
281 }
282 }
283 }
261 } 284 }
262 } 285 }
263 } 286 }
@@ -274,9 +297,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
274 } 297 }
275 } 298 }
276 } else { 299 } else {
277 if (EmulationMenuSettings.showOverlay && 300 if (EmulationMenuSettings.showOverlay && emulationViewModel.emulationStarted.value) {
278 emulationViewModel.emulationStarted.value == true
279 ) {
280 binding.surfaceInputOverlay.post { 301 binding.surfaceInputOverlay.post {
281 binding.surfaceInputOverlay.visibility = View.VISIBLE 302 binding.surfaceInputOverlay.visibility = View.VISIBLE
282 } 303 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt
index cbbe14d22..c119e69c9 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt
@@ -37,7 +37,6 @@ import org.yuzu.yuzu_emu.adapters.HomeSettingAdapter
37import org.yuzu.yuzu_emu.databinding.FragmentHomeSettingsBinding 37import org.yuzu.yuzu_emu.databinding.FragmentHomeSettingsBinding
38import org.yuzu.yuzu_emu.features.DocumentProvider 38import org.yuzu.yuzu_emu.features.DocumentProvider
39import org.yuzu.yuzu_emu.features.settings.model.Settings 39import org.yuzu.yuzu_emu.features.settings.model.Settings
40import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
41import org.yuzu.yuzu_emu.model.HomeSetting 40import org.yuzu.yuzu_emu.model.HomeSetting
42import org.yuzu.yuzu_emu.model.HomeViewModel 41import org.yuzu.yuzu_emu.model.HomeViewModel
43import org.yuzu.yuzu_emu.ui.main.MainActivity 42import org.yuzu.yuzu_emu.ui.main.MainActivity
@@ -78,7 +77,7 @@ class HomeSettingsFragment : Fragment() {
78 { 77 {
79 val action = HomeNavigationDirections.actionGlobalSettingsActivity( 78 val action = HomeNavigationDirections.actionGlobalSettingsActivity(
80 null, 79 null,
81 SettingsFile.FILE_NAME_CONFIG 80 Settings.MenuTag.SECTION_ROOT
82 ) 81 )
83 binding.root.findNavController().navigate(action) 82 binding.root.findNavController().navigate(action)
84 } 83 }
@@ -100,7 +99,7 @@ class HomeSettingsFragment : Fragment() {
100 { 99 {
101 val action = HomeNavigationDirections.actionGlobalSettingsActivity( 100 val action = HomeNavigationDirections.actionGlobalSettingsActivity(
102 null, 101 null,
103 Settings.SECTION_THEME 102 Settings.MenuTag.SECTION_THEME
104 ) 103 )
105 binding.root.findNavController().navigate(action) 104 binding.root.findNavController().navigate(action)
106 } 105 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt
index 181bd983a..ea8eb073a 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt
@@ -9,8 +9,12 @@ import android.widget.Toast
9import androidx.appcompat.app.AppCompatActivity 9import androidx.appcompat.app.AppCompatActivity
10import androidx.fragment.app.DialogFragment 10import androidx.fragment.app.DialogFragment
11import androidx.fragment.app.activityViewModels 11import androidx.fragment.app.activityViewModels
12import androidx.lifecycle.Lifecycle
12import androidx.lifecycle.ViewModelProvider 13import androidx.lifecycle.ViewModelProvider
14import androidx.lifecycle.lifecycleScope
15import androidx.lifecycle.repeatOnLifecycle
13import com.google.android.material.dialog.MaterialAlertDialogBuilder 16import com.google.android.material.dialog.MaterialAlertDialogBuilder
17import kotlinx.coroutines.launch
14import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding 18import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
15import org.yuzu.yuzu_emu.model.TaskViewModel 19import org.yuzu.yuzu_emu.model.TaskViewModel
16 20
@@ -28,21 +32,27 @@ class IndeterminateProgressDialogFragment : DialogFragment() {
28 .create() 32 .create()
29 dialog.setCanceledOnTouchOutside(false) 33 dialog.setCanceledOnTouchOutside(false)
30 34
31 taskViewModel.isComplete.observe(this) { complete -> 35 viewLifecycleOwner.lifecycleScope.launch {
32 if (complete) { 36 repeatOnLifecycle(Lifecycle.State.CREATED) {
33 dialog.dismiss() 37 taskViewModel.isComplete.collect {
34 when (val result = taskViewModel.result.value) { 38 if (it) {
35 is String -> Toast.makeText(requireContext(), result, Toast.LENGTH_LONG).show() 39 dialog.dismiss()
36 is MessageDialogFragment -> result.show( 40 when (val result = taskViewModel.result.value) {
37 requireActivity().supportFragmentManager, 41 is String -> Toast.makeText(requireContext(), result, Toast.LENGTH_LONG)
38 MessageDialogFragment.TAG 42 .show()
39 ) 43
44 is MessageDialogFragment -> result.show(
45 requireActivity().supportFragmentManager,
46 MessageDialogFragment.TAG
47 )
48 }
49 taskViewModel.clear()
50 }
40 } 51 }
41 taskViewModel.clear()
42 } 52 }
43 } 53 }
44 54
45 if (taskViewModel.isRunning.value == false) { 55 if (!taskViewModel.isRunning.value) {
46 taskViewModel.runTask() 56 taskViewModel.runTask()
47 } 57 }
48 return dialog 58 return dialog
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt
index f54dccc69..2dbca76a5 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt
@@ -3,6 +3,7 @@
3 3
4package org.yuzu.yuzu_emu.fragments 4package org.yuzu.yuzu_emu.fragments
5 5
6import android.annotation.SuppressLint
6import android.content.Context 7import android.content.Context
7import android.content.SharedPreferences 8import android.content.SharedPreferences
8import android.os.Bundle 9import android.os.Bundle
@@ -17,9 +18,13 @@ import androidx.core.view.updatePadding
17import androidx.core.widget.doOnTextChanged 18import androidx.core.widget.doOnTextChanged
18import androidx.fragment.app.Fragment 19import androidx.fragment.app.Fragment
19import androidx.fragment.app.activityViewModels 20import androidx.fragment.app.activityViewModels
21import androidx.lifecycle.Lifecycle
22import androidx.lifecycle.lifecycleScope
23import androidx.lifecycle.repeatOnLifecycle
20import androidx.preference.PreferenceManager 24import androidx.preference.PreferenceManager
21import info.debatty.java.stringsimilarity.Jaccard 25import info.debatty.java.stringsimilarity.Jaccard
22import info.debatty.java.stringsimilarity.JaroWinkler 26import info.debatty.java.stringsimilarity.JaroWinkler
27import kotlinx.coroutines.launch
23import java.util.Locale 28import java.util.Locale
24import org.yuzu.yuzu_emu.R 29import org.yuzu.yuzu_emu.R
25import org.yuzu.yuzu_emu.YuzuApplication 30import org.yuzu.yuzu_emu.YuzuApplication
@@ -52,6 +57,8 @@ class SearchFragment : Fragment() {
52 return binding.root 57 return binding.root
53 } 58 }
54 59
60 // This is using the correct scope, lint is just acting up
61 @SuppressLint("UnsafeRepeatOnLifecycleDetector")
55 override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 62 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
56 homeViewModel.setNavigationVisibility(visible = true, animated = false) 63 homeViewModel.setNavigationVisibility(visible = true, animated = false)
57 preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) 64 preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
@@ -79,21 +86,32 @@ class SearchFragment : Fragment() {
79 filterAndSearch() 86 filterAndSearch()
80 } 87 }
81 88
82 gamesViewModel.apply { 89 viewLifecycleOwner.lifecycleScope.apply {
83 searchFocused.observe(viewLifecycleOwner) { searchFocused -> 90 launch {
84 if (searchFocused) { 91 repeatOnLifecycle(Lifecycle.State.CREATED) {
85 focusSearch() 92 gamesViewModel.searchFocused.collect {
86 gamesViewModel.setSearchFocused(false) 93 if (it) {
94 focusSearch()
95 gamesViewModel.setSearchFocused(false)
96 }
97 }
87 } 98 }
88 } 99 }
89 100 launch {
90 games.observe(viewLifecycleOwner) { filterAndSearch() } 101 repeatOnLifecycle(Lifecycle.State.CREATED) {
91 searchedGames.observe(viewLifecycleOwner) { 102 gamesViewModel.games.collect { filterAndSearch() }
92 (binding.gridGamesSearch.adapter as GameAdapter).submitList(it) 103 }
93 if (it.isEmpty()) { 104 }
94 binding.noResultsView.visibility = View.VISIBLE 105 launch {
95 } else { 106 repeatOnLifecycle(Lifecycle.State.CREATED) {
96 binding.noResultsView.visibility = View.GONE 107 gamesViewModel.searchedGames.collect {
108 (binding.gridGamesSearch.adapter as GameAdapter).submitList(it)
109 if (it.isEmpty()) {
110 binding.noResultsView.visibility = View.VISIBLE
111 } else {
112 binding.noResultsView.visibility = View.GONE
113 }
114 }
97 } 115 }
98 } 116 }
99 } 117 }
@@ -109,7 +127,7 @@ class SearchFragment : Fragment() {
109 private inner class ScoredGame(val score: Double, val item: Game) 127 private inner class ScoredGame(val score: Double, val item: Game)
110 128
111 private fun filterAndSearch() { 129 private fun filterAndSearch() {
112 val baseList = gamesViewModel.games.value!! 130 val baseList = gamesViewModel.games.value
113 val filteredList: List<Game> = when (binding.chipGroup.checkedChipId) { 131 val filteredList: List<Game> = when (binding.chipGroup.checkedChipId) {
114 R.id.chip_recently_played -> { 132 R.id.chip_recently_played -> {
115 baseList.filter { 133 baseList.filter {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsSearchFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsSearchFragment.kt
index 55b6a0367..9d0594c6e 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsSearchFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsSearchFragment.kt
@@ -15,10 +15,14 @@ import androidx.core.view.updatePadding
15import androidx.core.widget.doOnTextChanged 15import androidx.core.widget.doOnTextChanged
16import androidx.fragment.app.Fragment 16import androidx.fragment.app.Fragment
17import androidx.fragment.app.activityViewModels 17import androidx.fragment.app.activityViewModels
18import androidx.lifecycle.Lifecycle
19import androidx.lifecycle.lifecycleScope
20import androidx.lifecycle.repeatOnLifecycle
18import androidx.recyclerview.widget.LinearLayoutManager 21import androidx.recyclerview.widget.LinearLayoutManager
19import com.google.android.material.divider.MaterialDividerItemDecoration 22import com.google.android.material.divider.MaterialDividerItemDecoration
20import com.google.android.material.transition.MaterialSharedAxis 23import com.google.android.material.transition.MaterialSharedAxis
21import info.debatty.java.stringsimilarity.Cosine 24import info.debatty.java.stringsimilarity.Cosine
25import kotlinx.coroutines.launch
22import org.yuzu.yuzu_emu.R 26import org.yuzu.yuzu_emu.R
23import org.yuzu.yuzu_emu.databinding.FragmentSettingsSearchBinding 27import org.yuzu.yuzu_emu.databinding.FragmentSettingsSearchBinding
24import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem 28import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
@@ -79,10 +83,14 @@ class SettingsSearchFragment : Fragment() {
79 search() 83 search()
80 binding.settingsList.smoothScrollToPosition(0) 84 binding.settingsList.smoothScrollToPosition(0)
81 } 85 }
82 settingsViewModel.shouldReloadSettingsList.observe(viewLifecycleOwner) { 86 viewLifecycleOwner.lifecycleScope.launch {
83 if (it) { 87 repeatOnLifecycle(Lifecycle.State.CREATED) {
84 settingsViewModel.setShouldReloadSettingsList(false) 88 settingsViewModel.shouldReloadSettingsList.collect {
85 search() 89 if (it) {
90 settingsViewModel.setShouldReloadSettingsList(false)
91 search()
92 }
93 }
86 } 94 }
87 } 95 }
88 96
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt
index d50c421a0..fbb2f6e18 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt
@@ -22,10 +22,14 @@ import androidx.core.view.isVisible
22import androidx.core.view.updatePadding 22import androidx.core.view.updatePadding
23import androidx.fragment.app.Fragment 23import androidx.fragment.app.Fragment
24import androidx.fragment.app.activityViewModels 24import androidx.fragment.app.activityViewModels
25import androidx.lifecycle.Lifecycle
26import androidx.lifecycle.lifecycleScope
27import androidx.lifecycle.repeatOnLifecycle
25import androidx.navigation.findNavController 28import androidx.navigation.findNavController
26import androidx.preference.PreferenceManager 29import androidx.preference.PreferenceManager
27import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback 30import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
28import com.google.android.material.transition.MaterialFadeThrough 31import com.google.android.material.transition.MaterialFadeThrough
32import kotlinx.coroutines.launch
29import java.io.File 33import java.io.File
30import org.yuzu.yuzu_emu.R 34import org.yuzu.yuzu_emu.R
31import org.yuzu.yuzu_emu.YuzuApplication 35import org.yuzu.yuzu_emu.YuzuApplication
@@ -206,10 +210,14 @@ class SetupFragment : Fragment() {
206 ) 210 )
207 } 211 }
208 212
209 homeViewModel.shouldPageForward.observe(viewLifecycleOwner) { 213 viewLifecycleOwner.lifecycleScope.launch {
210 if (it) { 214 repeatOnLifecycle(Lifecycle.State.CREATED) {
211 pageForward() 215 homeViewModel.shouldPageForward.collect {
212 homeViewModel.setShouldPageForward(false) 216 if (it) {
217 pageForward()
218 homeViewModel.setShouldPageForward(false)
219 }
220 }
213 } 221 }
214 } 222 }
215 223
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/EmulationViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/EmulationViewModel.kt
index e35f51bc3..f34870c2d 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/EmulationViewModel.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/EmulationViewModel.kt
@@ -3,28 +3,28 @@
3 3
4package org.yuzu.yuzu_emu.model 4package org.yuzu.yuzu_emu.model
5 5
6import androidx.lifecycle.LiveData
7import androidx.lifecycle.MutableLiveData
8import androidx.lifecycle.ViewModel 6import androidx.lifecycle.ViewModel
7import kotlinx.coroutines.flow.MutableStateFlow
8import kotlinx.coroutines.flow.StateFlow
9 9
10class EmulationViewModel : ViewModel() { 10class EmulationViewModel : ViewModel() {
11 private val _emulationStarted = MutableLiveData(false) 11 val emulationStarted: StateFlow<Boolean> get() = _emulationStarted
12 val emulationStarted: LiveData<Boolean> get() = _emulationStarted 12 private val _emulationStarted = MutableStateFlow(false)
13 13
14 private val _isEmulationStopping = MutableLiveData(false) 14 val isEmulationStopping: StateFlow<Boolean> get() = _isEmulationStopping
15 val isEmulationStopping: LiveData<Boolean> get() = _isEmulationStopping 15 private val _isEmulationStopping = MutableStateFlow(false)
16 16
17 private val _shaderProgress = MutableLiveData(0) 17 val shaderProgress: StateFlow<Int> get() = _shaderProgress
18 val shaderProgress: LiveData<Int> get() = _shaderProgress 18 private val _shaderProgress = MutableStateFlow(0)
19 19
20 private val _totalShaders = MutableLiveData(0) 20 val totalShaders: StateFlow<Int> get() = _totalShaders
21 val totalShaders: LiveData<Int> get() = _totalShaders 21 private val _totalShaders = MutableStateFlow(0)
22 22
23 private val _shaderMessage = MutableLiveData("") 23 val shaderMessage: StateFlow<String> get() = _shaderMessage
24 val shaderMessage: LiveData<String> get() = _shaderMessage 24 private val _shaderMessage = MutableStateFlow("")
25 25
26 fun setEmulationStarted(started: Boolean) { 26 fun setEmulationStarted(started: Boolean) {
27 _emulationStarted.postValue(started) 27 _emulationStarted.value = started
28 } 28 }
29 29
30 fun setIsEmulationStopping(value: Boolean) { 30 fun setIsEmulationStopping(value: Boolean) {
@@ -50,10 +50,18 @@ class EmulationViewModel : ViewModel() {
50 } 50 }
51 51
52 fun clear() { 52 fun clear() {
53 _emulationStarted.value = false 53 setEmulationStarted(false)
54 _isEmulationStopping.value = false 54 setIsEmulationStopping(false)
55 _shaderProgress.value = 0 55 setShaderProgress(0)
56 _totalShaders.value = 0 56 setTotalShaders(0)
57 _shaderMessage.value = "" 57 setShaderMessage("")
58 }
59
60 companion object {
61 const val KEY_EMULATION_STARTED = "EmulationStarted"
62 const val KEY_IS_EMULATION_STOPPING = "IsEmulationStarting"
63 const val KEY_SHADER_PROGRESS = "ShaderProgress"
64 const val KEY_TOTAL_SHADERS = "TotalShaders"
65 const val KEY_SHADER_MESSAGE = "ShaderMessage"
58 } 66 }
59} 67}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt
index 1fe42f922..6e09fa81d 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt
@@ -5,13 +5,13 @@ package org.yuzu.yuzu_emu.model
5 5
6import android.net.Uri 6import android.net.Uri
7import androidx.documentfile.provider.DocumentFile 7import androidx.documentfile.provider.DocumentFile
8import androidx.lifecycle.LiveData
9import androidx.lifecycle.MutableLiveData
10import androidx.lifecycle.ViewModel 8import androidx.lifecycle.ViewModel
11import androidx.lifecycle.viewModelScope 9import androidx.lifecycle.viewModelScope
12import androidx.preference.PreferenceManager 10import androidx.preference.PreferenceManager
13import java.util.Locale 11import java.util.Locale
14import kotlinx.coroutines.Dispatchers 12import kotlinx.coroutines.Dispatchers
13import kotlinx.coroutines.flow.MutableStateFlow
14import kotlinx.coroutines.flow.StateFlow
15import kotlinx.coroutines.launch 15import kotlinx.coroutines.launch
16import kotlinx.coroutines.withContext 16import kotlinx.coroutines.withContext
17import kotlinx.serialization.ExperimentalSerializationApi 17import kotlinx.serialization.ExperimentalSerializationApi
@@ -24,23 +24,23 @@ import org.yuzu.yuzu_emu.utils.GameHelper
24 24
25@OptIn(ExperimentalSerializationApi::class) 25@OptIn(ExperimentalSerializationApi::class)
26class GamesViewModel : ViewModel() { 26class GamesViewModel : ViewModel() {
27 private val _games = MutableLiveData<List<Game>>(emptyList()) 27 val games: StateFlow<List<Game>> get() = _games
28 val games: LiveData<List<Game>> get() = _games 28 private val _games = MutableStateFlow(emptyList<Game>())
29 29
30 private val _searchedGames = MutableLiveData<List<Game>>(emptyList()) 30 val searchedGames: StateFlow<List<Game>> get() = _searchedGames
31 val searchedGames: LiveData<List<Game>> get() = _searchedGames 31 private val _searchedGames = MutableStateFlow(emptyList<Game>())
32 32
33 private val _isReloading = MutableLiveData(false) 33 val isReloading: StateFlow<Boolean> get() = _isReloading
34 val isReloading: LiveData<Boolean> get() = _isReloading 34 private val _isReloading = MutableStateFlow(false)
35 35
36 private val _shouldSwapData = MutableLiveData(false) 36 val shouldSwapData: StateFlow<Boolean> get() = _shouldSwapData
37 val shouldSwapData: LiveData<Boolean> get() = _shouldSwapData 37 private val _shouldSwapData = MutableStateFlow(false)
38 38
39 private val _shouldScrollToTop = MutableLiveData(false) 39 val shouldScrollToTop: StateFlow<Boolean> get() = _shouldScrollToTop
40 val shouldScrollToTop: LiveData<Boolean> get() = _shouldScrollToTop 40 private val _shouldScrollToTop = MutableStateFlow(false)
41 41
42 private val _searchFocused = MutableLiveData(false) 42 val searchFocused: StateFlow<Boolean> get() = _searchFocused
43 val searchFocused: LiveData<Boolean> get() = _searchFocused 43 private val _searchFocused = MutableStateFlow(false)
44 44
45 init { 45 init {
46 // Ensure keys are loaded so that ROM metadata can be decrypted. 46 // Ensure keys are loaded so that ROM metadata can be decrypted.
@@ -79,36 +79,36 @@ class GamesViewModel : ViewModel() {
79 ) 79 )
80 ) 80 )
81 81
82 _games.postValue(sortedList) 82 _games.value = sortedList
83 } 83 }
84 84
85 fun setSearchedGames(games: List<Game>) { 85 fun setSearchedGames(games: List<Game>) {
86 _searchedGames.postValue(games) 86 _searchedGames.value = games
87 } 87 }
88 88
89 fun setShouldSwapData(shouldSwap: Boolean) { 89 fun setShouldSwapData(shouldSwap: Boolean) {
90 _shouldSwapData.postValue(shouldSwap) 90 _shouldSwapData.value = shouldSwap
91 } 91 }
92 92
93 fun setShouldScrollToTop(shouldScroll: Boolean) { 93 fun setShouldScrollToTop(shouldScroll: Boolean) {
94 _shouldScrollToTop.postValue(shouldScroll) 94 _shouldScrollToTop.value = shouldScroll
95 } 95 }
96 96
97 fun setSearchFocused(searchFocused: Boolean) { 97 fun setSearchFocused(searchFocused: Boolean) {
98 _searchFocused.postValue(searchFocused) 98 _searchFocused.value = searchFocused
99 } 99 }
100 100
101 fun reloadGames(directoryChanged: Boolean) { 101 fun reloadGames(directoryChanged: Boolean) {
102 if (isReloading.value == true) { 102 if (isReloading.value) {
103 return 103 return
104 } 104 }
105 _isReloading.postValue(true) 105 _isReloading.value = true
106 106
107 viewModelScope.launch { 107 viewModelScope.launch {
108 withContext(Dispatchers.IO) { 108 withContext(Dispatchers.IO) {
109 NativeLibrary.resetRomMetadata() 109 NativeLibrary.resetRomMetadata()
110 setGames(GameHelper.getGames()) 110 setGames(GameHelper.getGames())
111 _isReloading.postValue(false) 111 _isReloading.value = false
112 112
113 if (directoryChanged) { 113 if (directoryChanged) {
114 setShouldSwapData(true) 114 setShouldSwapData(true)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeSetting.kt
index 498c222fa..b32e19373 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeSetting.kt
@@ -3,8 +3,8 @@
3 3
4package org.yuzu.yuzu_emu.model 4package org.yuzu.yuzu_emu.model
5 5
6import androidx.lifecycle.LiveData 6import kotlinx.coroutines.flow.MutableStateFlow
7import androidx.lifecycle.MutableLiveData 7import kotlinx.coroutines.flow.StateFlow
8 8
9data class HomeSetting( 9data class HomeSetting(
10 val titleId: Int, 10 val titleId: Int,
@@ -14,5 +14,5 @@ data class HomeSetting(
14 val isEnabled: () -> Boolean = { true }, 14 val isEnabled: () -> Boolean = { true },
15 val disabledTitleId: Int = 0, 15 val disabledTitleId: Int = 0,
16 val disabledMessageId: Int = 0, 16 val disabledMessageId: Int = 0,
17 val details: LiveData<String> = MutableLiveData("") 17 val details: StateFlow<String> = MutableStateFlow("")
18) 18)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt
index a48ef7a88..756f76721 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt
@@ -5,47 +5,43 @@ package org.yuzu.yuzu_emu.model
5 5
6import android.net.Uri 6import android.net.Uri
7import androidx.fragment.app.FragmentActivity 7import androidx.fragment.app.FragmentActivity
8import androidx.lifecycle.LiveData
9import androidx.lifecycle.MutableLiveData
10import androidx.lifecycle.ViewModel 8import androidx.lifecycle.ViewModel
11import androidx.lifecycle.ViewModelProvider 9import androidx.lifecycle.ViewModelProvider
12import androidx.preference.PreferenceManager 10import androidx.preference.PreferenceManager
11import kotlinx.coroutines.flow.MutableStateFlow
12import kotlinx.coroutines.flow.StateFlow
13import org.yuzu.yuzu_emu.YuzuApplication 13import org.yuzu.yuzu_emu.YuzuApplication
14import org.yuzu.yuzu_emu.utils.GameHelper 14import org.yuzu.yuzu_emu.utils.GameHelper
15 15
16class HomeViewModel : ViewModel() { 16class HomeViewModel : ViewModel() {
17 private val _navigationVisible = MutableLiveData<Pair<Boolean, Boolean>>() 17 val navigationVisible: StateFlow<Pair<Boolean, Boolean>> get() = _navigationVisible
18 val navigationVisible: LiveData<Pair<Boolean, Boolean>> get() = _navigationVisible 18 private val _navigationVisible = MutableStateFlow(Pair(false, false))
19 19
20 private val _statusBarShadeVisible = MutableLiveData(true) 20 val statusBarShadeVisible: StateFlow<Boolean> get() = _statusBarShadeVisible
21 val statusBarShadeVisible: LiveData<Boolean> get() = _statusBarShadeVisible 21 private val _statusBarShadeVisible = MutableStateFlow(true)
22 22
23 private val _shouldPageForward = MutableLiveData(false) 23 val shouldPageForward: StateFlow<Boolean> get() = _shouldPageForward
24 val shouldPageForward: LiveData<Boolean> get() = _shouldPageForward 24 private val _shouldPageForward = MutableStateFlow(false)
25 25
26 private val _gamesDir = MutableLiveData( 26 val gamesDir: StateFlow<String> get() = _gamesDir
27 private val _gamesDir = MutableStateFlow(
27 Uri.parse( 28 Uri.parse(
28 PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) 29 PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
29 .getString(GameHelper.KEY_GAME_PATH, "") 30 .getString(GameHelper.KEY_GAME_PATH, "")
30 ).path ?: "" 31 ).path ?: ""
31 ) 32 )
32 val gamesDir: LiveData<String> get() = _gamesDir
33 33
34 var navigatedToSetup = false 34 var navigatedToSetup = false
35 35
36 init {
37 _navigationVisible.value = Pair(false, false)
38 }
39
40 fun setNavigationVisibility(visible: Boolean, animated: Boolean) { 36 fun setNavigationVisibility(visible: Boolean, animated: Boolean) {
41 if (_navigationVisible.value?.first == visible) { 37 if (navigationVisible.value.first == visible) {
42 return 38 return
43 } 39 }
44 _navigationVisible.value = Pair(visible, animated) 40 _navigationVisible.value = Pair(visible, animated)
45 } 41 }
46 42
47 fun setStatusBarShadeVisibility(visible: Boolean) { 43 fun setStatusBarShadeVisibility(visible: Boolean) {
48 if (_statusBarShadeVisible.value == visible) { 44 if (statusBarShadeVisible.value == visible) {
49 return 45 return
50 } 46 }
51 _statusBarShadeVisible.value = visible 47 _statusBarShadeVisible.value = visible
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt
index d16d15fa6..53fa7a8de 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt
@@ -3,48 +3,43 @@
3 3
4package org.yuzu.yuzu_emu.model 4package org.yuzu.yuzu_emu.model
5 5
6import androidx.lifecycle.LiveData
7import androidx.lifecycle.MutableLiveData
8import androidx.lifecycle.SavedStateHandle
9import androidx.lifecycle.ViewModel 6import androidx.lifecycle.ViewModel
7import kotlinx.coroutines.flow.MutableStateFlow
8import kotlinx.coroutines.flow.StateFlow
10import org.yuzu.yuzu_emu.R 9import org.yuzu.yuzu_emu.R
11import org.yuzu.yuzu_emu.YuzuApplication 10import org.yuzu.yuzu_emu.YuzuApplication
12import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem 11import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
13 12
14class SettingsViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() { 13class SettingsViewModel : ViewModel() {
15 var game: Game? = null 14 var game: Game? = null
16 15
17 var shouldSave = false 16 var shouldSave = false
18 17
19 var clickedItem: SettingsItem? = null 18 var clickedItem: SettingsItem? = null
20 19
21 private val _toolbarTitle = MutableLiveData("") 20 val shouldRecreate: StateFlow<Boolean> get() = _shouldRecreate
22 val toolbarTitle: LiveData<String> get() = _toolbarTitle 21 private val _shouldRecreate = MutableStateFlow(false)
23 22
24 private val _shouldRecreate = MutableLiveData(false) 23 val shouldNavigateBack: StateFlow<Boolean> get() = _shouldNavigateBack
25 val shouldRecreate: LiveData<Boolean> get() = _shouldRecreate 24 private val _shouldNavigateBack = MutableStateFlow(false)
26 25
27 private val _shouldNavigateBack = MutableLiveData(false) 26 val shouldShowResetSettingsDialog: StateFlow<Boolean> get() = _shouldShowResetSettingsDialog
28 val shouldNavigateBack: LiveData<Boolean> get() = _shouldNavigateBack 27 private val _shouldShowResetSettingsDialog = MutableStateFlow(false)
29 28
30 private val _shouldShowResetSettingsDialog = MutableLiveData(false) 29 val shouldReloadSettingsList: StateFlow<Boolean> get() = _shouldReloadSettingsList
31 val shouldShowResetSettingsDialog: LiveData<Boolean> get() = _shouldShowResetSettingsDialog 30 private val _shouldReloadSettingsList = MutableStateFlow(false)
32 31
33 private val _shouldReloadSettingsList = MutableLiveData(false) 32 val isUsingSearch: StateFlow<Boolean> get() = _isUsingSearch
34 val shouldReloadSettingsList: LiveData<Boolean> get() = _shouldReloadSettingsList 33 private val _isUsingSearch = MutableStateFlow(false)
35 34
36 private val _isUsingSearch = MutableLiveData(false) 35 val sliderProgress: StateFlow<Int> get() = _sliderProgress
37 val isUsingSearch: LiveData<Boolean> get() = _isUsingSearch 36 private val _sliderProgress = MutableStateFlow(-1)
38 37
39 val sliderProgress = savedStateHandle.getStateFlow(KEY_SLIDER_PROGRESS, -1) 38 val sliderTextValue: StateFlow<String> get() = _sliderTextValue
39 private val _sliderTextValue = MutableStateFlow("")
40 40
41 val sliderTextValue = savedStateHandle.getStateFlow(KEY_SLIDER_TEXT_VALUE, "") 41 val adapterItemChanged: StateFlow<Int> get() = _adapterItemChanged
42 42 private val _adapterItemChanged = MutableStateFlow(-1)
43 val adapterItemChanged = savedStateHandle.getStateFlow(KEY_ADAPTER_ITEM_CHANGED, -1)
44
45 fun setToolbarTitle(value: String) {
46 _toolbarTitle.value = value
47 }
48 43
49 fun setShouldRecreate(value: Boolean) { 44 fun setShouldRecreate(value: Boolean) {
50 _shouldRecreate.value = value 45 _shouldRecreate.value = value
@@ -67,8 +62,8 @@ class SettingsViewModel(private val savedStateHandle: SavedStateHandle) : ViewMo
67 } 62 }
68 63
69 fun setSliderTextValue(value: Float, units: String) { 64 fun setSliderTextValue(value: Float, units: String) {
70 savedStateHandle[KEY_SLIDER_PROGRESS] = value 65 _sliderProgress.value = value.toInt()
71 savedStateHandle[KEY_SLIDER_TEXT_VALUE] = String.format( 66 _sliderTextValue.value = String.format(
72 YuzuApplication.appContext.getString(R.string.value_with_units), 67 YuzuApplication.appContext.getString(R.string.value_with_units),
73 value.toInt().toString(), 68 value.toInt().toString(),
74 units 69 units
@@ -76,21 +71,15 @@ class SettingsViewModel(private val savedStateHandle: SavedStateHandle) : ViewMo
76 } 71 }
77 72
78 fun setSliderProgress(value: Float) { 73 fun setSliderProgress(value: Float) {
79 savedStateHandle[KEY_SLIDER_PROGRESS] = value 74 _sliderProgress.value = value.toInt()
80 } 75 }
81 76
82 fun setAdapterItemChanged(value: Int) { 77 fun setAdapterItemChanged(value: Int) {
83 savedStateHandle[KEY_ADAPTER_ITEM_CHANGED] = value 78 _adapterItemChanged.value = value
84 } 79 }
85 80
86 fun clear() { 81 fun clear() {
87 game = null 82 game = null
88 shouldSave = false 83 shouldSave = false
89 } 84 }
90
91 companion object {
92 const val KEY_SLIDER_TEXT_VALUE = "SliderTextValue"
93 const val KEY_SLIDER_PROGRESS = "SliderProgress"
94 const val KEY_ADAPTER_ITEM_CHANGED = "AdapterItemChanged"
95 }
96} 85}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt
index 27ea725a5..531c2aaf0 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt
@@ -3,29 +3,25 @@
3 3
4package org.yuzu.yuzu_emu.model 4package org.yuzu.yuzu_emu.model
5 5
6import androidx.lifecycle.LiveData
7import androidx.lifecycle.MutableLiveData
8import androidx.lifecycle.ViewModel 6import androidx.lifecycle.ViewModel
9import androidx.lifecycle.viewModelScope 7import androidx.lifecycle.viewModelScope
10import kotlinx.coroutines.Dispatchers 8import kotlinx.coroutines.Dispatchers
9import kotlinx.coroutines.flow.MutableStateFlow
10import kotlinx.coroutines.flow.StateFlow
11import kotlinx.coroutines.launch 11import kotlinx.coroutines.launch
12 12
13class TaskViewModel : ViewModel() { 13class TaskViewModel : ViewModel() {
14 private val _result = MutableLiveData<Any>() 14 val result: StateFlow<Any> get() = _result
15 val result: LiveData<Any> = _result 15 private val _result = MutableStateFlow(Any())
16 16
17 private val _isComplete = MutableLiveData<Boolean>() 17 val isComplete: StateFlow<Boolean> get() = _isComplete
18 val isComplete: LiveData<Boolean> = _isComplete 18 private val _isComplete = MutableStateFlow(false)
19 19
20 private val _isRunning = MutableLiveData<Boolean>() 20 val isRunning: StateFlow<Boolean> get() = _isRunning
21 val isRunning: LiveData<Boolean> = _isRunning 21 private val _isRunning = MutableStateFlow(false)
22 22
23 lateinit var task: () -> Any 23 lateinit var task: () -> Any
24 24
25 init {
26 clear()
27 }
28
29 fun clear() { 25 fun clear() {
30 _result.value = Any() 26 _result.value = Any()
31 _isComplete.value = false 27 _isComplete.value = false
@@ -33,15 +29,16 @@ class TaskViewModel : ViewModel() {
33 } 29 }
34 30
35 fun runTask() { 31 fun runTask() {
36 if (_isRunning.value == true) { 32 if (isRunning.value) {
37 return 33 return
38 } 34 }
39 _isRunning.value = true 35 _isRunning.value = true
40 36
41 viewModelScope.launch(Dispatchers.IO) { 37 viewModelScope.launch(Dispatchers.IO) {
42 val res = task() 38 val res = task()
43 _result.postValue(res) 39 _result.value = res
44 _isComplete.postValue(true) 40 _isComplete.value = true
41 _isRunning.value = false
45 } 42 }
46 } 43 }
47} 44}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt
index b0156dca5..805b89b31 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt
@@ -3,6 +3,7 @@
3 3
4package org.yuzu.yuzu_emu.ui 4package org.yuzu.yuzu_emu.ui
5 5
6import android.annotation.SuppressLint
6import android.os.Bundle 7import android.os.Bundle
7import android.view.LayoutInflater 8import android.view.LayoutInflater
8import android.view.View 9import android.view.View
@@ -14,8 +15,12 @@ import androidx.core.view.WindowInsetsCompat
14import androidx.core.view.updatePadding 15import androidx.core.view.updatePadding
15import androidx.fragment.app.Fragment 16import androidx.fragment.app.Fragment
16import androidx.fragment.app.activityViewModels 17import androidx.fragment.app.activityViewModels
18import androidx.lifecycle.Lifecycle
19import androidx.lifecycle.lifecycleScope
20import androidx.lifecycle.repeatOnLifecycle
17import com.google.android.material.color.MaterialColors 21import com.google.android.material.color.MaterialColors
18import com.google.android.material.transition.MaterialFadeThrough 22import com.google.android.material.transition.MaterialFadeThrough
23import kotlinx.coroutines.launch
19import org.yuzu.yuzu_emu.R 24import org.yuzu.yuzu_emu.R
20import org.yuzu.yuzu_emu.adapters.GameAdapter 25import org.yuzu.yuzu_emu.adapters.GameAdapter
21import org.yuzu.yuzu_emu.databinding.FragmentGamesBinding 26import org.yuzu.yuzu_emu.databinding.FragmentGamesBinding
@@ -44,6 +49,8 @@ class GamesFragment : Fragment() {
44 return binding.root 49 return binding.root
45 } 50 }
46 51
52 // This is using the correct scope, lint is just acting up
53 @SuppressLint("UnsafeRepeatOnLifecycleDetector")
47 override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 54 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
48 homeViewModel.setNavigationVisibility(visible = true, animated = false) 55 homeViewModel.setNavigationVisibility(visible = true, animated = false)
49 56
@@ -80,37 +87,48 @@ class GamesFragment : Fragment() {
80 if (_binding == null) { 87 if (_binding == null) {
81 return@post 88 return@post
82 } 89 }
83 binding.swipeRefresh.isRefreshing = gamesViewModel.isReloading.value!! 90 binding.swipeRefresh.isRefreshing = gamesViewModel.isReloading.value
84 } 91 }
85 } 92 }
86 93
87 gamesViewModel.apply { 94 viewLifecycleOwner.lifecycleScope.apply {
88 // Watch for when we get updates to any of our games lists 95 launch {
89 isReloading.observe(viewLifecycleOwner) { isReloading -> 96 repeatOnLifecycle(Lifecycle.State.RESUMED) {
90 binding.swipeRefresh.isRefreshing = isReloading 97 gamesViewModel.isReloading.collect { binding.swipeRefresh.isRefreshing = it }
98 }
91 } 99 }
92 games.observe(viewLifecycleOwner) { 100 launch {
93 (binding.gridGames.adapter as GameAdapter).submitList(it) 101 repeatOnLifecycle(Lifecycle.State.RESUMED) {
94 if (it.isEmpty()) { 102 gamesViewModel.games.collect {
95 binding.noticeText.visibility = View.VISIBLE 103 (binding.gridGames.adapter as GameAdapter).submitList(it)
96 } else { 104 if (it.isEmpty()) {
97 binding.noticeText.visibility = View.GONE 105 binding.noticeText.visibility = View.VISIBLE
106 } else {
107 binding.noticeText.visibility = View.GONE
108 }
109 }
98 } 110 }
99 } 111 }
100 shouldSwapData.observe(viewLifecycleOwner) { shouldSwapData -> 112 launch {
101 if (shouldSwapData) { 113 repeatOnLifecycle(Lifecycle.State.RESUMED) {
102 (binding.gridGames.adapter as GameAdapter).submitList( 114 gamesViewModel.shouldSwapData.collect {
103 gamesViewModel.games.value!! 115 if (it) {
104 ) 116 (binding.gridGames.adapter as GameAdapter).submitList(
105 gamesViewModel.setShouldSwapData(false) 117 gamesViewModel.games.value
118 )
119 gamesViewModel.setShouldSwapData(false)
120 }
121 }
106 } 122 }
107 } 123 }
108 124 launch {
109 // Check if the user reselected the games menu item and then scroll to top of the list 125 repeatOnLifecycle(Lifecycle.State.RESUMED) {
110 shouldScrollToTop.observe(viewLifecycleOwner) { shouldScroll -> 126 gamesViewModel.shouldScrollToTop.collect {
111 if (shouldScroll) { 127 if (it) {
112 scrollToTop() 128 scrollToTop()
113 gamesViewModel.setShouldScrollToTop(false) 129 gamesViewModel.setShouldScrollToTop(false)
130 }
131 }
114 } 132 }
115 } 133 }
116 } 134 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
index 7d8e06ad8..b6b6c6c17 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
@@ -19,7 +19,9 @@ import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
19import androidx.core.view.ViewCompat 19import androidx.core.view.ViewCompat
20import androidx.core.view.WindowCompat 20import androidx.core.view.WindowCompat
21import androidx.core.view.WindowInsetsCompat 21import androidx.core.view.WindowInsetsCompat
22import androidx.lifecycle.Lifecycle
22import androidx.lifecycle.lifecycleScope 23import androidx.lifecycle.lifecycleScope
24import androidx.lifecycle.repeatOnLifecycle
23import androidx.navigation.NavController 25import androidx.navigation.NavController
24import androidx.navigation.fragment.NavHostFragment 26import androidx.navigation.fragment.NavHostFragment
25import androidx.navigation.ui.setupWithNavController 27import androidx.navigation.ui.setupWithNavController
@@ -40,7 +42,6 @@ import org.yuzu.yuzu_emu.activities.EmulationActivity
40import org.yuzu.yuzu_emu.databinding.ActivityMainBinding 42import org.yuzu.yuzu_emu.databinding.ActivityMainBinding
41import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding 43import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
42import org.yuzu.yuzu_emu.features.settings.model.Settings 44import org.yuzu.yuzu_emu.features.settings.model.Settings
43import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
44import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment 45import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment
45import org.yuzu.yuzu_emu.fragments.MessageDialogFragment 46import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
46import org.yuzu.yuzu_emu.model.GamesViewModel 47import org.yuzu.yuzu_emu.model.GamesViewModel
@@ -107,7 +108,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
107 R.id.homeSettingsFragment -> { 108 R.id.homeSettingsFragment -> {
108 val action = HomeNavigationDirections.actionGlobalSettingsActivity( 109 val action = HomeNavigationDirections.actionGlobalSettingsActivity(
109 null, 110 null,
110 SettingsFile.FILE_NAME_CONFIG 111 Settings.MenuTag.SECTION_ROOT
111 ) 112 )
112 navHostFragment.navController.navigate(action) 113 navHostFragment.navController.navigate(action)
113 } 114 }
@@ -115,16 +116,22 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
115 } 116 }
116 117
117 // Prevents navigation from being drawn for a short time on recreation if set to hidden 118 // Prevents navigation from being drawn for a short time on recreation if set to hidden
118 if (!homeViewModel.navigationVisible.value?.first!!) { 119 if (!homeViewModel.navigationVisible.value.first) {
119 binding.navigationView.visibility = View.INVISIBLE 120 binding.navigationView.visibility = View.INVISIBLE
120 binding.statusBarShade.visibility = View.INVISIBLE 121 binding.statusBarShade.visibility = View.INVISIBLE
121 } 122 }
122 123
123 homeViewModel.navigationVisible.observe(this) { 124 lifecycleScope.apply {
124 showNavigation(it.first, it.second) 125 launch {
125 } 126 repeatOnLifecycle(Lifecycle.State.CREATED) {
126 homeViewModel.statusBarShadeVisible.observe(this) { visible -> 127 homeViewModel.navigationVisible.collect { showNavigation(it.first, it.second) }
127 showStatusBarShade(visible) 128 }
129 }
130 launch {
131 repeatOnLifecycle(Lifecycle.State.CREATED) {
132 homeViewModel.statusBarShadeVisible.collect { showStatusBarShade(it) }
133 }
134 }
128 } 135 }
129 136
130 // Dismiss previous notifications (should not happen unless a crash occurred) 137 // Dismiss previous notifications (should not happen unless a crash occurred)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconUtils.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconUtils.kt
index c0fe596d7..9fe99fab1 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconUtils.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconUtils.kt
@@ -6,9 +6,11 @@ package org.yuzu.yuzu_emu.utils
6import android.graphics.Bitmap 6import android.graphics.Bitmap
7import android.graphics.BitmapFactory 7import android.graphics.BitmapFactory
8import android.widget.ImageView 8import android.widget.ImageView
9import androidx.core.graphics.drawable.toBitmap
9import androidx.core.graphics.drawable.toDrawable 10import androidx.core.graphics.drawable.toDrawable
10import coil.ImageLoader 11import coil.ImageLoader
11import coil.decode.DataSource 12import coil.decode.DataSource
13import coil.executeBlocking
12import coil.fetch.DrawableResult 14import coil.fetch.DrawableResult
13import coil.fetch.FetchResult 15import coil.fetch.FetchResult
14import coil.fetch.Fetcher 16import coil.fetch.Fetcher
@@ -74,4 +76,13 @@ object GameIconUtils {
74 .build() 76 .build()
75 imageLoader.enqueue(request) 77 imageLoader.enqueue(request)
76 } 78 }
79
80 fun getGameIcon(game: Game): Bitmap {
81 val request = ImageRequest.Builder(YuzuApplication.appContext)
82 .data(game)
83 .error(R.drawable.default_icon)
84 .build()
85 return imageLoader.executeBlocking(request)
86 .drawable!!.toBitmap(config = Bitmap.Config.ARGB_8888)
87 }
77} 88}
diff --git a/src/android/app/src/main/jni/config.cpp b/src/android/app/src/main/jni/config.cpp
index 34b425cb4..81120ab0f 100644
--- a/src/android/app/src/main/jni/config.cpp
+++ b/src/android/app/src/main/jni/config.cpp
@@ -282,7 +282,7 @@ void Config::ReadValues() {
282 std::stringstream ss(title_list); 282 std::stringstream ss(title_list);
283 std::string line; 283 std::string line;
284 while (std::getline(ss, line, '|')) { 284 while (std::getline(ss, line, '|')) {
285 const auto title_id = std::stoul(line, nullptr, 16); 285 const auto title_id = std::strtoul(line.c_str(), nullptr, 16);
286 const auto disabled_list = config->Get("AddOns", "disabled_" + line, ""); 286 const auto disabled_list = config->Get("AddOns", "disabled_" + line, "");
287 287
288 std::stringstream inner_ss(disabled_list); 288 std::stringstream inner_ss(disabled_list);
diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp
index b9ecefa74..f31fe054b 100644
--- a/src/android/app/src/main/jni/native.cpp
+++ b/src/android/app/src/main/jni/native.cpp
@@ -262,9 +262,6 @@ public:
262 Core::SystemResultStatus InitializeEmulation(const std::string& filepath) { 262 Core::SystemResultStatus InitializeEmulation(const std::string& filepath) {
263 std::scoped_lock lock(m_mutex); 263 std::scoped_lock lock(m_mutex);
264 264
265 // Loads the configuration.
266 Config{};
267
268 // Create the render window. 265 // Create the render window.
269 m_window = std::make_unique<EmuWindow_Android>(&m_input_subsystem, m_native_window, 266 m_window = std::make_unique<EmuWindow_Android>(&m_input_subsystem, m_native_window,
270 m_vulkan_library); 267 m_vulkan_library);
@@ -330,12 +327,13 @@ public:
330 m_system.ShutdownMainProcess(); 327 m_system.ShutdownMainProcess();
331 m_detached_tasks.WaitForAllTasks(); 328 m_detached_tasks.WaitForAllTasks();
332 m_load_result = Core::SystemResultStatus::ErrorNotInitialized; 329 m_load_result = Core::SystemResultStatus::ErrorNotInitialized;
330 m_window.reset();
331 OnEmulationStopped(Core::SystemResultStatus::Success);
332 return;
333 } 333 }
334 334
335 // Tear down the render window. 335 // Tear down the render window.
336 m_window.reset(); 336 m_window.reset();
337
338 OnEmulationStopped(m_load_result);
339 } 337 }
340 338
341 void PauseEmulation() { 339 void PauseEmulation() {
@@ -672,18 +670,6 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isPaused(JNIEnv* env, jclass claz
672 return static_cast<jboolean>(EmulationSession::GetInstance().IsPaused()); 670 return static_cast<jboolean>(EmulationSession::GetInstance().IsPaused());
673} 671}
674 672
675void Java_org_yuzu_yuzu_1emu_NativeLibrary_muteAduio(JNIEnv* env, jclass clazz) {
676 Settings::values.audio_muted = true;
677}
678
679void Java_org_yuzu_yuzu_1emu_NativeLibrary_unmuteAudio(JNIEnv* env, jclass clazz) {
680 Settings::values.audio_muted = false;
681}
682
683jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isMuted(JNIEnv* env, jclass clazz) {
684 return static_cast<jboolean>(Settings::values.audio_muted.GetValue());
685}
686
687jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHandheldOnly(JNIEnv* env, jclass clazz) { 673jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHandheldOnly(JNIEnv* env, jclass clazz) {
688 return EmulationSession::GetInstance().IsHandheldOnly(); 674 return EmulationSession::GetInstance().IsHandheldOnly();
689} 675}
diff --git a/src/android/app/src/main/res/drawable/shortcut.xml b/src/android/app/src/main/res/drawable/shortcut.xml
new file mode 100644
index 000000000..c749e5d72
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/shortcut.xml
@@ -0,0 +1,11 @@
1<?xml version="1.0" encoding="utf-8"?>
2<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
3
4 <item>
5 <color android:color="@android:color/white" />
6 </item>
7 <item android:id="@+id/shortcut_foreground">
8 <bitmap android:src="@drawable/default_icon" />
9 </item>
10
11</layer-list>
diff --git a/src/android/app/src/main/res/navigation/emulation_navigation.xml b/src/android/app/src/main/res/navigation/emulation_navigation.xml
index c7be37f9b..cfc494b3f 100644
--- a/src/android/app/src/main/res/navigation/emulation_navigation.xml
+++ b/src/android/app/src/main/res/navigation/emulation_navigation.xml
@@ -27,7 +27,7 @@
27 app:nullable="true" /> 27 app:nullable="true" />
28 <argument 28 <argument
29 android:name="menuTag" 29 android:name="menuTag"
30 app:argType="string" /> 30 app:argType="org.yuzu.yuzu_emu.features.settings.model.Settings$MenuTag" />
31 </activity> 31 </activity>
32 32
33 <action 33 <action
diff --git a/src/android/app/src/main/res/navigation/home_navigation.xml b/src/android/app/src/main/res/navigation/home_navigation.xml
index 2085430bf..2e0ce7a3d 100644
--- a/src/android/app/src/main/res/navigation/home_navigation.xml
+++ b/src/android/app/src/main/res/navigation/home_navigation.xml
@@ -82,7 +82,7 @@
82 app:nullable="true" /> 82 app:nullable="true" />
83 <argument 83 <argument
84 android:name="menuTag" 84 android:name="menuTag"
85 app:argType="string" /> 85 app:argType="org.yuzu.yuzu_emu.features.settings.model.Settings$MenuTag" />
86 </activity> 86 </activity>
87 87
88 <action 88 <action
diff --git a/src/android/app/src/main/res/navigation/settings_navigation.xml b/src/android/app/src/main/res/navigation/settings_navigation.xml
index 88e1b4587..1d87d36b3 100644
--- a/src/android/app/src/main/res/navigation/settings_navigation.xml
+++ b/src/android/app/src/main/res/navigation/settings_navigation.xml
@@ -10,7 +10,7 @@
10 android:label="SettingsFragment"> 10 android:label="SettingsFragment">
11 <argument 11 <argument
12 android:name="menuTag" 12 android:name="menuTag"
13 app:argType="string" /> 13 app:argType="org.yuzu.yuzu_emu.features.settings.model.Settings$MenuTag" />
14 <argument 14 <argument
15 android:name="game" 15 android:name="game"
16 app:argType="org.yuzu.yuzu_emu.model.Game" 16 app:argType="org.yuzu.yuzu_emu.model.Game"
diff --git a/src/android/app/src/main/res/values/dimens.xml b/src/android/app/src/main/res/values/dimens.xml
index 00757e5e8..7b2296d95 100644
--- a/src/android/app/src/main/res/values/dimens.xml
+++ b/src/android/app/src/main/res/values/dimens.xml
@@ -12,6 +12,7 @@
12 <dimen name="spacing_refresh_end">72dp</dimen> 12 <dimen name="spacing_refresh_end">72dp</dimen>
13 <dimen name="menu_width">256dp</dimen> 13 <dimen name="menu_width">256dp</dimen>
14 <dimen name="card_width">165dp</dimen> 14 <dimen name="card_width">165dp</dimen>
15 <dimen name="icon_inset">24dp</dimen>
15 16
16 <dimen name="dialog_margin">20dp</dimen> 17 <dimen name="dialog_margin">20dp</dimen>
17 <dimen name="elevated_app_bar">3dp</dimen> 18 <dimen name="elevated_app_bar">3dp</dimen>
diff --git a/src/common/fs/fs.cpp b/src/common/fs/fs.cpp
index 36e67c145..174aed49b 100644
--- a/src/common/fs/fs.cpp
+++ b/src/common/fs/fs.cpp
@@ -528,38 +528,41 @@ void IterateDirEntriesRecursively(const std::filesystem::path& path,
528// Generic Filesystem Operations 528// Generic Filesystem Operations
529 529
530bool Exists(const fs::path& path) { 530bool Exists(const fs::path& path) {
531 std::error_code ec;
531#ifdef ANDROID 532#ifdef ANDROID
532 if (Android::IsContentUri(path)) { 533 if (Android::IsContentUri(path)) {
533 return Android::Exists(path); 534 return Android::Exists(path);
534 } else { 535 } else {
535 return fs::exists(path); 536 return fs::exists(path, ec);
536 } 537 }
537#else 538#else
538 return fs::exists(path); 539 return fs::exists(path, ec);
539#endif 540#endif
540} 541}
541 542
542bool IsFile(const fs::path& path) { 543bool IsFile(const fs::path& path) {
544 std::error_code ec;
543#ifdef ANDROID 545#ifdef ANDROID
544 if (Android::IsContentUri(path)) { 546 if (Android::IsContentUri(path)) {
545 return !Android::IsDirectory(path); 547 return !Android::IsDirectory(path);
546 } else { 548 } else {
547 return fs::is_regular_file(path); 549 return fs::is_regular_file(path, ec);
548 } 550 }
549#else 551#else
550 return fs::is_regular_file(path); 552 return fs::is_regular_file(path, ec);
551#endif 553#endif
552} 554}
553 555
554bool IsDir(const fs::path& path) { 556bool IsDir(const fs::path& path) {
557 std::error_code ec;
555#ifdef ANDROID 558#ifdef ANDROID
556 if (Android::IsContentUri(path)) { 559 if (Android::IsContentUri(path)) {
557 return Android::IsDirectory(path); 560 return Android::IsDirectory(path);
558 } else { 561 } else {
559 return fs::is_directory(path); 562 return fs::is_directory(path, ec);
560 } 563 }
561#else 564#else
562 return fs::is_directory(path); 565 return fs::is_directory(path, ec);
563#endif 566#endif
564} 567}
565 568
diff --git a/src/common/logging/filter.cpp b/src/common/logging/filter.cpp
index c95909561..4e3a614a4 100644
--- a/src/common/logging/filter.cpp
+++ b/src/common/logging/filter.cpp
@@ -112,7 +112,7 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) {
112 SUB(Service, NCM) \ 112 SUB(Service, NCM) \
113 SUB(Service, NFC) \ 113 SUB(Service, NFC) \
114 SUB(Service, NFP) \ 114 SUB(Service, NFP) \
115 SUB(Service, NGCT) \ 115 SUB(Service, NGC) \
116 SUB(Service, NIFM) \ 116 SUB(Service, NIFM) \
117 SUB(Service, NIM) \ 117 SUB(Service, NIM) \
118 SUB(Service, NOTIF) \ 118 SUB(Service, NOTIF) \
diff --git a/src/common/logging/types.h b/src/common/logging/types.h
index 8356e3183..08af50ee0 100644
--- a/src/common/logging/types.h
+++ b/src/common/logging/types.h
@@ -80,7 +80,7 @@ enum class Class : u8 {
80 Service_NCM, ///< The NCM service 80 Service_NCM, ///< The NCM service
81 Service_NFC, ///< The NFC (Near-field communication) service 81 Service_NFC, ///< The NFC (Near-field communication) service
82 Service_NFP, ///< The NFP service 82 Service_NFP, ///< The NFP service
83 Service_NGCT, ///< The NGCT (No Good Content for Terra) service 83 Service_NGC, ///< The NGC (No Good Content) service
84 Service_NIFM, ///< The NIFM (Network interface) service 84 Service_NIFM, ///< The NIFM (Network interface) service
85 Service_NIM, ///< The NIM service 85 Service_NIM, ///< The NIM service
86 Service_NOTIF, ///< The NOTIF (Notification) service 86 Service_NOTIF, ///< The NOTIF (Notification) service
diff --git a/src/common/polyfill_thread.h b/src/common/polyfill_thread.h
index b5ef055db..41cbb9ed5 100644
--- a/src/common/polyfill_thread.h
+++ b/src/common/polyfill_thread.h
@@ -19,8 +19,8 @@
19namespace Common { 19namespace Common {
20 20
21template <typename Condvar, typename Lock, typename Pred> 21template <typename Condvar, typename Lock, typename Pred>
22void CondvarWait(Condvar& cv, Lock& lock, std::stop_token token, Pred&& pred) { 22void CondvarWait(Condvar& cv, std::unique_lock<Lock>& lk, std::stop_token token, Pred&& pred) {
23 cv.wait(lock, token, std::move(pred)); 23 cv.wait(lk, token, std::move(pred));
24} 24}
25 25
26template <typename Rep, typename Period> 26template <typename Rep, typename Period>
@@ -332,13 +332,17 @@ private:
332namespace Common { 332namespace Common {
333 333
334template <typename Condvar, typename Lock, typename Pred> 334template <typename Condvar, typename Lock, typename Pred>
335void CondvarWait(Condvar& cv, Lock& lock, std::stop_token token, Pred pred) { 335void CondvarWait(Condvar& cv, std::unique_lock<Lock>& lk, std::stop_token token, Pred pred) {
336 if (token.stop_requested()) { 336 if (token.stop_requested()) {
337 return; 337 return;
338 } 338 }
339 339
340 std::stop_callback callback(token, [&] { cv.notify_all(); }); 340 std::stop_callback callback(token, [&] {
341 cv.wait(lock, [&] { return pred() || token.stop_requested(); }); 341 { std::scoped_lock lk2{*lk.mutex()}; }
342 cv.notify_all();
343 });
344
345 cv.wait(lk, [&] { return pred() || token.stop_requested(); });
342} 346}
343 347
344template <typename Rep, typename Period> 348template <typename Rep, typename Period>
@@ -353,8 +357,10 @@ bool StoppableTimedWait(std::stop_token token, const std::chrono::duration<Rep,
353 357
354 std::stop_callback cb(token, [&] { 358 std::stop_callback cb(token, [&] {
355 // Wake up the waiting thread. 359 // Wake up the waiting thread.
356 std::unique_lock lk{m}; 360 {
357 stop_requested = true; 361 std::scoped_lock lk{m};
362 stop_requested = true;
363 }
358 cv.notify_one(); 364 cv.notify_one();
359 }); 365 });
360 366
diff --git a/src/common/settings.h b/src/common/settings.h
index b15213bd7..82ec9077e 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -348,6 +348,8 @@ struct Values {
348 Category::RendererDebug}; 348 Category::RendererDebug};
349 Setting<bool> disable_shader_loop_safety_checks{ 349 Setting<bool> disable_shader_loop_safety_checks{
350 linkage, false, "disable_shader_loop_safety_checks", Category::RendererDebug}; 350 linkage, false, "disable_shader_loop_safety_checks", Category::RendererDebug};
351 Setting<bool> enable_renderdoc_hotkey{linkage, false, "renderdoc_hotkey",
352 Category::RendererDebug};
351 353
352 // System 354 // System
353 SwitchableSetting<Language, true> language_index{linkage, 355 SwitchableSetting<Language, true> language_index{linkage,
diff --git a/src/common/settings_common.h b/src/common/settings_common.h
index 5b170dfd5..1800ab10d 100644
--- a/src/common/settings_common.h
+++ b/src/common/settings_common.h
@@ -225,6 +225,16 @@ public:
225 */ 225 */
226 [[nodiscard]] virtual constexpr u32 EnumIndex() const = 0; 226 [[nodiscard]] virtual constexpr u32 EnumIndex() const = 0;
227 227
228 /**
229 * @returns True if the underlying type is a floating point storage
230 */
231 [[nodiscard]] virtual constexpr bool IsFloatingPoint() const = 0;
232
233 /**
234 * @returns True if the underlying type is an integer storage
235 */
236 [[nodiscard]] virtual constexpr bool IsIntegral() const = 0;
237
228 /* 238 /*
229 * Switchable settings 239 * Switchable settings
230 */ 240 */
diff --git a/src/common/settings_setting.h b/src/common/settings_setting.h
index e10843c73..7be6f26f7 100644
--- a/src/common/settings_setting.h
+++ b/src/common/settings_setting.h
@@ -10,6 +10,7 @@
10#include <string> 10#include <string>
11#include <typeindex> 11#include <typeindex>
12#include <typeinfo> 12#include <typeinfo>
13#include <fmt/core.h>
13#include "common/common_types.h" 14#include "common/common_types.h"
14#include "common/settings_common.h" 15#include "common/settings_common.h"
15#include "common/settings_enums.h" 16#include "common/settings_enums.h"
@@ -115,8 +116,12 @@ protected:
115 } else if constexpr (std::is_same_v<Type, AudioEngine>) { 116 } else if constexpr (std::is_same_v<Type, AudioEngine>) {
116 // Compatibility with old AudioEngine setting being a string 117 // Compatibility with old AudioEngine setting being a string
117 return CanonicalizeEnum(value_); 118 return CanonicalizeEnum(value_);
119 } else if constexpr (std::is_floating_point_v<Type>) {
120 return fmt::format("{:f}", value_);
121 } else if constexpr (std::is_enum_v<Type>) {
122 return std::to_string(static_cast<u32>(value_));
118 } else { 123 } else {
119 return std::to_string(static_cast<u64>(value_)); 124 return std::to_string(value_);
120 } 125 }
121 } 126 }
122 127
@@ -180,13 +185,15 @@ public:
180 this->SetValue(static_cast<u32>(std::stoul(input))); 185 this->SetValue(static_cast<u32>(std::stoul(input)));
181 } else if constexpr (std::is_same_v<Type, bool>) { 186 } else if constexpr (std::is_same_v<Type, bool>) {
182 this->SetValue(input == "true"); 187 this->SetValue(input == "true");
183 } else if constexpr (std::is_same_v<Type, AudioEngine>) { 188 } else if constexpr (std::is_same_v<Type, float>) {
184 this->SetValue(ToEnum<Type>(input)); 189 this->SetValue(std::stof(input));
185 } else { 190 } else {
186 this->SetValue(static_cast<Type>(std::stoll(input))); 191 this->SetValue(static_cast<Type>(std::stoll(input)));
187 } 192 }
188 } catch (std::invalid_argument&) { 193 } catch (std::invalid_argument&) {
189 this->SetValue(this->GetDefault()); 194 this->SetValue(this->GetDefault());
195 } catch (std::out_of_range&) {
196 this->SetValue(this->GetDefault());
190 } 197 }
191 } 198 }
192 199
@@ -215,11 +222,27 @@ public:
215 } 222 }
216 } 223 }
217 224
225 [[nodiscard]] constexpr bool IsFloatingPoint() const final {
226 return std::is_floating_point_v<Type>;
227 }
228
229 [[nodiscard]] constexpr bool IsIntegral() const final {
230 return std::is_integral_v<Type>;
231 }
232
218 [[nodiscard]] std::string MinVal() const override final { 233 [[nodiscard]] std::string MinVal() const override final {
219 return this->ToString(minimum); 234 if constexpr (std::is_arithmetic_v<Type> && !ranged) {
235 return this->ToString(std::numeric_limits<Type>::min());
236 } else {
237 return this->ToString(minimum);
238 }
220 } 239 }
221 [[nodiscard]] std::string MaxVal() const override final { 240 [[nodiscard]] std::string MaxVal() const override final {
222 return this->ToString(maximum); 241 if constexpr (std::is_arithmetic_v<Type> && !ranged) {
242 return this->ToString(std::numeric_limits<Type>::max());
243 } else {
244 return this->ToString(maximum);
245 }
223 } 246 }
224 247
225 [[nodiscard]] constexpr bool Ranged() const override { 248 [[nodiscard]] constexpr bool Ranged() const override {
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index c33910ade..30d2f7df6 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -627,8 +627,8 @@ add_library(core STATIC
627 hle/service/nfp/nfp_interface.h 627 hle/service/nfp/nfp_interface.h
628 hle/service/nfp/nfp_result.h 628 hle/service/nfp/nfp_result.h
629 hle/service/nfp/nfp_types.h 629 hle/service/nfp/nfp_types.h
630 hle/service/ngct/ngct.cpp 630 hle/service/ngc/ngc.cpp
631 hle/service/ngct/ngct.h 631 hle/service/ngc/ngc.h
632 hle/service/nifm/nifm.cpp 632 hle/service/nifm/nifm.cpp
633 hle/service/nifm/nifm.h 633 hle/service/nifm/nifm.h
634 hle/service/nim/nim.cpp 634 hle/service/nim/nim.cpp
@@ -864,6 +864,8 @@ add_library(core STATIC
864 telemetry_session.h 864 telemetry_session.h
865 tools/freezer.cpp 865 tools/freezer.cpp
866 tools/freezer.h 866 tools/freezer.h
867 tools/renderdoc.cpp
868 tools/renderdoc.h
867) 869)
868 870
869if (MSVC) 871if (MSVC)
@@ -879,6 +881,7 @@ else()
879 -Werror=conversion 881 -Werror=conversion
880 882
881 -Wno-sign-conversion 883 -Wno-sign-conversion
884 -Wno-cast-function-type
882 885
883 $<$<CXX_COMPILER_ID:Clang>:-fsized-deallocation> 886 $<$<CXX_COMPILER_ID:Clang>:-fsized-deallocation>
884 ) 887 )
@@ -887,7 +890,7 @@ endif()
887create_target_directory_groups(core) 890create_target_directory_groups(core)
888 891
889target_link_libraries(core PUBLIC common PRIVATE audio_core network video_core nx_tzdb) 892target_link_libraries(core PUBLIC common PRIVATE audio_core network video_core nx_tzdb)
890target_link_libraries(core PUBLIC Boost::headers PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls Opus::opus) 893target_link_libraries(core PUBLIC Boost::headers PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls Opus::opus renderdoc)
891if (MINGW) 894if (MINGW)
892 target_link_libraries(core PRIVATE ${MSWSOCK_LIBRARY}) 895 target_link_libraries(core PRIVATE ${MSWSOCK_LIBRARY})
893endif() 896endif()
diff --git a/src/core/core.cpp b/src/core/core.cpp
index 2d6e61398..e8300cd05 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -51,6 +51,7 @@
51#include "core/reporter.h" 51#include "core/reporter.h"
52#include "core/telemetry_session.h" 52#include "core/telemetry_session.h"
53#include "core/tools/freezer.h" 53#include "core/tools/freezer.h"
54#include "core/tools/renderdoc.h"
54#include "network/network.h" 55#include "network/network.h"
55#include "video_core/host1x/host1x.h" 56#include "video_core/host1x/host1x.h"
56#include "video_core/renderer_base.h" 57#include "video_core/renderer_base.h"
@@ -281,6 +282,10 @@ struct System::Impl {
281 microprofile_cpu[2] = MICROPROFILE_TOKEN(ARM_CPU2); 282 microprofile_cpu[2] = MICROPROFILE_TOKEN(ARM_CPU2);
282 microprofile_cpu[3] = MICROPROFILE_TOKEN(ARM_CPU3); 283 microprofile_cpu[3] = MICROPROFILE_TOKEN(ARM_CPU3);
283 284
285 if (Settings::values.enable_renderdoc_hotkey) {
286 renderdoc_api = std::make_unique<Tools::RenderdocAPI>();
287 }
288
284 LOG_DEBUG(Core, "Initialized OK"); 289 LOG_DEBUG(Core, "Initialized OK");
285 290
286 return SystemResultStatus::Success; 291 return SystemResultStatus::Success;
@@ -521,6 +526,8 @@ struct System::Impl {
521 std::unique_ptr<Tools::Freezer> memory_freezer; 526 std::unique_ptr<Tools::Freezer> memory_freezer;
522 std::array<u8, 0x20> build_id{}; 527 std::array<u8, 0x20> build_id{};
523 528
529 std::unique_ptr<Tools::RenderdocAPI> renderdoc_api;
530
524 /// Frontend applets 531 /// Frontend applets
525 Service::AM::Applets::AppletManager applet_manager; 532 Service::AM::Applets::AppletManager applet_manager;
526 533
@@ -1024,6 +1031,10 @@ const Network::RoomNetwork& System::GetRoomNetwork() const {
1024 return impl->room_network; 1031 return impl->room_network;
1025} 1032}
1026 1033
1034Tools::RenderdocAPI& System::GetRenderdocAPI() {
1035 return *impl->renderdoc_api;
1036}
1037
1027void System::RunServer(std::unique_ptr<Service::ServerManager>&& server_manager) { 1038void System::RunServer(std::unique_ptr<Service::ServerManager>&& server_manager) {
1028 return impl->kernel.RunServer(std::move(server_manager)); 1039 return impl->kernel.RunServer(std::move(server_manager));
1029} 1040}
diff --git a/src/core/core.h b/src/core/core.h
index fba312125..df20f26f3 100644
--- a/src/core/core.h
+++ b/src/core/core.h
@@ -102,6 +102,10 @@ namespace Network {
102class RoomNetwork; 102class RoomNetwork;
103} 103}
104 104
105namespace Tools {
106class RenderdocAPI;
107}
108
105namespace Core { 109namespace Core {
106 110
107class ARM_Interface; 111class ARM_Interface;
@@ -413,6 +417,8 @@ public:
413 /// Gets an immutable reference to the Room Network. 417 /// Gets an immutable reference to the Room Network.
414 [[nodiscard]] const Network::RoomNetwork& GetRoomNetwork() const; 418 [[nodiscard]] const Network::RoomNetwork& GetRoomNetwork() const;
415 419
420 [[nodiscard]] Tools::RenderdocAPI& GetRenderdocAPI();
421
416 void SetExitLocked(bool locked); 422 void SetExitLocked(bool locked);
417 bool GetExitLocked() const; 423 bool GetExitLocked() const;
418 424
diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp
index e13c5cdc7..43a3c5ffd 100644
--- a/src/core/crypto/key_manager.cpp
+++ b/src/core/crypto/key_manager.cpp
@@ -724,14 +724,14 @@ void KeyManager::LoadFromFile(const std::filesystem::path& file_path, bool is_ti
724 continue; 724 continue;
725 } 725 }
726 726
727 const auto index = std::stoul(out[0].substr(8, 2), nullptr, 16); 727 const auto index = std::strtoul(out[0].substr(8, 2).c_str(), nullptr, 16);
728 keyblobs[index] = Common::HexStringToArray<0x90>(out[1]); 728 keyblobs[index] = Common::HexStringToArray<0x90>(out[1]);
729 } else if (out[0].compare(0, 18, "encrypted_keyblob_") == 0) { 729 } else if (out[0].compare(0, 18, "encrypted_keyblob_") == 0) {
730 if (!ValidCryptoRevisionString(out[0], 18, 2)) { 730 if (!ValidCryptoRevisionString(out[0], 18, 2)) {
731 continue; 731 continue;
732 } 732 }
733 733
734 const auto index = std::stoul(out[0].substr(18, 2), nullptr, 16); 734 const auto index = std::strtoul(out[0].substr(18, 2).c_str(), nullptr, 16);
735 encrypted_keyblobs[index] = Common::HexStringToArray<0xB0>(out[1]); 735 encrypted_keyblobs[index] = Common::HexStringToArray<0xB0>(out[1]);
736 } else if (out[0].compare(0, 20, "eticket_extended_kek") == 0) { 736 } else if (out[0].compare(0, 20, "eticket_extended_kek") == 0) {
737 eticket_extended_kek = Common::HexStringToArray<576>(out[1]); 737 eticket_extended_kek = Common::HexStringToArray<576>(out[1]);
@@ -750,7 +750,7 @@ void KeyManager::LoadFromFile(const std::filesystem::path& file_path, bool is_ti
750 } 750 }
751 if (out[0].compare(0, kv.second.size(), kv.second) == 0) { 751 if (out[0].compare(0, kv.second.size(), kv.second) == 0) {
752 const auto index = 752 const auto index =
753 std::stoul(out[0].substr(kv.second.size(), 2), nullptr, 16); 753 std::strtoul(out[0].substr(kv.second.size(), 2).c_str(), nullptr, 16);
754 const auto sub = kv.first.second; 754 const auto sub = kv.first.second;
755 if (sub == 0) { 755 if (sub == 0) {
756 s128_keys[{kv.first.first, index, 0}] = 756 s128_keys[{kv.first.first, index, 0}] =
@@ -770,7 +770,7 @@ void KeyManager::LoadFromFile(const std::filesystem::path& file_path, bool is_ti
770 const auto& match = kak_names[j]; 770 const auto& match = kak_names[j];
771 if (out[0].compare(0, std::strlen(match), match) == 0) { 771 if (out[0].compare(0, std::strlen(match), match) == 0) {
772 const auto index = 772 const auto index =
773 std::stoul(out[0].substr(std::strlen(match), 2), nullptr, 16); 773 std::strtoul(out[0].substr(std::strlen(match), 2).c_str(), nullptr, 16);
774 s128_keys[{S128KeyType::KeyArea, index, j}] = 774 s128_keys[{S128KeyType::KeyArea, index, j}] =
775 Common::HexStringToArray<16>(out[1]); 775 Common::HexStringToArray<16>(out[1]);
776 } 776 }
diff --git a/src/core/file_sys/ips_layer.cpp b/src/core/file_sys/ips_layer.cpp
index efdf18cee..7be1322cc 100644
--- a/src/core/file_sys/ips_layer.cpp
+++ b/src/core/file_sys/ips_layer.cpp
@@ -165,7 +165,7 @@ static std::string EscapeStringSequences(std::string in) {
165void IPSwitchCompiler::ParseFlag(const std::string& line) { 165void IPSwitchCompiler::ParseFlag(const std::string& line) {
166 if (StartsWith(line, "@flag offset_shift ")) { 166 if (StartsWith(line, "@flag offset_shift ")) {
167 // Offset Shift Flag 167 // Offset Shift Flag
168 offset_shift = std::stoll(line.substr(19), nullptr, 0); 168 offset_shift = std::strtoll(line.substr(19).c_str(), nullptr, 0);
169 } else if (StartsWith(line, "@little-endian")) { 169 } else if (StartsWith(line, "@little-endian")) {
170 // Set values to read as little endian 170 // Set values to read as little endian
171 is_little_endian = true; 171 is_little_endian = true;
@@ -263,7 +263,7 @@ void IPSwitchCompiler::Parse() {
263 // 11 - 8 hex digit offset + space + minimum two digit overwrite val 263 // 11 - 8 hex digit offset + space + minimum two digit overwrite val
264 if (patch_line.length() < 11) 264 if (patch_line.length() < 11)
265 break; 265 break;
266 auto offset = std::stoul(patch_line.substr(0, 8), nullptr, 16); 266 auto offset = std::strtoul(patch_line.substr(0, 8).c_str(), nullptr, 16);
267 offset += static_cast<unsigned long>(offset_shift); 267 offset += static_cast<unsigned long>(offset_shift);
268 268
269 std::vector<u8> replace; 269 std::vector<u8> replace;
diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp
index a4baddb15..8e475f25a 100644
--- a/src/core/file_sys/patch_manager.cpp
+++ b/src/core/file_sys/patch_manager.cpp
@@ -294,11 +294,11 @@ std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso, const std::st
294 return out; 294 return out;
295} 295}
296 296
297bool PatchManager::HasNSOPatch(const BuildID& build_id_) const { 297bool PatchManager::HasNSOPatch(const BuildID& build_id_, std::string_view name) const {
298 const auto build_id_raw = Common::HexToString(build_id_); 298 const auto build_id_raw = Common::HexToString(build_id_);
299 const auto build_id = build_id_raw.substr(0, build_id_raw.find_last_not_of('0') + 1); 299 const auto build_id = build_id_raw.substr(0, build_id_raw.find_last_not_of('0') + 1);
300 300
301 LOG_INFO(Loader, "Querying NSO patch existence for build_id={}", build_id); 301 LOG_INFO(Loader, "Querying NSO patch existence for build_id={}, name={}", build_id, name);
302 302
303 const auto load_dir = fs_controller.GetModificationLoadRoot(title_id); 303 const auto load_dir = fs_controller.GetModificationLoadRoot(title_id);
304 if (load_dir == nullptr) { 304 if (load_dir == nullptr) {
diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h
index adcde7b7d..03e9c7301 100644
--- a/src/core/file_sys/patch_manager.h
+++ b/src/core/file_sys/patch_manager.h
@@ -52,7 +52,7 @@ public:
52 52
53 // Checks to see if PatchNSO() will have any effect given the NSO's build ID. 53 // Checks to see if PatchNSO() will have any effect given the NSO's build ID.
54 // Used to prevent expensive copies in NSO loader. 54 // Used to prevent expensive copies in NSO loader.
55 [[nodiscard]] bool HasNSOPatch(const BuildID& build_id) const; 55 [[nodiscard]] bool HasNSOPatch(const BuildID& build_id, std::string_view name) const;
56 56
57 // Creates a CheatList object with all 57 // Creates a CheatList object with all
58 [[nodiscard]] std::vector<Core::Memory::CheatEntry> CreateCheatList( 58 [[nodiscard]] std::vector<Core::Memory::CheatEntry> CreateCheatList(
diff --git a/src/core/hid/hid_core.cpp b/src/core/hid/hid_core.cpp
index 7d6373414..cf53c04d9 100644
--- a/src/core/hid/hid_core.cpp
+++ b/src/core/hid/hid_core.cpp
@@ -154,6 +154,14 @@ NpadIdType HIDCore::GetFirstDisconnectedNpadId() const {
154 return NpadIdType::Player1; 154 return NpadIdType::Player1;
155} 155}
156 156
157void HIDCore::SetLastActiveController(NpadIdType npad_id) {
158 last_active_controller = npad_id;
159}
160
161NpadIdType HIDCore::GetLastActiveController() const {
162 return last_active_controller;
163}
164
157void HIDCore::EnableAllControllerConfiguration() { 165void HIDCore::EnableAllControllerConfiguration() {
158 player_1->EnableConfiguration(); 166 player_1->EnableConfiguration();
159 player_2->EnableConfiguration(); 167 player_2->EnableConfiguration();
diff --git a/src/core/hid/hid_core.h b/src/core/hid/hid_core.h
index 5fe36551e..80abab18b 100644
--- a/src/core/hid/hid_core.h
+++ b/src/core/hid/hid_core.h
@@ -48,6 +48,12 @@ public:
48 /// Returns the first disconnected npad id 48 /// Returns the first disconnected npad id
49 NpadIdType GetFirstDisconnectedNpadId() const; 49 NpadIdType GetFirstDisconnectedNpadId() const;
50 50
51 /// Sets the npad id of the last active controller
52 void SetLastActiveController(NpadIdType npad_id);
53
54 /// Returns the npad id of the last controller that pushed a button
55 NpadIdType GetLastActiveController() const;
56
51 /// Sets all emulated controllers into configuring mode. 57 /// Sets all emulated controllers into configuring mode.
52 void EnableAllControllerConfiguration(); 58 void EnableAllControllerConfiguration();
53 59
@@ -77,6 +83,7 @@ private:
77 std::unique_ptr<EmulatedConsole> console; 83 std::unique_ptr<EmulatedConsole> console;
78 std::unique_ptr<EmulatedDevices> devices; 84 std::unique_ptr<EmulatedDevices> devices;
79 NpadStyleTag supported_style_tag{NpadStyleSet::All}; 85 NpadStyleTag supported_style_tag{NpadStyleSet::All};
86 NpadIdType last_active_controller{NpadIdType::Handheld};
80}; 87};
81 88
82} // namespace Core::HID 89} // namespace Core::HID
diff --git a/src/core/hle/kernel/k_process.cpp b/src/core/hle/kernel/k_process.cpp
index 703049ede..4a099286b 100644
--- a/src/core/hle/kernel/k_process.cpp
+++ b/src/core/hle/kernel/k_process.cpp
@@ -96,6 +96,7 @@ Result KProcess::Initialize(KProcess* process, Core::System& system, std::string
96 process->m_is_suspended = false; 96 process->m_is_suspended = false;
97 process->m_schedule_count = 0; 97 process->m_schedule_count = 0;
98 process->m_is_handle_table_initialized = false; 98 process->m_is_handle_table_initialized = false;
99 process->m_is_hbl = false;
99 100
100 // Open a reference to the resource limit. 101 // Open a reference to the resource limit.
101 process->m_resource_limit->Open(); 102 process->m_resource_limit->Open();
@@ -351,12 +352,14 @@ Result KProcess::SetActivity(ProcessActivity activity) {
351 R_SUCCEED(); 352 R_SUCCEED();
352} 353}
353 354
354Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size) { 355Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size,
356 bool is_hbl) {
355 m_program_id = metadata.GetTitleID(); 357 m_program_id = metadata.GetTitleID();
356 m_ideal_core = metadata.GetMainThreadCore(); 358 m_ideal_core = metadata.GetMainThreadCore();
357 m_is_64bit_process = metadata.Is64BitProgram(); 359 m_is_64bit_process = metadata.Is64BitProgram();
358 m_system_resource_size = metadata.GetSystemResourceSize(); 360 m_system_resource_size = metadata.GetSystemResourceSize();
359 m_image_size = code_size; 361 m_image_size = code_size;
362 m_is_hbl = is_hbl;
360 363
361 if (metadata.GetAddressSpaceType() == FileSys::ProgramAddressSpaceType::Is39Bit) { 364 if (metadata.GetAddressSpaceType() == FileSys::ProgramAddressSpaceType::Is39Bit) {
362 // For 39-bit processes, the ASLR region starts at 0x800'0000 and is ~512GiB large. 365 // For 39-bit processes, the ASLR region starts at 0x800'0000 and is ~512GiB large.
diff --git a/src/core/hle/kernel/k_process.h b/src/core/hle/kernel/k_process.h
index 4fdeaf11a..146e07a57 100644
--- a/src/core/hle/kernel/k_process.h
+++ b/src/core/hle/kernel/k_process.h
@@ -338,7 +338,8 @@ public:
338 * @returns ResultSuccess if all relevant metadata was able to be 338 * @returns ResultSuccess if all relevant metadata was able to be
339 * loaded and parsed. Otherwise, an error code is returned. 339 * loaded and parsed. Otherwise, an error code is returned.
340 */ 340 */
341 Result LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size); 341 Result LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size,
342 bool is_hbl);
342 343
343 /** 344 /**
344 * Starts the main application thread for this process. 345 * Starts the main application thread for this process.
@@ -368,6 +369,10 @@ public:
368 return GetProcessId(); 369 return GetProcessId();
369 } 370 }
370 371
372 bool IsHbl() const {
373 return m_is_hbl;
374 }
375
371 bool IsSignaled() const override; 376 bool IsSignaled() const override;
372 377
373 void DoWorkerTaskImpl(); 378 void DoWorkerTaskImpl();
@@ -525,6 +530,7 @@ private:
525 bool m_is_immortal{}; 530 bool m_is_immortal{};
526 bool m_is_handle_table_initialized{}; 531 bool m_is_handle_table_initialized{};
527 bool m_is_initialized{}; 532 bool m_is_initialized{};
533 bool m_is_hbl{};
528 534
529 std::atomic<u16> m_num_running_threads{}; 535 std::atomic<u16> m_num_running_threads{};
530 536
diff --git a/src/core/hle/kernel/svc/svc_debug_string.cpp b/src/core/hle/kernel/svc/svc_debug_string.cpp
index 4c14ce668..00b65429b 100644
--- a/src/core/hle/kernel/svc/svc_debug_string.cpp
+++ b/src/core/hle/kernel/svc/svc_debug_string.cpp
@@ -14,7 +14,7 @@ Result OutputDebugString(Core::System& system, u64 address, u64 len) {
14 14
15 std::string str(len, '\0'); 15 std::string str(len, '\0');
16 GetCurrentMemory(system.Kernel()).ReadBlock(address, str.data(), str.size()); 16 GetCurrentMemory(system.Kernel()).ReadBlock(address, str.data(), str.size());
17 LOG_DEBUG(Debug_Emulated, "{}", str); 17 LOG_INFO(Debug_Emulated, "{}", str);
18 18
19 R_SUCCEED(); 19 R_SUCCEED();
20} 20}
diff --git a/src/core/hle/kernel/svc/svc_exception.cpp b/src/core/hle/kernel/svc/svc_exception.cpp
index 580cf2f75..c581c086b 100644
--- a/src/core/hle/kernel/svc/svc_exception.cpp
+++ b/src/core/hle/kernel/svc/svc_exception.cpp
@@ -3,6 +3,7 @@
3 3
4#include "core/core.h" 4#include "core/core.h"
5#include "core/debugger/debugger.h" 5#include "core/debugger/debugger.h"
6#include "core/hle/kernel/k_process.h"
6#include "core/hle/kernel/k_thread.h" 7#include "core/hle/kernel/k_thread.h"
7#include "core/hle/kernel/svc.h" 8#include "core/hle/kernel/svc.h"
8#include "core/hle/kernel/svc_types.h" 9#include "core/hle/kernel/svc_types.h"
@@ -107,7 +108,10 @@ void Break(Core::System& system, BreakReason reason, u64 info1, u64 info2) {
107 system.ArmInterface(static_cast<std::size_t>(thread_processor_id)).LogBacktrace(); 108 system.ArmInterface(static_cast<std::size_t>(thread_processor_id)).LogBacktrace();
108 } 109 }
109 110
110 if (system.DebuggerEnabled()) { 111 const bool is_hbl = GetCurrentProcess(system.Kernel()).IsHbl();
112 const bool should_break = is_hbl || !notification_only;
113
114 if (system.DebuggerEnabled() && should_break) {
111 auto* thread = system.Kernel().GetCurrentEmuThread(); 115 auto* thread = system.Kernel().GetCurrentEmuThread();
112 system.GetDebugger().NotifyThreadStopped(thread); 116 system.GetDebugger().NotifyThreadStopped(thread);
113 thread->RequestSuspend(Kernel::SuspendType::Debug); 117 thread->RequestSuspend(Kernel::SuspendType::Debug);
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index f9c4f9678..8ffdd19e7 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -1386,7 +1386,7 @@ IApplicationFunctions::IApplicationFunctions(Core::System& system_)
1386 {25, &IApplicationFunctions::ExtendSaveData, "ExtendSaveData"}, 1386 {25, &IApplicationFunctions::ExtendSaveData, "ExtendSaveData"},
1387 {26, &IApplicationFunctions::GetSaveDataSize, "GetSaveDataSize"}, 1387 {26, &IApplicationFunctions::GetSaveDataSize, "GetSaveDataSize"},
1388 {27, &IApplicationFunctions::CreateCacheStorage, "CreateCacheStorage"}, 1388 {27, &IApplicationFunctions::CreateCacheStorage, "CreateCacheStorage"},
1389 {28, nullptr, "GetSaveDataSizeMax"}, 1389 {28, &IApplicationFunctions::GetSaveDataSizeMax, "GetSaveDataSizeMax"},
1390 {29, nullptr, "GetCacheStorageMax"}, 1390 {29, nullptr, "GetCacheStorageMax"},
1391 {30, &IApplicationFunctions::BeginBlockingHomeButtonShortAndLongPressed, "BeginBlockingHomeButtonShortAndLongPressed"}, 1391 {30, &IApplicationFunctions::BeginBlockingHomeButtonShortAndLongPressed, "BeginBlockingHomeButtonShortAndLongPressed"},
1392 {31, &IApplicationFunctions::EndBlockingHomeButtonShortAndLongPressed, "EndBlockingHomeButtonShortAndLongPressed"}, 1392 {31, &IApplicationFunctions::EndBlockingHomeButtonShortAndLongPressed, "EndBlockingHomeButtonShortAndLongPressed"},
@@ -1821,6 +1821,18 @@ void IApplicationFunctions::CreateCacheStorage(HLERequestContext& ctx) {
1821 rb.PushRaw(resp); 1821 rb.PushRaw(resp);
1822} 1822}
1823 1823
1824void IApplicationFunctions::GetSaveDataSizeMax(HLERequestContext& ctx) {
1825 LOG_WARNING(Service_AM, "(STUBBED) called");
1826
1827 constexpr u64 size_max_normal = 0xFFFFFFF;
1828 constexpr u64 size_max_journal = 0xFFFFFFF;
1829
1830 IPC::ResponseBuilder rb{ctx, 6};
1831 rb.Push(ResultSuccess);
1832 rb.Push(size_max_normal);
1833 rb.Push(size_max_journal);
1834}
1835
1824void IApplicationFunctions::QueryApplicationPlayStatistics(HLERequestContext& ctx) { 1836void IApplicationFunctions::QueryApplicationPlayStatistics(HLERequestContext& ctx) {
1825 LOG_WARNING(Service_AM, "(STUBBED) called"); 1837 LOG_WARNING(Service_AM, "(STUBBED) called");
1826 1838
diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h
index f75a665b2..f86841c60 100644
--- a/src/core/hle/service/am/am.h
+++ b/src/core/hle/service/am/am.h
@@ -316,6 +316,7 @@ private:
316 void ExtendSaveData(HLERequestContext& ctx); 316 void ExtendSaveData(HLERequestContext& ctx);
317 void GetSaveDataSize(HLERequestContext& ctx); 317 void GetSaveDataSize(HLERequestContext& ctx);
318 void CreateCacheStorage(HLERequestContext& ctx); 318 void CreateCacheStorage(HLERequestContext& ctx);
319 void GetSaveDataSizeMax(HLERequestContext& ctx);
319 void BeginBlockingHomeButtonShortAndLongPressed(HLERequestContext& ctx); 320 void BeginBlockingHomeButtonShortAndLongPressed(HLERequestContext& ctx);
320 void EndBlockingHomeButtonShortAndLongPressed(HLERequestContext& ctx); 321 void EndBlockingHomeButtonShortAndLongPressed(HLERequestContext& ctx);
321 void BeginBlockingHomeButton(HLERequestContext& ctx); 322 void BeginBlockingHomeButton(HLERequestContext& ctx);
diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp
index c744b0f87..146bb486d 100644
--- a/src/core/hle/service/hid/controllers/npad.cpp
+++ b/src/core/hle/service/hid/controllers/npad.cpp
@@ -193,7 +193,7 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
193 shared_memory->system_properties.use_minus.Assign(1); 193 shared_memory->system_properties.use_minus.Assign(1);
194 shared_memory->system_properties.is_charging_joy_dual.Assign( 194 shared_memory->system_properties.is_charging_joy_dual.Assign(
195 battery_level.dual.is_charging); 195 battery_level.dual.is_charging);
196 shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::SwitchProController; 196 shared_memory->applet_footer_type = AppletFooterUiType::SwitchProController;
197 shared_memory->sixaxis_fullkey_properties.is_newly_assigned.Assign(1); 197 shared_memory->sixaxis_fullkey_properties.is_newly_assigned.Assign(1);
198 break; 198 break;
199 case Core::HID::NpadStyleIndex::Handheld: 199 case Core::HID::NpadStyleIndex::Handheld:
@@ -216,8 +216,7 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
216 shared_memory->system_properties.is_charging_joy_right.Assign( 216 shared_memory->system_properties.is_charging_joy_right.Assign(
217 battery_level.right.is_charging); 217 battery_level.right.is_charging);
218 shared_memory->assignment_mode = NpadJoyAssignmentMode::Dual; 218 shared_memory->assignment_mode = NpadJoyAssignmentMode::Dual;
219 shared_memory->applet_nfc_xcd.applet_footer.type = 219 shared_memory->applet_footer_type = AppletFooterUiType::HandheldJoyConLeftJoyConRight;
220 AppletFooterUiType::HandheldJoyConLeftJoyConRight;
221 shared_memory->sixaxis_handheld_properties.is_newly_assigned.Assign(1); 220 shared_memory->sixaxis_handheld_properties.is_newly_assigned.Assign(1);
222 break; 221 break;
223 case Core::HID::NpadStyleIndex::JoyconDual: 222 case Core::HID::NpadStyleIndex::JoyconDual:
@@ -247,19 +246,19 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
247 shared_memory->assignment_mode = NpadJoyAssignmentMode::Dual; 246 shared_memory->assignment_mode = NpadJoyAssignmentMode::Dual;
248 247
249 if (controller.is_dual_left_connected && controller.is_dual_right_connected) { 248 if (controller.is_dual_left_connected && controller.is_dual_right_connected) {
250 shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyDual; 249 shared_memory->applet_footer_type = AppletFooterUiType::JoyDual;
251 shared_memory->fullkey_color.fullkey = body_colors.left; 250 shared_memory->fullkey_color.fullkey = body_colors.left;
252 shared_memory->battery_level_dual = battery_level.left.battery_level; 251 shared_memory->battery_level_dual = battery_level.left.battery_level;
253 shared_memory->system_properties.is_charging_joy_dual.Assign( 252 shared_memory->system_properties.is_charging_joy_dual.Assign(
254 battery_level.left.is_charging); 253 battery_level.left.is_charging);
255 } else if (controller.is_dual_left_connected) { 254 } else if (controller.is_dual_left_connected) {
256 shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyDualLeftOnly; 255 shared_memory->applet_footer_type = AppletFooterUiType::JoyDualLeftOnly;
257 shared_memory->fullkey_color.fullkey = body_colors.left; 256 shared_memory->fullkey_color.fullkey = body_colors.left;
258 shared_memory->battery_level_dual = battery_level.left.battery_level; 257 shared_memory->battery_level_dual = battery_level.left.battery_level;
259 shared_memory->system_properties.is_charging_joy_dual.Assign( 258 shared_memory->system_properties.is_charging_joy_dual.Assign(
260 battery_level.left.is_charging); 259 battery_level.left.is_charging);
261 } else { 260 } else {
262 shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyDualRightOnly; 261 shared_memory->applet_footer_type = AppletFooterUiType::JoyDualRightOnly;
263 shared_memory->fullkey_color.fullkey = body_colors.right; 262 shared_memory->fullkey_color.fullkey = body_colors.right;
264 shared_memory->battery_level_dual = battery_level.right.battery_level; 263 shared_memory->battery_level_dual = battery_level.right.battery_level;
265 shared_memory->system_properties.is_charging_joy_dual.Assign( 264 shared_memory->system_properties.is_charging_joy_dual.Assign(
@@ -278,7 +277,7 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
278 shared_memory->system_properties.use_minus.Assign(1); 277 shared_memory->system_properties.use_minus.Assign(1);
279 shared_memory->system_properties.is_charging_joy_left.Assign( 278 shared_memory->system_properties.is_charging_joy_left.Assign(
280 battery_level.left.is_charging); 279 battery_level.left.is_charging);
281 shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyLeftHorizontal; 280 shared_memory->applet_footer_type = AppletFooterUiType::JoyLeftHorizontal;
282 shared_memory->sixaxis_left_properties.is_newly_assigned.Assign(1); 281 shared_memory->sixaxis_left_properties.is_newly_assigned.Assign(1);
283 break; 282 break;
284 case Core::HID::NpadStyleIndex::JoyconRight: 283 case Core::HID::NpadStyleIndex::JoyconRight:
@@ -293,7 +292,7 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
293 shared_memory->system_properties.use_plus.Assign(1); 292 shared_memory->system_properties.use_plus.Assign(1);
294 shared_memory->system_properties.is_charging_joy_right.Assign( 293 shared_memory->system_properties.is_charging_joy_right.Assign(
295 battery_level.right.is_charging); 294 battery_level.right.is_charging);
296 shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyRightHorizontal; 295 shared_memory->applet_footer_type = AppletFooterUiType::JoyRightHorizontal;
297 shared_memory->sixaxis_right_properties.is_newly_assigned.Assign(1); 296 shared_memory->sixaxis_right_properties.is_newly_assigned.Assign(1);
298 break; 297 break;
299 case Core::HID::NpadStyleIndex::GameCube: 298 case Core::HID::NpadStyleIndex::GameCube:
@@ -314,12 +313,12 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
314 case Core::HID::NpadStyleIndex::SNES: 313 case Core::HID::NpadStyleIndex::SNES:
315 shared_memory->style_tag.lucia.Assign(1); 314 shared_memory->style_tag.lucia.Assign(1);
316 shared_memory->device_type.fullkey.Assign(1); 315 shared_memory->device_type.fullkey.Assign(1);
317 shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::Lucia; 316 shared_memory->applet_footer_type = AppletFooterUiType::Lucia;
318 break; 317 break;
319 case Core::HID::NpadStyleIndex::N64: 318 case Core::HID::NpadStyleIndex::N64:
320 shared_memory->style_tag.lagoon.Assign(1); 319 shared_memory->style_tag.lagoon.Assign(1);
321 shared_memory->device_type.fullkey.Assign(1); 320 shared_memory->device_type.fullkey.Assign(1);
322 shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::Lagon; 321 shared_memory->applet_footer_type = AppletFooterUiType::Lagon;
323 break; 322 break;
324 case Core::HID::NpadStyleIndex::SegaGenesis: 323 case Core::HID::NpadStyleIndex::SegaGenesis:
325 shared_memory->style_tag.lager.Assign(1); 324 shared_memory->style_tag.lager.Assign(1);
@@ -419,9 +418,17 @@ void Controller_NPad::RequestPadStateUpdate(Core::HID::NpadIdType npad_id) {
419 std::scoped_lock lock{mutex}; 418 std::scoped_lock lock{mutex};
420 auto& controller = GetControllerFromNpadIdType(npad_id); 419 auto& controller = GetControllerFromNpadIdType(npad_id);
421 const auto controller_type = controller.device->GetNpadStyleIndex(); 420 const auto controller_type = controller.device->GetNpadStyleIndex();
421
422 if (!controller.device->IsConnected() && controller.is_connected) {
423 DisconnectNpad(npad_id);
424 return;
425 }
422 if (!controller.device->IsConnected()) { 426 if (!controller.device->IsConnected()) {
423 return; 427 return;
424 } 428 }
429 if (controller.device->IsConnected() && !controller.is_connected) {
430 InitNewlyAddedController(npad_id);
431 }
425 432
426 // This function is unique to yuzu for the turbo buttons and motion to work properly 433 // This function is unique to yuzu for the turbo buttons and motion to work properly
427 controller.device->StatusUpdate(); 434 controller.device->StatusUpdate();
@@ -468,6 +475,10 @@ void Controller_NPad::RequestPadStateUpdate(Core::HID::NpadIdType npad_id) {
468 pad_entry.npad_buttons.l.Assign(button_state.zl); 475 pad_entry.npad_buttons.l.Assign(button_state.zl);
469 pad_entry.npad_buttons.r.Assign(button_state.zr); 476 pad_entry.npad_buttons.r.Assign(button_state.zr);
470 } 477 }
478
479 if (pad_entry.npad_buttons.raw != Core::HID::NpadButton::None) {
480 hid_core.SetLastActiveController(npad_id);
481 }
471} 482}
472 483
473void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing) { 484void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing) {
@@ -736,14 +747,6 @@ void Controller_NPad::SetSupportedStyleSet(Core::HID::NpadStyleTag style_set) {
736 747
737 // Once SetSupportedStyleSet is called controllers are fully initialized 748 // Once SetSupportedStyleSet is called controllers are fully initialized
738 is_controller_initialized = true; 749 is_controller_initialized = true;
739
740 // Connect all active controllers
741 for (auto& controller : controller_data) {
742 const auto& device = controller.device;
743 if (device->IsConnected()) {
744 AddNewControllerAt(device->GetNpadStyleIndex(), device->GetNpadIdType());
745 }
746 }
747} 750}
748 751
749Core::HID::NpadStyleTag Controller_NPad::GetSupportedStyleSet() const { 752Core::HID::NpadStyleTag Controller_NPad::GetSupportedStyleSet() const {
@@ -1116,7 +1119,7 @@ Result Controller_NPad::DisconnectNpad(Core::HID::NpadIdType npad_id) {
1116 .left = {}, 1119 .left = {},
1117 .right = {}, 1120 .right = {},
1118 }; 1121 };
1119 shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::None; 1122 shared_memory->applet_footer_type = AppletFooterUiType::None;
1120 1123
1121 controller.is_dual_left_connected = true; 1124 controller.is_dual_left_connected = true;
1122 controller.is_dual_right_connected = true; 1125 controller.is_dual_right_connected = true;
diff --git a/src/core/hle/service/hid/controllers/npad.h b/src/core/hle/service/hid/controllers/npad.h
index 0e9f899a4..949e58a4c 100644
--- a/src/core/hle/service/hid/controllers/npad.h
+++ b/src/core/hle/service/hid/controllers/npad.h
@@ -362,7 +362,7 @@ private:
362 enum class AppletFooterUiType : u8 { 362 enum class AppletFooterUiType : u8 {
363 None = 0, 363 None = 0,
364 HandheldNone = 1, 364 HandheldNone = 1,
365 HandheldJoyConLeftOnly = 1, 365 HandheldJoyConLeftOnly = 2,
366 HandheldJoyConRightOnly = 3, 366 HandheldJoyConRightOnly = 3,
367 HandheldJoyConLeftJoyConRight = 4, 367 HandheldJoyConLeftJoyConRight = 4,
368 JoyDual = 5, 368 JoyDual = 5,
@@ -384,13 +384,6 @@ private:
384 Lagon = 21, 384 Lagon = 21,
385 }; 385 };
386 386
387 struct AppletFooterUi {
388 AppletFooterUiAttributes attributes{};
389 AppletFooterUiType type{AppletFooterUiType::None};
390 INSERT_PADDING_BYTES(0x5B); // Reserved
391 };
392 static_assert(sizeof(AppletFooterUi) == 0x60, "AppletFooterUi is an invalid size");
393
394 // This is nn::hid::NpadLarkType 387 // This is nn::hid::NpadLarkType
395 enum class NpadLarkType : u32 { 388 enum class NpadLarkType : u32 {
396 Invalid, 389 Invalid,
@@ -421,13 +414,6 @@ private:
421 U, 414 U,
422 }; 415 };
423 416
424 struct AppletNfcXcd {
425 union {
426 AppletFooterUi applet_footer{};
427 Lifo<NfcXcdDeviceHandleStateImpl, 0x2> nfc_xcd_device_lifo;
428 };
429 };
430
431 // This is nn::hid::detail::NpadInternalState 417 // This is nn::hid::detail::NpadInternalState
432 struct NpadInternalState { 418 struct NpadInternalState {
433 Core::HID::NpadStyleTag style_tag{Core::HID::NpadStyleSet::None}; 419 Core::HID::NpadStyleTag style_tag{Core::HID::NpadStyleSet::None};
@@ -454,7 +440,9 @@ private:
454 Core::HID::NpadBatteryLevel battery_level_dual{}; 440 Core::HID::NpadBatteryLevel battery_level_dual{};
455 Core::HID::NpadBatteryLevel battery_level_left{}; 441 Core::HID::NpadBatteryLevel battery_level_left{};
456 Core::HID::NpadBatteryLevel battery_level_right{}; 442 Core::HID::NpadBatteryLevel battery_level_right{};
457 AppletNfcXcd applet_nfc_xcd{}; 443 AppletFooterUiAttributes applet_footer_attributes{};
444 AppletFooterUiType applet_footer_type{AppletFooterUiType::None};
445 INSERT_PADDING_BYTES(0x5B); // Reserved
458 INSERT_PADDING_BYTES(0x20); // Unknown 446 INSERT_PADDING_BYTES(0x20); // Unknown
459 Lifo<NpadGcTriggerState, hid_entry_count> gc_trigger_lifo{}; 447 Lifo<NpadGcTriggerState, hid_entry_count> gc_trigger_lifo{};
460 NpadLarkType lark_type_l_and_main{}; 448 NpadLarkType lark_type_l_and_main{};
diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp
index 6f711c0da..4d70006c1 100644
--- a/src/core/hle/service/hid/hid.cpp
+++ b/src/core/hle/service/hid/hid.cpp
@@ -2774,7 +2774,7 @@ private:
2774 2774
2775 IPC::ResponseBuilder rb{ctx, 3}; 2775 IPC::ResponseBuilder rb{ctx, 3};
2776 rb.Push(ResultSuccess); 2776 rb.Push(ResultSuccess);
2777 rb.PushEnum(Core::HID::NpadIdType::Handheld); 2777 rb.PushEnum(system.HIDCore().GetLastActiveController());
2778 } 2778 }
2779 2779
2780 void GetUniquePadsFromNpad(HLERequestContext& ctx) { 2780 void GetUniquePadsFromNpad(HLERequestContext& ctx) {
diff --git a/src/core/hle/service/nfc/common/device.cpp b/src/core/hle/service/nfc/common/device.cpp
index 5dda12343..674d2e4b2 100644
--- a/src/core/hle/service/nfc/common/device.cpp
+++ b/src/core/hle/service/nfc/common/device.cpp
@@ -874,17 +874,19 @@ Result NfcDevice::RestoreAmiibo() {
874} 874}
875 875
876Result NfcDevice::Format() { 876Result NfcDevice::Format() {
877 auto result1 = DeleteApplicationArea(); 877 Result result = ResultSuccess;
878 auto result2 = DeleteRegisterInfo();
879 878
880 if (result1.IsError()) { 879 if (device_state == DeviceState::TagFound) {
881 return result1; 880 result = Mount(NFP::ModelType::Amiibo, NFP::MountTarget::All);
882 } 881 }
883 882
884 if (result2.IsError()) { 883 if (result.IsError()) {
885 return result2; 884 return result;
886 } 885 }
887 886
887 DeleteApplicationArea();
888 DeleteRegisterInfo();
889
888 return Flush(); 890 return Flush();
889} 891}
890 892
diff --git a/src/core/hle/service/ngc/ngc.cpp b/src/core/hle/service/ngc/ngc.cpp
new file mode 100644
index 000000000..c26019ec0
--- /dev/null
+++ b/src/core/hle/service/ngc/ngc.cpp
@@ -0,0 +1,150 @@
1// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "common/string_util.h"
5#include "core/core.h"
6#include "core/hle/service/ipc_helpers.h"
7#include "core/hle/service/ngc/ngc.h"
8#include "core/hle/service/server_manager.h"
9#include "core/hle/service/service.h"
10
11namespace Service::NGC {
12
13class NgctServiceImpl final : public ServiceFramework<NgctServiceImpl> {
14public:
15 explicit NgctServiceImpl(Core::System& system_) : ServiceFramework{system_, "ngct:u"} {
16 // clang-format off
17 static const FunctionInfo functions[] = {
18 {0, &NgctServiceImpl::Match, "Match"},
19 {1, &NgctServiceImpl::Filter, "Filter"},
20 };
21 // clang-format on
22
23 RegisterHandlers(functions);
24 }
25
26private:
27 void Match(HLERequestContext& ctx) {
28 const auto buffer = ctx.ReadBuffer();
29 const auto text = Common::StringFromFixedZeroTerminatedBuffer(
30 reinterpret_cast<const char*>(buffer.data()), buffer.size());
31
32 LOG_WARNING(Service_NGC, "(STUBBED) called, text={}", text);
33
34 IPC::ResponseBuilder rb{ctx, 3};
35 rb.Push(ResultSuccess);
36 // Return false since we don't censor anything
37 rb.Push(false);
38 }
39
40 void Filter(HLERequestContext& ctx) {
41 const auto buffer = ctx.ReadBuffer();
42 const auto text = Common::StringFromFixedZeroTerminatedBuffer(
43 reinterpret_cast<const char*>(buffer.data()), buffer.size());
44
45 LOG_WARNING(Service_NGC, "(STUBBED) called, text={}", text);
46
47 // Return the same string since we don't censor anything
48 ctx.WriteBuffer(buffer);
49
50 IPC::ResponseBuilder rb{ctx, 2};
51 rb.Push(ResultSuccess);
52 }
53};
54
55class NgcServiceImpl final : public ServiceFramework<NgcServiceImpl> {
56public:
57 explicit NgcServiceImpl(Core::System& system_) : ServiceFramework(system_, "ngc:u") {
58 // clang-format off
59 static const FunctionInfo functions[] = {
60 {0, &NgcServiceImpl::GetContentVersion, "GetContentVersion"},
61 {1, &NgcServiceImpl::Check, "Check"},
62 {2, &NgcServiceImpl::Mask, "Mask"},
63 {3, &NgcServiceImpl::Reload, "Reload"},
64 };
65 // clang-format on
66
67 RegisterHandlers(functions);
68 }
69
70private:
71 static constexpr u32 NgcContentVersion = 1;
72
73 // This is nn::ngc::detail::ProfanityFilterOption
74 struct ProfanityFilterOption {
75 INSERT_PADDING_BYTES_NOINIT(0x20);
76 };
77 static_assert(sizeof(ProfanityFilterOption) == 0x20,
78 "ProfanityFilterOption has incorrect size");
79
80 void GetContentVersion(HLERequestContext& ctx) {
81 LOG_INFO(Service_NGC, "(STUBBED) called");
82
83 // This calls nn::ngc::ProfanityFilter::GetContentVersion
84 const u32 version = NgcContentVersion;
85
86 IPC::ResponseBuilder rb{ctx, 3};
87 rb.Push(ResultSuccess);
88 rb.Push(version);
89 }
90
91 void Check(HLERequestContext& ctx) {
92 LOG_INFO(Service_NGC, "(STUBBED) called");
93
94 struct InputParameters {
95 u32 flags;
96 ProfanityFilterOption option;
97 };
98
99 IPC::RequestParser rp{ctx};
100 [[maybe_unused]] const auto params = rp.PopRaw<InputParameters>();
101 [[maybe_unused]] const auto input = ctx.ReadBuffer(0);
102
103 // This calls nn::ngc::ProfanityFilter::CheckProfanityWords
104 const u32 out_flags = 0;
105
106 IPC::ResponseBuilder rb{ctx, 3};
107 rb.Push(ResultSuccess);
108 rb.Push(out_flags);
109 }
110
111 void Mask(HLERequestContext& ctx) {
112 LOG_INFO(Service_NGC, "(STUBBED) called");
113
114 struct InputParameters {
115 u32 flags;
116 ProfanityFilterOption option;
117 };
118
119 IPC::RequestParser rp{ctx};
120 [[maybe_unused]] const auto params = rp.PopRaw<InputParameters>();
121 const auto input = ctx.ReadBuffer(0);
122
123 // This calls nn::ngc::ProfanityFilter::MaskProfanityWordsInText
124 const u32 out_flags = 0;
125 ctx.WriteBuffer(input);
126
127 IPC::ResponseBuilder rb{ctx, 3};
128 rb.Push(ResultSuccess);
129 rb.Push(out_flags);
130 }
131
132 void Reload(HLERequestContext& ctx) {
133 LOG_INFO(Service_NGC, "(STUBBED) called");
134
135 // This reloads the database.
136
137 IPC::ResponseBuilder rb{ctx, 2};
138 rb.Push(ResultSuccess);
139 }
140};
141
142void LoopProcess(Core::System& system) {
143 auto server_manager = std::make_unique<ServerManager>(system);
144
145 server_manager->RegisterNamedService("ngct:u", std::make_shared<NgctServiceImpl>(system));
146 server_manager->RegisterNamedService("ngc:u", std::make_shared<NgcServiceImpl>(system));
147 ServerManager::RunServer(std::move(server_manager));
148}
149
150} // namespace Service::NGC
diff --git a/src/core/hle/service/ngct/ngct.h b/src/core/hle/service/ngc/ngc.h
index 27c34dad4..823b1aa81 100644
--- a/src/core/hle/service/ngct/ngct.h
+++ b/src/core/hle/service/ngc/ngc.h
@@ -7,8 +7,8 @@ namespace Core {
7class System; 7class System;
8} 8}
9 9
10namespace Service::NGCT { 10namespace Service::NGC {
11 11
12void LoopProcess(Core::System& system); 12void LoopProcess(Core::System& system);
13 13
14} // namespace Service::NGCT 14} // namespace Service::NGC
diff --git a/src/core/hle/service/ngct/ngct.cpp b/src/core/hle/service/ngct/ngct.cpp
deleted file mode 100644
index 493c80ed2..000000000
--- a/src/core/hle/service/ngct/ngct.cpp
+++ /dev/null
@@ -1,62 +0,0 @@
1// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "common/string_util.h"
5#include "core/core.h"
6#include "core/hle/service/ipc_helpers.h"
7#include "core/hle/service/ngct/ngct.h"
8#include "core/hle/service/server_manager.h"
9#include "core/hle/service/service.h"
10
11namespace Service::NGCT {
12
13class IService final : public ServiceFramework<IService> {
14public:
15 explicit IService(Core::System& system_) : ServiceFramework{system_, "ngct:u"} {
16 // clang-format off
17 static const FunctionInfo functions[] = {
18 {0, &IService::Match, "Match"},
19 {1, &IService::Filter, "Filter"},
20 };
21 // clang-format on
22
23 RegisterHandlers(functions);
24 }
25
26private:
27 void Match(HLERequestContext& ctx) {
28 const auto buffer = ctx.ReadBuffer();
29 const auto text = Common::StringFromFixedZeroTerminatedBuffer(
30 reinterpret_cast<const char*>(buffer.data()), buffer.size());
31
32 LOG_WARNING(Service_NGCT, "(STUBBED) called, text={}", text);
33
34 IPC::ResponseBuilder rb{ctx, 3};
35 rb.Push(ResultSuccess);
36 // Return false since we don't censor anything
37 rb.Push(false);
38 }
39
40 void Filter(HLERequestContext& ctx) {
41 const auto buffer = ctx.ReadBuffer();
42 const auto text = Common::StringFromFixedZeroTerminatedBuffer(
43 reinterpret_cast<const char*>(buffer.data()), buffer.size());
44
45 LOG_WARNING(Service_NGCT, "(STUBBED) called, text={}", text);
46
47 // Return the same string since we don't censor anything
48 ctx.WriteBuffer(buffer);
49
50 IPC::ResponseBuilder rb{ctx, 2};
51 rb.Push(ResultSuccess);
52 }
53};
54
55void LoopProcess(Core::System& system) {
56 auto server_manager = std::make_unique<ServerManager>(system);
57
58 server_manager->RegisterNamedService("ngct:u", std::make_shared<IService>(system));
59 ServerManager::RunServer(std::move(server_manager));
60}
61
62} // namespace Service::NGCT
diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp
index 69cdb5918..0ad607391 100644
--- a/src/core/hle/service/service.cpp
+++ b/src/core/hle/service/service.cpp
@@ -43,7 +43,7 @@
43#include "core/hle/service/ncm/ncm.h" 43#include "core/hle/service/ncm/ncm.h"
44#include "core/hle/service/nfc/nfc.h" 44#include "core/hle/service/nfc/nfc.h"
45#include "core/hle/service/nfp/nfp.h" 45#include "core/hle/service/nfp/nfp.h"
46#include "core/hle/service/ngct/ngct.h" 46#include "core/hle/service/ngc/ngc.h"
47#include "core/hle/service/nifm/nifm.h" 47#include "core/hle/service/nifm/nifm.h"
48#include "core/hle/service/nim/nim.h" 48#include "core/hle/service/nim/nim.h"
49#include "core/hle/service/npns/npns.h" 49#include "core/hle/service/npns/npns.h"
@@ -257,7 +257,7 @@ Services::Services(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system
257 kernel.RunOnGuestCoreProcess("NCM", [&] { NCM::LoopProcess(system); }); 257 kernel.RunOnGuestCoreProcess("NCM", [&] { NCM::LoopProcess(system); });
258 kernel.RunOnGuestCoreProcess("nfc", [&] { NFC::LoopProcess(system); }); 258 kernel.RunOnGuestCoreProcess("nfc", [&] { NFC::LoopProcess(system); });
259 kernel.RunOnGuestCoreProcess("nfp", [&] { NFP::LoopProcess(system); }); 259 kernel.RunOnGuestCoreProcess("nfp", [&] { NFP::LoopProcess(system); });
260 kernel.RunOnGuestCoreProcess("ngct", [&] { NGCT::LoopProcess(system); }); 260 kernel.RunOnGuestCoreProcess("ngc", [&] { NGC::LoopProcess(system); });
261 kernel.RunOnGuestCoreProcess("nifm", [&] { NIFM::LoopProcess(system); }); 261 kernel.RunOnGuestCoreProcess("nifm", [&] { NIFM::LoopProcess(system); });
262 kernel.RunOnGuestCoreProcess("nim", [&] { NIM::LoopProcess(system); }); 262 kernel.RunOnGuestCoreProcess("nim", [&] { NIM::LoopProcess(system); });
263 kernel.RunOnGuestCoreProcess("npns", [&] { NPNS::LoopProcess(system); }); 263 kernel.RunOnGuestCoreProcess("npns", [&] { NPNS::LoopProcess(system); });
diff --git a/src/core/hle/service/sockets/bsd.cpp b/src/core/hle/service/sockets/bsd.cpp
index d8509c1dd..85849d5f3 100644
--- a/src/core/hle/service/sockets/bsd.cpp
+++ b/src/core/hle/service/sockets/bsd.cpp
@@ -170,7 +170,7 @@ void BSD::Socket(HLERequestContext& ctx) {
170} 170}
171 171
172void BSD::Select(HLERequestContext& ctx) { 172void BSD::Select(HLERequestContext& ctx) {
173 LOG_WARNING(Service, "(STUBBED) called"); 173 LOG_DEBUG(Service, "(STUBBED) called");
174 174
175 IPC::ResponseBuilder rb{ctx, 4}; 175 IPC::ResponseBuilder rb{ctx, 4};
176 176
diff --git a/src/core/loader/deconstructed_rom_directory.cpp b/src/core/loader/deconstructed_rom_directory.cpp
index f4eaf3331..5a42dea48 100644
--- a/src/core/loader/deconstructed_rom_directory.cpp
+++ b/src/core/loader/deconstructed_rom_directory.cpp
@@ -18,7 +18,7 @@ namespace Loader {
18 18
19AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileSys::VirtualFile file_, 19AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileSys::VirtualFile file_,
20 bool override_update_) 20 bool override_update_)
21 : AppLoader(std::move(file_)), override_update(override_update_) { 21 : AppLoader(std::move(file_)), override_update(override_update_), is_hbl(false) {
22 const auto file_dir = file->GetContainingDirectory(); 22 const auto file_dir = file->GetContainingDirectory();
23 23
24 // Title ID 24 // Title ID
@@ -69,9 +69,9 @@ AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileSys
69} 69}
70 70
71AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory( 71AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(
72 FileSys::VirtualDir directory, bool override_update_) 72 FileSys::VirtualDir directory, bool override_update_, bool is_hbl_)
73 : AppLoader(directory->GetFile("main")), dir(std::move(directory)), 73 : AppLoader(directory->GetFile("main")), dir(std::move(directory)),
74 override_update(override_update_) {} 74 override_update(override_update_), is_hbl(is_hbl_) {}
75 75
76FileType AppLoader_DeconstructedRomDirectory::IdentifyType(const FileSys::VirtualFile& dir_file) { 76FileType AppLoader_DeconstructedRomDirectory::IdentifyType(const FileSys::VirtualFile& dir_file) {
77 if (FileSys::IsDirectoryExeFS(dir_file->GetContainingDirectory())) { 77 if (FileSys::IsDirectoryExeFS(dir_file->GetContainingDirectory())) {
@@ -147,7 +147,7 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
147 } 147 }
148 148
149 // Setup the process code layout 149 // Setup the process code layout
150 if (process.LoadFromMetadata(metadata, code_size).IsError()) { 150 if (process.LoadFromMetadata(metadata, code_size, is_hbl).IsError()) {
151 return {ResultStatus::ErrorUnableToParseKernelMetadata, {}}; 151 return {ResultStatus::ErrorUnableToParseKernelMetadata, {}};
152 } 152 }
153 153
diff --git a/src/core/loader/deconstructed_rom_directory.h b/src/core/loader/deconstructed_rom_directory.h
index f7702225e..1e9f765c9 100644
--- a/src/core/loader/deconstructed_rom_directory.h
+++ b/src/core/loader/deconstructed_rom_directory.h
@@ -27,7 +27,8 @@ public:
27 27
28 // Overload to accept exefs directory. Must contain 'main' and 'main.npdm' 28 // Overload to accept exefs directory. Must contain 'main' and 'main.npdm'
29 explicit AppLoader_DeconstructedRomDirectory(FileSys::VirtualDir directory, 29 explicit AppLoader_DeconstructedRomDirectory(FileSys::VirtualDir directory,
30 bool override_update_ = false); 30 bool override_update_ = false,
31 bool is_hbl_ = false);
31 32
32 /** 33 /**
33 * Identifies whether or not the given file is a deconstructed ROM directory. 34 * Identifies whether or not the given file is a deconstructed ROM directory.
@@ -62,6 +63,7 @@ private:
62 std::string name; 63 std::string name;
63 u64 title_id{}; 64 u64 title_id{};
64 bool override_update; 65 bool override_update;
66 bool is_hbl;
65 67
66 Modules modules; 68 Modules modules;
67}; 69};
diff --git a/src/core/loader/kip.cpp b/src/core/loader/kip.cpp
index d722459c6..bf56a08b4 100644
--- a/src/core/loader/kip.cpp
+++ b/src/core/loader/kip.cpp
@@ -90,7 +90,8 @@ AppLoader::LoadResult AppLoader_KIP::Load(Kernel::KProcess& process,
90 codeset.DataSegment().size += kip->GetBSSSize(); 90 codeset.DataSegment().size += kip->GetBSSSize();
91 91
92 // Setup the process code layout 92 // Setup the process code layout
93 if (process.LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), program_image.size()) 93 if (process
94 .LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), program_image.size(), false)
94 .IsError()) { 95 .IsError()) {
95 return {ResultStatus::ErrorNotInitialized, {}}; 96 return {ResultStatus::ErrorNotInitialized, {}};
96 } 97 }
diff --git a/src/core/loader/nro.cpp b/src/core/loader/nro.cpp
index d7562b4bc..69f1a54ed 100644
--- a/src/core/loader/nro.cpp
+++ b/src/core/loader/nro.cpp
@@ -196,7 +196,8 @@ static bool LoadNroImpl(Kernel::KProcess& process, const std::vector<u8>& data)
196 program_image.resize(static_cast<u32>(program_image.size()) + bss_size); 196 program_image.resize(static_cast<u32>(program_image.size()) + bss_size);
197 197
198 // Setup the process code layout 198 // Setup the process code layout
199 if (process.LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), program_image.size()) 199 if (process
200 .LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), program_image.size(), false)
200 .IsError()) { 201 .IsError()) {
201 return false; 202 return false;
202 } 203 }
diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp
index 549822506..1350da8dc 100644
--- a/src/core/loader/nso.cpp
+++ b/src/core/loader/nso.cpp
@@ -127,13 +127,14 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core::
127 } 127 }
128 128
129 // Apply patches if necessary 129 // Apply patches if necessary
130 if (pm && (pm->HasNSOPatch(nso_header.build_id) || Settings::values.dump_nso)) { 130 const auto name = nso_file.GetName();
131 if (pm && (pm->HasNSOPatch(nso_header.build_id, name) || Settings::values.dump_nso)) {
131 std::vector<u8> pi_header(sizeof(NSOHeader) + program_image.size()); 132 std::vector<u8> pi_header(sizeof(NSOHeader) + program_image.size());
132 std::memcpy(pi_header.data(), &nso_header, sizeof(NSOHeader)); 133 std::memcpy(pi_header.data(), &nso_header, sizeof(NSOHeader));
133 std::memcpy(pi_header.data() + sizeof(NSOHeader), program_image.data(), 134 std::memcpy(pi_header.data() + sizeof(NSOHeader), program_image.data(),
134 program_image.size()); 135 program_image.size());
135 136
136 pi_header = pm->PatchNSO(pi_header, nso_file.GetName()); 137 pi_header = pm->PatchNSO(pi_header, name);
137 138
138 std::copy(pi_header.begin() + sizeof(NSOHeader), pi_header.end(), program_image.data()); 139 std::copy(pi_header.begin() + sizeof(NSOHeader), pi_header.end(), program_image.data());
139 } 140 }
diff --git a/src/core/loader/nsp.cpp b/src/core/loader/nsp.cpp
index fe2af1ae6..f4ab75b77 100644
--- a/src/core/loader/nsp.cpp
+++ b/src/core/loader/nsp.cpp
@@ -30,7 +30,8 @@ AppLoader_NSP::AppLoader_NSP(FileSys::VirtualFile file_,
30 } 30 }
31 31
32 if (nsp->IsExtractedType()) { 32 if (nsp->IsExtractedType()) {
33 secondary_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>(nsp->GetExeFS()); 33 secondary_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>(
34 nsp->GetExeFS(), false, file->GetName() == "hbl.nsp");
34 } else { 35 } else {
35 const auto control_nca = 36 const auto control_nca =
36 nsp->GetNCA(nsp->GetProgramTitleID(), FileSys::ContentRecordType::Control); 37 nsp->GetNCA(nsp->GetProgramTitleID(), FileSys::ContentRecordType::Control);
diff --git a/src/core/memory/cheat_engine.cpp b/src/core/memory/cheat_engine.cpp
index 7b52f61a7..a06e99166 100644
--- a/src/core/memory/cheat_engine.cpp
+++ b/src/core/memory/cheat_engine.cpp
@@ -154,7 +154,7 @@ std::vector<CheatEntry> TextCheatParser::Parse(std::string_view data) const {
154 return {}; 154 return {};
155 } 155 }
156 156
157 const auto value = static_cast<u32>(std::stoul(hex, nullptr, 0x10)); 157 const auto value = static_cast<u32>(std::strtoul(hex.c_str(), nullptr, 0x10));
158 out[*current_entry].definition.opcodes[out[*current_entry].definition.num_opcodes++] = 158 out[*current_entry].definition.opcodes[out[*current_entry].definition.num_opcodes++] =
159 value; 159 value;
160 160
diff --git a/src/core/tools/renderdoc.cpp b/src/core/tools/renderdoc.cpp
new file mode 100644
index 000000000..44d24822a
--- /dev/null
+++ b/src/core/tools/renderdoc.cpp
@@ -0,0 +1,55 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <renderdoc_app.h>
5
6#include "common/assert.h"
7#include "common/dynamic_library.h"
8#include "core/tools/renderdoc.h"
9
10#ifdef WIN32
11#include <windows.h>
12#else
13#include <dlfcn.h>
14#endif
15
16namespace Tools {
17
18RenderdocAPI::RenderdocAPI() {
19#ifdef WIN32
20 if (HMODULE mod = GetModuleHandleA("renderdoc.dll")) {
21 const auto RENDERDOC_GetAPI =
22 reinterpret_cast<pRENDERDOC_GetAPI>(GetProcAddress(mod, "RENDERDOC_GetAPI"));
23 const s32 ret = RENDERDOC_GetAPI(eRENDERDOC_API_Version_1_6_0, (void**)&rdoc_api);
24 ASSERT(ret == 1);
25 }
26#else
27#ifdef ANDROID
28 static constexpr const char RENDERDOC_LIB[] = "libVkLayer_GLES_RenderDoc.so";
29#else
30 static constexpr const char RENDERDOC_LIB[] = "librenderdoc.so";
31#endif
32 if (void* mod = dlopen(RENDERDOC_LIB, RTLD_NOW | RTLD_NOLOAD)) {
33 const auto RENDERDOC_GetAPI =
34 reinterpret_cast<pRENDERDOC_GetAPI>(dlsym(mod, "RENDERDOC_GetAPI"));
35 const s32 ret = RENDERDOC_GetAPI(eRENDERDOC_API_Version_1_6_0, (void**)&rdoc_api);
36 ASSERT(ret == 1);
37 }
38#endif
39}
40
41RenderdocAPI::~RenderdocAPI() = default;
42
43void RenderdocAPI::ToggleCapture() {
44 if (!rdoc_api) [[unlikely]] {
45 return;
46 }
47 if (!is_capturing) {
48 rdoc_api->StartFrameCapture(NULL, NULL);
49 } else {
50 rdoc_api->EndFrameCapture(NULL, NULL);
51 }
52 is_capturing = !is_capturing;
53}
54
55} // namespace Tools
diff --git a/src/core/tools/renderdoc.h b/src/core/tools/renderdoc.h
new file mode 100644
index 000000000..0e5e43da5
--- /dev/null
+++ b/src/core/tools/renderdoc.h
@@ -0,0 +1,22 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6struct RENDERDOC_API_1_6_0;
7
8namespace Tools {
9
10class RenderdocAPI {
11public:
12 explicit RenderdocAPI();
13 ~RenderdocAPI();
14
15 void ToggleCapture();
16
17private:
18 RENDERDOC_API_1_6_0* rdoc_api{};
19 bool is_capturing{false};
20};
21
22} // namespace Tools
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp
index 34240b36f..8decdf399 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp
+++ b/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp
@@ -204,9 +204,7 @@ Id TextureImage(EmitContext& ctx, IR::TextureInstInfo info, const IR::Value& ind
204 if (def.count > 1) { 204 if (def.count > 1) {
205 throw NotImplementedException("Indirect texture sample"); 205 throw NotImplementedException("Indirect texture sample");
206 } 206 }
207 const Id sampler_id{def.id}; 207 return ctx.OpLoad(ctx.image_buffer_type, def.id);
208 const Id id{ctx.OpLoad(ctx.sampled_texture_buffer_type, sampler_id)};
209 return ctx.OpImage(ctx.image_buffer_type, id);
210 } else { 208 } else {
211 const TextureDefinition& def{ctx.textures.at(info.descriptor_index)}; 209 const TextureDefinition& def{ctx.textures.at(info.descriptor_index)};
212 if (def.count > 1) { 210 if (def.count > 1) {
diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
index 238fb40e3..72f69b7aa 100644
--- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
+++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
@@ -1247,9 +1247,8 @@ void EmitContext::DefineTextureBuffers(const Info& info, u32& binding) {
1247 } 1247 }
1248 const spv::ImageFormat format{spv::ImageFormat::Unknown}; 1248 const spv::ImageFormat format{spv::ImageFormat::Unknown};
1249 image_buffer_type = TypeImage(F32[1], spv::Dim::Buffer, 0U, false, false, 1, format); 1249 image_buffer_type = TypeImage(F32[1], spv::Dim::Buffer, 0U, false, false, 1, format);
1250 sampled_texture_buffer_type = TypeSampledImage(image_buffer_type);
1251 1250
1252 const Id type{TypePointer(spv::StorageClass::UniformConstant, sampled_texture_buffer_type)}; 1251 const Id type{TypePointer(spv::StorageClass::UniformConstant, image_buffer_type)};
1253 texture_buffers.reserve(info.texture_buffer_descriptors.size()); 1252 texture_buffers.reserve(info.texture_buffer_descriptors.size());
1254 for (const TextureBufferDescriptor& desc : info.texture_buffer_descriptors) { 1253 for (const TextureBufferDescriptor& desc : info.texture_buffer_descriptors) {
1255 if (desc.count != 1) { 1254 if (desc.count != 1) {
diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.h b/src/shader_recompiler/backend/spirv/spirv_emit_context.h
index e63330f11..7c49fd504 100644
--- a/src/shader_recompiler/backend/spirv/spirv_emit_context.h
+++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.h
@@ -206,7 +206,6 @@ public:
206 Id output_u32{}; 206 Id output_u32{};
207 207
208 Id image_buffer_type{}; 208 Id image_buffer_type{};
209 Id sampled_texture_buffer_type{};
210 Id image_u32{}; 209 Id image_u32{};
211 210
212 std::array<UniformDefinitions, Info::MAX_CBUFS> cbufs{}; 211 std::array<UniformDefinitions, Info::MAX_CBUFS> cbufs{};
diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h
index 4457b366f..1bdb0def5 100644
--- a/src/video_core/texture_cache/texture_cache.h
+++ b/src/video_core/texture_cache/texture_cache.h
@@ -719,6 +719,7 @@ typename P::ImageView* TextureCache<P>::TryFindFramebufferImageView(VAddr cpu_ad
719 return nullptr; 719 return nullptr;
720 } 720 }
721 const auto& image_map_ids = it->second; 721 const auto& image_map_ids = it->second;
722 boost::container::small_vector<const ImageBase*, 4> valid_images;
722 for (const ImageMapId map_id : image_map_ids) { 723 for (const ImageMapId map_id : image_map_ids) {
723 const ImageMapView& map = slot_map_views[map_id]; 724 const ImageMapView& map = slot_map_views[map_id];
724 const ImageBase& image = slot_images[map.image_id]; 725 const ImageBase& image = slot_images[map.image_id];
@@ -728,8 +729,20 @@ typename P::ImageView* TextureCache<P>::TryFindFramebufferImageView(VAddr cpu_ad
728 if (image.image_view_ids.empty()) { 729 if (image.image_view_ids.empty()) {
729 continue; 730 continue;
730 } 731 }
731 return &slot_image_views[image.image_view_ids.at(0)]; 732 valid_images.push_back(&image);
732 } 733 }
734
735 if (valid_images.size() == 1) [[likely]] {
736 return &slot_image_views[valid_images[0]->image_view_ids.at(0)];
737 }
738
739 if (valid_images.size() > 0) [[unlikely]] {
740 std::ranges::sort(valid_images, [](const auto* a, const auto* b) {
741 return a->modification_tick > b->modification_tick;
742 });
743 return &slot_image_views[valid_images[0]->image_view_ids.at(0)];
744 }
745
733 return nullptr; 746 return nullptr;
734} 747}
735 748
diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp
index cbeb8f168..b22fda746 100644
--- a/src/yuzu/configuration/configure_debug.cpp
+++ b/src/yuzu/configuration/configure_debug.cpp
@@ -59,6 +59,8 @@ void ConfigureDebug::SetConfiguration() {
59 ui->use_debug_asserts->setChecked(Settings::values.use_debug_asserts.GetValue()); 59 ui->use_debug_asserts->setChecked(Settings::values.use_debug_asserts.GetValue());
60 ui->use_auto_stub->setChecked(Settings::values.use_auto_stub.GetValue()); 60 ui->use_auto_stub->setChecked(Settings::values.use_auto_stub.GetValue());
61 ui->enable_all_controllers->setChecked(Settings::values.enable_all_controllers.GetValue()); 61 ui->enable_all_controllers->setChecked(Settings::values.enable_all_controllers.GetValue());
62 ui->enable_renderdoc_hotkey->setEnabled(runtime_lock);
63 ui->enable_renderdoc_hotkey->setChecked(Settings::values.enable_renderdoc_hotkey.GetValue());
62 ui->enable_graphics_debugging->setEnabled(runtime_lock); 64 ui->enable_graphics_debugging->setEnabled(runtime_lock);
63 ui->enable_graphics_debugging->setChecked(Settings::values.renderer_debug.GetValue()); 65 ui->enable_graphics_debugging->setChecked(Settings::values.renderer_debug.GetValue());
64 ui->enable_shader_feedback->setEnabled(runtime_lock); 66 ui->enable_shader_feedback->setEnabled(runtime_lock);
@@ -111,6 +113,7 @@ void ConfigureDebug::ApplyConfiguration() {
111 Settings::values.use_auto_stub = ui->use_auto_stub->isChecked(); 113 Settings::values.use_auto_stub = ui->use_auto_stub->isChecked();
112 Settings::values.enable_all_controllers = ui->enable_all_controllers->isChecked(); 114 Settings::values.enable_all_controllers = ui->enable_all_controllers->isChecked();
113 Settings::values.renderer_debug = ui->enable_graphics_debugging->isChecked(); 115 Settings::values.renderer_debug = ui->enable_graphics_debugging->isChecked();
116 Settings::values.enable_renderdoc_hotkey = ui->enable_renderdoc_hotkey->isChecked();
114 Settings::values.renderer_shader_feedback = ui->enable_shader_feedback->isChecked(); 117 Settings::values.renderer_shader_feedback = ui->enable_shader_feedback->isChecked();
115 Settings::values.cpu_debug_mode = ui->enable_cpu_debugging->isChecked(); 118 Settings::values.cpu_debug_mode = ui->enable_cpu_debugging->isChecked();
116 Settings::values.enable_nsight_aftermath = ui->enable_nsight_aftermath->isChecked(); 119 Settings::values.enable_nsight_aftermath = ui->enable_nsight_aftermath->isChecked();
diff --git a/src/yuzu/configuration/configure_debug.ui b/src/yuzu/configuration/configure_debug.ui
index 97c7d9022..66b8b7459 100644
--- a/src/yuzu/configuration/configure_debug.ui
+++ b/src/yuzu/configuration/configure_debug.ui
@@ -18,8 +18,8 @@
18 <rect> 18 <rect>
19 <x>0</x> 19 <x>0</x>
20 <y>0</y> 20 <y>0</y>
21 <width>829</width> 21 <width>842</width>
22 <height>758</height> 22 <height>741</height>
23 </rect> 23 </rect>
24 </property> 24 </property>
25 <layout class="QVBoxLayout" name="verticalLayout_1"> 25 <layout class="QVBoxLayout" name="verticalLayout_1">
@@ -260,7 +260,7 @@
260 <string>Graphics</string> 260 <string>Graphics</string>
261 </property> 261 </property>
262 <layout class="QGridLayout" name="gridLayout_2"> 262 <layout class="QGridLayout" name="gridLayout_2">
263 <item row="3" column="0"> 263 <item row="4" column="0">
264 <widget class="QCheckBox" name="disable_loop_safety_checks"> 264 <widget class="QCheckBox" name="disable_loop_safety_checks">
265 <property name="toolTip"> 265 <property name="toolTip">
266 <string>When checked, it executes shaders without loop logic changes</string> 266 <string>When checked, it executes shaders without loop logic changes</string>
@@ -270,33 +270,53 @@
270 </property> 270 </property>
271 </widget> 271 </widget>
272 </item> 272 </item>
273 <item row="4" column="0"> 273 <item row="8" column="0">
274 <widget class="QCheckBox" name="dump_shaders"> 274 <widget class="QCheckBox" name="disable_macro_hle">
275 <property name="enabled"> 275 <property name="enabled">
276 <bool>true</bool> 276 <bool>true</bool>
277 </property> 277 </property>
278 <property name="toolTip"> 278 <property name="toolTip">
279 <string>When checked, it will dump all the original assembler shaders from the disk shader cache or game as found</string> 279 <string>When checked, it disables the macro HLE functions. Enabling this makes games run slower</string>
280 </property> 280 </property>
281 <property name="text"> 281 <property name="text">
282 <string>Dump Game Shaders</string> 282 <string>Disable Macro HLE</string>
283 </property> 283 </property>
284 </widget> 284 </widget>
285 </item> 285 </item>
286 <item row="7" column="0"> 286 <item row="7" column="0">
287 <widget class="QCheckBox" name="disable_macro_hle"> 287 <widget class="QCheckBox" name="dump_macros">
288 <property name="enabled"> 288 <property name="enabled">
289 <bool>true</bool> 289 <bool>true</bool>
290 </property> 290 </property>
291 <property name="toolTip"> 291 <property name="toolTip">
292 <string>When checked, it disables the macro HLE functions. Enabling this makes games run slower</string> 292 <string>When checked, it will dump all the macro programs of the GPU</string>
293 </property> 293 </property>
294 <property name="text"> 294 <property name="text">
295 <string>Disable Macro HLE</string> 295 <string>Dump Maxwell Macros</string>
296 </property> 296 </property>
297 </widget> 297 </widget>
298 </item> 298 </item>
299 <item row="5" column="0"> 299 <item row="3" column="0">
300 <widget class="QCheckBox" name="enable_nsight_aftermath">
301 <property name="toolTip">
302 <string>When checked, it enables Nsight Aftermath crash dumps</string>
303 </property>
304 <property name="text">
305 <string>Enable Nsight Aftermath</string>
306 </property>
307 </widget>
308 </item>
309 <item row="2" column="0">
310 <widget class="QCheckBox" name="enable_shader_feedback">
311 <property name="toolTip">
312 <string>When checked, yuzu will log statistics about the compiled pipeline cache</string>
313 </property>
314 <property name="text">
315 <string>Enable Shader Feedback</string>
316 </property>
317 </widget>
318 </item>
319 <item row="6" column="0">
300 <widget class="QCheckBox" name="disable_macro_jit"> 320 <widget class="QCheckBox" name="disable_macro_jit">
301 <property name="enabled"> 321 <property name="enabled">
302 <bool>true</bool> 322 <bool>true</bool>
@@ -309,6 +329,22 @@
309 </property> 329 </property>
310 </widget> 330 </widget>
311 </item> 331 </item>
332 <item row="9" column="0">
333 <spacer name="verticalSpacer_5">
334 <property name="orientation">
335 <enum>Qt::Vertical</enum>
336 </property>
337 <property name="sizeType">
338 <enum>QSizePolicy::Preferred</enum>
339 </property>
340 <property name="sizeHint" stdset="0">
341 <size>
342 <width>20</width>
343 <height>0</height>
344 </size>
345 </property>
346 </spacer>
347 </item>
312 <item row="0" column="0"> 348 <item row="0" column="0">
313 <widget class="QCheckBox" name="enable_graphics_debugging"> 349 <widget class="QCheckBox" name="enable_graphics_debugging">
314 <property name="enabled"> 350 <property name="enabled">
@@ -322,55 +358,26 @@
322 </property> 358 </property>
323 </widget> 359 </widget>
324 </item> 360 </item>
325 <item row="6" column="0"> 361 <item row="5" column="0">
326 <widget class="QCheckBox" name="dump_macros"> 362 <widget class="QCheckBox" name="dump_shaders">
327 <property name="enabled"> 363 <property name="enabled">
328 <bool>true</bool> 364 <bool>true</bool>
329 </property> 365 </property>
330 <property name="toolTip"> 366 <property name="toolTip">
331 <string>When checked, it will dump all the macro programs of the GPU</string> 367 <string>When checked, it will dump all the original assembler shaders from the disk shader cache or game as found</string>
332 </property> 368 </property>
333 <property name="text"> 369 <property name="text">
334 <string>Dump Maxwell Macros</string> 370 <string>Dump Game Shaders</string>
335 </property> 371 </property>
336 </widget> 372 </widget>
337 </item> 373 </item>
338 <item row="1" column="0"> 374 <item row="1" column="0">
339 <widget class="QCheckBox" name="enable_shader_feedback"> 375 <widget class="QCheckBox" name="enable_renderdoc_hotkey">
340 <property name="toolTip">
341 <string>When checked, yuzu will log statistics about the compiled pipeline cache</string>
342 </property>
343 <property name="text">
344 <string>Enable Shader Feedback</string>
345 </property>
346 </widget>
347 </item>
348 <item row="2" column="0">
349 <widget class="QCheckBox" name="enable_nsight_aftermath">
350 <property name="toolTip">
351 <string>When checked, it enables Nsight Aftermath crash dumps</string>
352 </property>
353 <property name="text"> 376 <property name="text">
354 <string>Enable Nsight Aftermath</string> 377 <string>Enable Renderdoc Hotkey</string>
355 </property> 378 </property>
356 </widget> 379 </widget>
357 </item> 380 </item>
358 <item row="8" column="0">
359 <spacer name="verticalSpacer_5">
360 <property name="orientation">
361 <enum>Qt::Vertical</enum>
362 </property>
363 <property name="sizeType">
364 <enum>QSizePolicy::Preferred</enum>
365 </property>
366 <property name="sizeHint" stdset="0">
367 <size>
368 <width>20</width>
369 <height>0</height>
370 </size>
371 </property>
372 </spacer>
373 </item>
374 </layout> 381 </layout>
375 </widget> 382 </widget>
376 </item> 383 </item>
diff --git a/src/yuzu/configuration/configure_ui.cpp b/src/yuzu/configuration/configure_ui.cpp
index 34ab01617..a9fde9f4f 100644
--- a/src/yuzu/configuration/configure_ui.cpp
+++ b/src/yuzu/configuration/configure_ui.cpp
@@ -4,6 +4,7 @@
4#include "yuzu/configuration/configure_ui.h" 4#include "yuzu/configuration/configure_ui.h"
5 5
6#include <array> 6#include <array>
7#include <cstdlib>
7#include <set> 8#include <set>
8#include <stdexcept> 9#include <stdexcept>
9#include <string> 10#include <string>
@@ -94,11 +95,7 @@ static void PopulateResolutionComboBox(QComboBox* screenshot_height, QWidget* pa
94} 95}
95 96
96static u32 ScreenshotDimensionToInt(const QString& height) { 97static u32 ScreenshotDimensionToInt(const QString& height) {
97 try { 98 return std::strtoul(height.toUtf8(), nullptr, 0);
98 return std::stoi(height.toStdString());
99 } catch (std::invalid_argument&) {
100 return 0;
101 }
102} 99}
103 100
104ConfigureUi::ConfigureUi(Core::System& system_, QWidget* parent) 101ConfigureUi::ConfigureUi(Core::System& system_, QWidget* parent)
diff --git a/src/yuzu/configuration/shared_widget.cpp b/src/yuzu/configuration/shared_widget.cpp
index d63093985..ea8d7add4 100644
--- a/src/yuzu/configuration/shared_widget.cpp
+++ b/src/yuzu/configuration/shared_widget.cpp
@@ -63,7 +63,7 @@ static QString DefaultSuffix(QWidget* parent, Settings::BasicSetting& setting) {
63 return tr("%", context.c_str()); 63 return tr("%", context.c_str());
64 } 64 }
65 65
66 return QStringLiteral(""); 66 return default_suffix;
67} 67}
68 68
69QPushButton* Widget::CreateRestoreGlobalButton(bool using_global, QWidget* parent) { 69QPushButton* Widget::CreateRestoreGlobalButton(bool using_global, QWidget* parent) {
@@ -71,7 +71,7 @@ QPushButton* Widget::CreateRestoreGlobalButton(bool using_global, QWidget* paren
71 71
72 QStyle* style = parent->style(); 72 QStyle* style = parent->style();
73 QIcon* icon = new QIcon(style->standardIcon(QStyle::SP_LineEditClearButton)); 73 QIcon* icon = new QIcon(style->standardIcon(QStyle::SP_LineEditClearButton));
74 QPushButton* restore_button = new QPushButton(*icon, QStringLiteral(""), parent); 74 QPushButton* restore_button = new QPushButton(*icon, QStringLiteral(), parent);
75 restore_button->setObjectName(QStringLiteral("RestoreButton%1").arg(restore_button_count)); 75 restore_button->setObjectName(QStringLiteral("RestoreButton%1").arg(restore_button_count));
76 restore_button->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); 76 restore_button->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
77 77
@@ -151,7 +151,7 @@ QWidget* Widget::CreateCombobox(std::function<std::string()>& serializer,
151 return -1; 151 return -1;
152 }; 152 };
153 153
154 const u32 setting_value = std::stoi(setting.ToString()); 154 const u32 setting_value = std::strtoul(setting.ToString().c_str(), nullptr, 0);
155 combobox->setCurrentIndex(find_index(setting_value)); 155 combobox->setCurrentIndex(find_index(setting_value));
156 156
157 serializer = [this, enumeration]() { 157 serializer = [this, enumeration]() {
@@ -160,7 +160,7 @@ QWidget* Widget::CreateCombobox(std::function<std::string()>& serializer,
160 }; 160 };
161 161
162 restore_func = [this, find_index]() { 162 restore_func = [this, find_index]() {
163 const u32 global_value = std::stoi(RelevantDefault(setting)); 163 const u32 global_value = std::strtoul(RelevantDefault(setting).c_str(), nullptr, 0);
164 combobox->setCurrentIndex(find_index(global_value)); 164 combobox->setCurrentIndex(find_index(global_value));
165 }; 165 };
166 166
@@ -209,7 +209,7 @@ QWidget* Widget::CreateRadioGroup(std::function<std::string()>& serializer,
209 } 209 }
210 }; 210 };
211 211
212 const u32 setting_value = std::stoi(setting.ToString()); 212 const u32 setting_value = std::strtoul(setting.ToString().c_str(), nullptr, 0);
213 set_index(setting_value); 213 set_index(setting_value);
214 214
215 serializer = [get_selected]() { 215 serializer = [get_selected]() {
@@ -218,7 +218,7 @@ QWidget* Widget::CreateRadioGroup(std::function<std::string()>& serializer,
218 }; 218 };
219 219
220 restore_func = [this, set_index]() { 220 restore_func = [this, set_index]() {
221 const u32 global_value = std::stoi(RelevantDefault(setting)); 221 const u32 global_value = std::strtoul(RelevantDefault(setting).c_str(), nullptr, 0);
222 set_index(global_value); 222 set_index(global_value);
223 }; 223 };
224 224
@@ -255,6 +255,59 @@ QWidget* Widget::CreateLineEdit(std::function<std::string()>& serializer,
255 return line_edit; 255 return line_edit;
256} 256}
257 257
258static void CreateIntSlider(Settings::BasicSetting& setting, bool reversed, float multiplier,
259 QLabel* feedback, const QString& use_format, QSlider* slider,
260 std::function<std::string()>& serializer,
261 std::function<void()>& restore_func) {
262 const int max_val = std::strtol(setting.MaxVal().c_str(), nullptr, 0);
263
264 const auto update_feedback = [=](int value) {
265 int present = (reversed ? max_val - value : value) * multiplier + 0.5f;
266 feedback->setText(use_format.arg(QVariant::fromValue(present).value<QString>()));
267 };
268
269 QObject::connect(slider, &QAbstractSlider::valueChanged, update_feedback);
270 update_feedback(std::strtol(setting.ToString().c_str(), nullptr, 0));
271
272 slider->setMinimum(std::strtol(setting.MinVal().c_str(), nullptr, 0));
273 slider->setMaximum(max_val);
274 slider->setValue(std::strtol(setting.ToString().c_str(), nullptr, 0));
275
276 serializer = [slider]() { return std::to_string(slider->value()); };
277 restore_func = [slider, &setting]() {
278 slider->setValue(std::strtol(RelevantDefault(setting).c_str(), nullptr, 0));
279 };
280}
281
282static void CreateFloatSlider(Settings::BasicSetting& setting, bool reversed, float multiplier,
283 QLabel* feedback, const QString& use_format, QSlider* slider,
284 std::function<std::string()>& serializer,
285 std::function<void()>& restore_func) {
286 const float max_val = std::strtof(setting.MaxVal().c_str(), nullptr);
287 const float min_val = std::strtof(setting.MinVal().c_str(), nullptr);
288 const float use_multiplier =
289 multiplier == default_multiplier ? default_float_multiplier : multiplier;
290
291 const auto update_feedback = [=](float value) {
292 int present = (reversed ? max_val - value : value) + 0.5f;
293 feedback->setText(use_format.arg(QVariant::fromValue(present).value<QString>()));
294 };
295
296 QObject::connect(slider, &QAbstractSlider::valueChanged, update_feedback);
297 update_feedback(std::strtof(setting.ToString().c_str(), nullptr));
298
299 slider->setMinimum(min_val * use_multiplier);
300 slider->setMaximum(max_val * use_multiplier);
301 slider->setValue(std::strtof(setting.ToString().c_str(), nullptr) * use_multiplier);
302
303 serializer = [slider, use_multiplier]() {
304 return std::to_string(slider->value() / use_multiplier);
305 };
306 restore_func = [slider, &setting, use_multiplier]() {
307 slider->setValue(std::strtof(RelevantDefault(setting).c_str(), nullptr) * use_multiplier);
308 };
309}
310
258QWidget* Widget::CreateSlider(bool reversed, float multiplier, const QString& given_suffix, 311QWidget* Widget::CreateSlider(bool reversed, float multiplier, const QString& given_suffix,
259 std::function<std::string()>& serializer, 312 std::function<std::string()>& serializer,
260 std::function<void()>& restore_func, 313 std::function<void()>& restore_func,
@@ -278,27 +331,19 @@ QWidget* Widget::CreateSlider(bool reversed, float multiplier, const QString& gi
278 331
279 layout->setContentsMargins(0, 0, 0, 0); 332 layout->setContentsMargins(0, 0, 0, 0);
280 333
281 int max_val = std::stoi(setting.MaxVal()); 334 QString suffix = given_suffix == default_suffix ? DefaultSuffix(this, setting) : given_suffix;
282
283 QString suffix =
284 given_suffix == QStringLiteral("") ? DefaultSuffix(this, setting) : given_suffix;
285 335
286 const QString use_format = QStringLiteral("%1").append(suffix); 336 const QString use_format = QStringLiteral("%1").append(suffix);
287 337
288 QObject::connect(slider, &QAbstractSlider::valueChanged, [=](int value) { 338 if (setting.IsIntegral()) {
289 int present = (reversed ? max_val - value : value) * multiplier + 0.5f; 339 CreateIntSlider(setting, reversed, multiplier, feedback, use_format, slider, serializer,
290 feedback->setText(use_format.arg(QVariant::fromValue(present).value<QString>())); 340 restore_func);
291 }); 341 } else {
292 342 CreateFloatSlider(setting, reversed, multiplier, feedback, use_format, slider, serializer,
293 slider->setMinimum(std::stoi(setting.MinVal())); 343 restore_func);
294 slider->setMaximum(max_val); 344 }
295 slider->setValue(std::stoi(setting.ToString()));
296 345
297 slider->setInvertedAppearance(reversed); 346 slider->setInvertedAppearance(reversed);
298 slider->setInvertedControls(reversed);
299
300 serializer = [this]() { return std::to_string(slider->value()); };
301 restore_func = [this]() { slider->setValue(std::stoi(RelevantDefault(setting))); };
302 347
303 if (!Settings::IsConfiguringGlobal()) { 348 if (!Settings::IsConfiguringGlobal()) {
304 QObject::connect(slider, &QAbstractSlider::actionTriggered, [touch]() { touch(); }); 349 QObject::connect(slider, &QAbstractSlider::actionTriggered, [touch]() { touch(); });
@@ -311,14 +356,11 @@ QWidget* Widget::CreateSpinBox(const QString& given_suffix,
311 std::function<std::string()>& serializer, 356 std::function<std::string()>& serializer,
312 std::function<void()>& restore_func, 357 std::function<void()>& restore_func,
313 const std::function<void()>& touch) { 358 const std::function<void()>& touch) {
314 const int min_val = 359 const auto min_val = std::strtol(setting.MinVal().c_str(), nullptr, 0);
315 setting.Ranged() ? std::stoi(setting.MinVal()) : std::numeric_limits<int>::min(); 360 const auto max_val = std::strtol(setting.MaxVal().c_str(), nullptr, 0);
316 const int max_val = 361 const auto default_val = std::strtol(setting.ToString().c_str(), nullptr, 0);
317 setting.Ranged() ? std::stoi(setting.MaxVal()) : std::numeric_limits<int>::max();
318 const int default_val = std::stoi(setting.ToString());
319 362
320 QString suffix = 363 QString suffix = given_suffix == default_suffix ? DefaultSuffix(this, setting) : given_suffix;
321 given_suffix == QStringLiteral("") ? DefaultSuffix(this, setting) : given_suffix;
322 364
323 spinbox = new QSpinBox(this); 365 spinbox = new QSpinBox(this);
324 spinbox->setRange(min_val, max_val); 366 spinbox->setRange(min_val, max_val);
@@ -329,13 +371,13 @@ QWidget* Widget::CreateSpinBox(const QString& given_suffix,
329 serializer = [this]() { return std::to_string(spinbox->value()); }; 371 serializer = [this]() { return std::to_string(spinbox->value()); };
330 372
331 restore_func = [this]() { 373 restore_func = [this]() {
332 auto value{std::stol(RelevantDefault(setting))}; 374 auto value{std::strtol(RelevantDefault(setting).c_str(), nullptr, 0)};
333 spinbox->setValue(value); 375 spinbox->setValue(value);
334 }; 376 };
335 377
336 if (!Settings::IsConfiguringGlobal()) { 378 if (!Settings::IsConfiguringGlobal()) {
337 QObject::connect(spinbox, QOverload<int>::of(&QSpinBox::valueChanged), [this, touch]() { 379 QObject::connect(spinbox, QOverload<int>::of(&QSpinBox::valueChanged), [this, touch]() {
338 if (spinbox->value() != std::stoi(setting.ToStringGlobal())) { 380 if (spinbox->value() != std::strtol(setting.ToStringGlobal().c_str(), nullptr, 0)) {
339 touch(); 381 touch();
340 } 382 }
341 }); 383 });
@@ -344,6 +386,42 @@ QWidget* Widget::CreateSpinBox(const QString& given_suffix,
344 return spinbox; 386 return spinbox;
345} 387}
346 388
389QWidget* Widget::CreateDoubleSpinBox(const QString& given_suffix,
390 std::function<std::string()>& serializer,
391 std::function<void()>& restore_func,
392 const std::function<void()>& touch) {
393 const auto min_val = std::strtod(setting.MinVal().c_str(), nullptr);
394 const auto max_val = std::strtod(setting.MaxVal().c_str(), nullptr);
395 const auto default_val = std::strtod(setting.ToString().c_str(), nullptr);
396
397 QString suffix = given_suffix == default_suffix ? DefaultSuffix(this, setting) : given_suffix;
398
399 double_spinbox = new QDoubleSpinBox(this);
400 double_spinbox->setRange(min_val, max_val);
401 double_spinbox->setValue(default_val);
402 double_spinbox->setSuffix(suffix);
403 double_spinbox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
404
405 serializer = [this]() { return fmt::format("{:f}", double_spinbox->value()); };
406
407 restore_func = [this]() {
408 auto value{std::strtod(RelevantDefault(setting).c_str(), nullptr)};
409 double_spinbox->setValue(value);
410 };
411
412 if (!Settings::IsConfiguringGlobal()) {
413 QObject::connect(double_spinbox, QOverload<double>::of(&QDoubleSpinBox::valueChanged),
414 [this, touch]() {
415 if (double_spinbox->value() !=
416 std::strtod(setting.ToStringGlobal().c_str(), nullptr)) {
417 touch();
418 }
419 });
420 }
421
422 return double_spinbox;
423}
424
347QWidget* Widget::CreateHexEdit(std::function<std::string()>& serializer, 425QWidget* Widget::CreateHexEdit(std::function<std::string()>& serializer,
348 std::function<void()>& restore_func, 426 std::function<void()>& restore_func,
349 const std::function<void()>& touch) { 427 const std::function<void()>& touch) {
@@ -353,7 +431,8 @@ QWidget* Widget::CreateHexEdit(std::function<std::string()>& serializer,
353 } 431 }
354 432
355 auto to_hex = [=](const std::string& input) { 433 auto to_hex = [=](const std::string& input) {
356 return QString::fromStdString(fmt::format("{:08x}", std::stoul(input))); 434 return QString::fromStdString(
435 fmt::format("{:08x}", std::strtoul(input.c_str(), nullptr, 0)));
357 }; 436 };
358 437
359 QRegularExpressionValidator* regex = new QRegularExpressionValidator( 438 QRegularExpressionValidator* regex = new QRegularExpressionValidator(
@@ -366,7 +445,7 @@ QWidget* Widget::CreateHexEdit(std::function<std::string()>& serializer,
366 line_edit->setValidator(regex); 445 line_edit->setValidator(regex);
367 446
368 auto hex_to_dec = [this]() -> std::string { 447 auto hex_to_dec = [this]() -> std::string {
369 return std::to_string(std::stoul(line_edit->text().toStdString(), nullptr, 16)); 448 return std::to_string(std::strtoul(line_edit->text().toStdString().c_str(), nullptr, 16));
370 }; 449 };
371 450
372 serializer = [hex_to_dec]() { return hex_to_dec(); }; 451 serializer = [hex_to_dec]() { return hex_to_dec(); };
@@ -386,7 +465,8 @@ QWidget* Widget::CreateDateTimeEdit(bool disabled, bool restrict,
386 std::function<void()>& restore_func, 465 std::function<void()>& restore_func,
387 const std::function<void()>& touch) { 466 const std::function<void()>& touch) {
388 const long long current_time = QDateTime::currentSecsSinceEpoch(); 467 const long long current_time = QDateTime::currentSecsSinceEpoch();
389 const s64 the_time = disabled ? current_time : std::stoll(setting.ToString()); 468 const s64 the_time =
469 disabled ? current_time : std::strtoll(setting.ToString().c_str(), nullptr, 0);
390 const auto default_val = QDateTime::fromSecsSinceEpoch(the_time); 470 const auto default_val = QDateTime::fromSecsSinceEpoch(the_time);
391 471
392 date_time_edit = new QDateTimeEdit(this); 472 date_time_edit = new QDateTimeEdit(this);
@@ -399,7 +479,7 @@ QWidget* Widget::CreateDateTimeEdit(bool disabled, bool restrict,
399 auto get_clear_val = [this, restrict, current_time]() { 479 auto get_clear_val = [this, restrict, current_time]() {
400 return QDateTime::fromSecsSinceEpoch([this, restrict, current_time]() { 480 return QDateTime::fromSecsSinceEpoch([this, restrict, current_time]() {
401 if (restrict && checkbox->checkState() == Qt::Checked) { 481 if (restrict && checkbox->checkState() == Qt::Checked) {
402 return std::stoll(RelevantDefault(setting)); 482 return std::strtoll(RelevantDefault(setting).c_str(), nullptr, 0);
403 } 483 }
404 return current_time; 484 return current_time;
405 }()); 485 }());
@@ -506,8 +586,7 @@ void Widget::SetupComponent(const QString& label, std::function<void()>& load_fu
506 } else { 586 } else {
507 data_component = CreateCombobox(serializer, restore_func, touch); 587 data_component = CreateCombobox(serializer, restore_func, touch);
508 } 588 }
509 } else if (type == typeid(u32) || type == typeid(int) || type == typeid(u16) || 589 } else if (setting.IsIntegral()) {
510 type == typeid(s64) || type == typeid(u8)) {
511 switch (request) { 590 switch (request) {
512 case RequestType::Slider: 591 case RequestType::Slider:
513 case RequestType::ReverseSlider: 592 case RequestType::ReverseSlider:
@@ -534,6 +613,20 @@ void Widget::SetupComponent(const QString& label, std::function<void()>& load_fu
534 default: 613 default:
535 UNIMPLEMENTED(); 614 UNIMPLEMENTED();
536 } 615 }
616 } else if (setting.IsFloatingPoint()) {
617 switch (request) {
618 case RequestType::Default:
619 case RequestType::SpinBox:
620 data_component = CreateDoubleSpinBox(suffix, serializer, restore_func, touch);
621 break;
622 case RequestType::Slider:
623 case RequestType::ReverseSlider:
624 data_component = CreateSlider(request == RequestType::ReverseSlider, multiplier, suffix,
625 serializer, restore_func, touch);
626 break;
627 default:
628 UNIMPLEMENTED();
629 }
537 } else if (type == typeid(std::string)) { 630 } else if (type == typeid(std::string)) {
538 switch (request) { 631 switch (request) {
539 case RequestType::Default: 632 case RequestType::Default:
@@ -638,10 +731,10 @@ Widget::Widget(Settings::BasicSetting* setting_, const TranslationMap& translati
638 return std::pair{translations.at(id).first, translations.at(id).second}; 731 return std::pair{translations.at(id).first, translations.at(id).second};
639 } 732 }
640 LOG_WARNING(Frontend, "Translation table lacks entry for \"{}\"", setting_label); 733 LOG_WARNING(Frontend, "Translation table lacks entry for \"{}\"", setting_label);
641 return std::pair{QString::fromStdString(setting_label), QStringLiteral("")}; 734 return std::pair{QString::fromStdString(setting_label), QStringLiteral()};
642 }(); 735 }();
643 736
644 if (label == QStringLiteral("")) { 737 if (label == QStringLiteral()) {
645 LOG_DEBUG(Frontend, "Translation table has empty entry for \"{}\", skipping...", 738 LOG_DEBUG(Frontend, "Translation table has empty entry for \"{}\", skipping...",
646 setting.GetLabel()); 739 setting.GetLabel());
647 return; 740 return;
diff --git a/src/yuzu/configuration/shared_widget.h b/src/yuzu/configuration/shared_widget.h
index 5303dd898..226284cf3 100644
--- a/src/yuzu/configuration/shared_widget.h
+++ b/src/yuzu/configuration/shared_widget.h
@@ -22,6 +22,7 @@ class QObject;
22class QPushButton; 22class QPushButton;
23class QSlider; 23class QSlider;
24class QSpinBox; 24class QSpinBox;
25class QDoubleSpinBox;
25class QRadioButton; 26class QRadioButton;
26 27
27namespace Settings { 28namespace Settings {
@@ -43,6 +44,10 @@ enum class RequestType {
43 MaxEnum, 44 MaxEnum,
44}; 45};
45 46
47constexpr float default_multiplier{1.f};
48constexpr float default_float_multiplier{100.f};
49static const QString default_suffix = QStringLiteral();
50
46class Widget : public QWidget { 51class Widget : public QWidget {
47 Q_OBJECT 52 Q_OBJECT
48 53
@@ -66,8 +71,9 @@ public:
66 const ComboboxTranslationMap& combobox_translations, QWidget* parent, 71 const ComboboxTranslationMap& combobox_translations, QWidget* parent,
67 bool runtime_lock, std::vector<std::function<void(bool)>>& apply_funcs_, 72 bool runtime_lock, std::vector<std::function<void(bool)>>& apply_funcs_,
68 RequestType request = RequestType::Default, bool managed = true, 73 RequestType request = RequestType::Default, bool managed = true,
69 float multiplier = 1.0f, Settings::BasicSetting* other_setting = nullptr, 74 float multiplier = default_multiplier,
70 const QString& suffix = QStringLiteral("")); 75 Settings::BasicSetting* other_setting = nullptr,
76 const QString& suffix = default_suffix);
71 virtual ~Widget(); 77 virtual ~Widget();
72 78
73 /** 79 /**
@@ -89,6 +95,7 @@ public:
89 QPushButton* restore_button{}; ///< Restore button for custom configurations 95 QPushButton* restore_button{}; ///< Restore button for custom configurations
90 QLineEdit* line_edit{}; ///< QLineEdit, used for LineEdit and HexEdit 96 QLineEdit* line_edit{}; ///< QLineEdit, used for LineEdit and HexEdit
91 QSpinBox* spinbox{}; 97 QSpinBox* spinbox{};
98 QDoubleSpinBox* double_spinbox{};
92 QCheckBox* checkbox{}; 99 QCheckBox* checkbox{};
93 QSlider* slider{}; 100 QSlider* slider{};
94 QComboBox* combobox{}; 101 QComboBox* combobox{};
@@ -126,6 +133,9 @@ private:
126 const std::function<void()>& touch); 133 const std::function<void()>& touch);
127 QWidget* CreateSpinBox(const QString& suffix, std::function<std::string()>& serializer, 134 QWidget* CreateSpinBox(const QString& suffix, std::function<std::string()>& serializer,
128 std::function<void()>& restore_func, const std::function<void()>& touch); 135 std::function<void()>& restore_func, const std::function<void()>& touch);
136 QWidget* CreateDoubleSpinBox(const QString& suffix, std::function<std::string()>& serializer,
137 std::function<void()>& restore_func,
138 const std::function<void()>& touch);
129 139
130 QWidget* parent; 140 QWidget* parent;
131 const TranslationMap& translations; 141 const TranslationMap& translations;
@@ -145,14 +155,15 @@ public:
145 Widget* BuildWidget(Settings::BasicSetting* setting, 155 Widget* BuildWidget(Settings::BasicSetting* setting,
146 std::vector<std::function<void(bool)>>& apply_funcs, 156 std::vector<std::function<void(bool)>>& apply_funcs,
147 RequestType request = RequestType::Default, bool managed = true, 157 RequestType request = RequestType::Default, bool managed = true,
148 float multiplier = 1.0f, Settings::BasicSetting* other_setting = nullptr, 158 float multiplier = default_multiplier,
149 const QString& suffix = QStringLiteral("")) const; 159 Settings::BasicSetting* other_setting = nullptr,
160 const QString& suffix = default_suffix) const;
150 161
151 Widget* BuildWidget(Settings::BasicSetting* setting, 162 Widget* BuildWidget(Settings::BasicSetting* setting,
152 std::vector<std::function<void(bool)>>& apply_funcs, 163 std::vector<std::function<void(bool)>>& apply_funcs,
153 Settings::BasicSetting* other_setting, 164 Settings::BasicSetting* other_setting,
154 RequestType request = RequestType::Default, 165 RequestType request = RequestType::Default,
155 const QString& suffix = QStringLiteral("")) const; 166 const QString& suffix = default_suffix) const;
156 167
157 const ComboboxTranslationMap& ComboboxTranslations() const; 168 const ComboboxTranslationMap& ComboboxTranslations() const;
158 169
diff --git a/src/yuzu/hotkeys.h b/src/yuzu/hotkeys.h
index 848239c35..56eee8d82 100644
--- a/src/yuzu/hotkeys.h
+++ b/src/yuzu/hotkeys.h
@@ -4,10 +4,12 @@
4#pragma once 4#pragma once
5 5
6#include <map> 6#include <map>
7#include <QKeySequence>
8#include <QString>
9#include <QWidget>
7#include "core/hid/hid_types.h" 10#include "core/hid/hid_types.h"
8 11
9class QDialog; 12class QDialog;
10class QKeySequence;
11class QSettings; 13class QSettings;
12class QShortcut; 14class QShortcut;
13class ControllerShortcut; 15class ControllerShortcut;
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 97d216638..d32aa9615 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -9,6 +9,7 @@
9#include <memory> 9#include <memory>
10#include <thread> 10#include <thread>
11#include "core/loader/nca.h" 11#include "core/loader/nca.h"
12#include "core/tools/renderdoc.h"
12#ifdef __APPLE__ 13#ifdef __APPLE__
13#include <unistd.h> // for chdir 14#include <unistd.h> // for chdir
14#endif 15#endif
@@ -1348,6 +1349,11 @@ void GMainWindow::InitializeHotkeys() {
1348 connect_shortcut(QStringLiteral("Toggle Framerate Limit"), [] { 1349 connect_shortcut(QStringLiteral("Toggle Framerate Limit"), [] {
1349 Settings::values.use_speed_limit.SetValue(!Settings::values.use_speed_limit.GetValue()); 1350 Settings::values.use_speed_limit.SetValue(!Settings::values.use_speed_limit.GetValue());
1350 }); 1351 });
1352 connect_shortcut(QStringLiteral("Toggle Renderdoc Capture"), [this] {
1353 if (Settings::values.enable_renderdoc_hotkey) {
1354 system->GetRenderdocAPI().ToggleCapture();
1355 }
1356 });
1351 connect_shortcut(QStringLiteral("Toggle Mouse Panning"), [&] { 1357 connect_shortcut(QStringLiteral("Toggle Mouse Panning"), [&] {
1352 if (Settings::values.mouse_enabled) { 1358 if (Settings::values.mouse_enabled) {
1353 Settings::values.mouse_panning = false; 1359 Settings::values.mouse_panning = false;
diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp
index c42d98709..0d25ff400 100644
--- a/src/yuzu_cmd/config.cpp
+++ b/src/yuzu_cmd/config.cpp
@@ -259,7 +259,7 @@ void Config::ReadValues() {
259 std::stringstream ss(title_list); 259 std::stringstream ss(title_list);
260 std::string line; 260 std::string line;
261 while (std::getline(ss, line, '|')) { 261 while (std::getline(ss, line, '|')) {
262 const auto title_id = std::stoul(line, nullptr, 16); 262 const auto title_id = std::strtoul(line.c_str(), nullptr, 16);
263 const auto disabled_list = sdl2_config->Get("AddOns", "disabled_" + line, ""); 263 const auto disabled_list = sdl2_config->Get("AddOns", "disabled_" + line, "");
264 264
265 std::stringstream inner_ss(disabled_list); 265 std::stringstream inner_ss(disabled_list);
diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp
index 55d0938f7..087cfaa26 100644
--- a/src/yuzu_cmd/yuzu.cpp
+++ b/src/yuzu_cmd/yuzu.cpp
@@ -264,8 +264,9 @@ int main(int argc, char** argv) {
264 nickname = match[1]; 264 nickname = match[1];
265 password = match[2]; 265 password = match[2];
266 address = match[3]; 266 address = match[3];
267 if (!match[4].str().empty()) 267 if (!match[4].str().empty()) {
268 port = static_cast<u16>(std::stoi(match[4])); 268 port = static_cast<u16>(std::strtoul(match[4].str().c_str(), nullptr, 0));
269 }
269 std::regex nickname_re("^[a-zA-Z0-9._\\- ]+$"); 270 std::regex nickname_re("^[a-zA-Z0-9._\\- ]+$");
270 if (!std::regex_match(nickname, nickname_re)) { 271 if (!std::regex_match(nickname, nickname_re)) {
271 std::cout 272 std::cout