Compare commits

...

7 Commits

Author SHA1 Message Date
0adba08441 Android #178 2024-01-01 00:57:34 +00:00
b7bb6fd44c Merge PR 12543 2024-01-01 00:57:33 +00:00
1f99106eb4 Merge PR 12518 2024-01-01 00:57:33 +00:00
a5377a59a1 Merge PR 12513 2024-01-01 00:57:33 +00:00
8c3dfeb0d6 Merge PR 12501 2024-01-01 00:57:33 +00:00
fe1e6a0448 Merge PR 12466 2024-01-01 00:57:33 +00:00
79cfd1f97e Merge PR 12454 2024-01-01 00:57:33 +00:00
76 changed files with 2436 additions and 1271 deletions

View File

@ -1,3 +1,17 @@
| Pull Request | Commit | Title | Author | Merged? |
|----|----|----|----|----|
| [12454](https://github.com/yuzu-emu/yuzu//pull/12454) | [`3a4e7d45f`](https://github.com/yuzu-emu/yuzu//pull/12454/files) | core_timing: minor refactors | [liamwhite](https://github.com/liamwhite/) | Yes |
| [12466](https://github.com/yuzu-emu/yuzu//pull/12466) | [`adb2af0a2`](https://github.com/yuzu-emu/yuzu//pull/12466/files) | core: track separate heap allocation for linux | [liamwhite](https://github.com/liamwhite/) | Yes |
| [12501](https://github.com/yuzu-emu/yuzu//pull/12501) | [`d1c99c5d5`](https://github.com/yuzu-emu/yuzu//pull/12501/files) | ips_layer: prevent out of bounds access with offset exceeding module size | [liamwhite](https://github.com/liamwhite/) | Yes |
| [12513](https://github.com/yuzu-emu/yuzu//pull/12513) | [`558192abf`](https://github.com/yuzu-emu/yuzu//pull/12513/files) | jit: use code memory handles correctly | [liamwhite](https://github.com/liamwhite/) | Yes |
| [12518](https://github.com/yuzu-emu/yuzu//pull/12518) | [`aa4d15594`](https://github.com/yuzu-emu/yuzu//pull/12518/files) | android: Migrate remaining settings to ini | [t895](https://github.com/t895/) | Yes |
| [12543](https://github.com/yuzu-emu/yuzu//pull/12543) | [`737e6e531`](https://github.com/yuzu-emu/yuzu//pull/12543/files) | VideoCore: A few fixes to DMA and swapchain | [FernandoS27](https://github.com/FernandoS27/) | Yes |
End of merge log. You can find the original README.md below the break.
-----
<!-- <!--
SPDX-FileCopyrightText: 2018 yuzu Emulator Project SPDX-FileCopyrightText: 2018 yuzu Emulator Project
SPDX-License-Identifier: GPL-2.0-or-later SPDX-License-Identifier: GPL-2.0-or-later

View File

@ -10,7 +10,7 @@ plugins {
id("com.android.application") id("com.android.application")
id("org.jetbrains.kotlin.android") id("org.jetbrains.kotlin.android")
id("kotlin-parcelize") id("kotlin-parcelize")
kotlin("plugin.serialization") version "1.8.21" kotlin("plugin.serialization") version "1.9.20"
id("androidx.navigation.safeargs.kotlin") id("androidx.navigation.safeargs.kotlin")
id("org.jlleitschuh.gradle.ktlint") version "11.4.0" id("org.jlleitschuh.gradle.ktlint") version "11.4.0"
} }

View File

@ -49,6 +49,7 @@ import org.yuzu.yuzu_emu.utils.ForegroundService
import org.yuzu.yuzu_emu.utils.InputHandler import org.yuzu.yuzu_emu.utils.InputHandler
import org.yuzu.yuzu_emu.utils.Log import org.yuzu.yuzu_emu.utils.Log
import org.yuzu.yuzu_emu.utils.MemoryUtil import org.yuzu.yuzu_emu.utils.MemoryUtil
import org.yuzu.yuzu_emu.utils.NativeConfig
import org.yuzu.yuzu_emu.utils.NfcReader import org.yuzu.yuzu_emu.utils.NfcReader
import org.yuzu.yuzu_emu.utils.ThemeHelper import org.yuzu.yuzu_emu.utils.ThemeHelper
import java.text.NumberFormat import java.text.NumberFormat
@ -170,6 +171,11 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
stopMotionSensorListener() stopMotionSensorListener()
} }
override fun onStop() {
super.onStop()
NativeConfig.saveGlobalConfig()
}
override fun onUserLeaveHint() { override fun onUserLeaveHint() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
if (BooleanSetting.PICTURE_IN_PICTURE.getBoolean() && !isInPictureInPictureMode) { if (BooleanSetting.PICTURE_IN_PICTURE.getBoolean() && !isInPictureInPictureMode) {

View File

@ -18,7 +18,14 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
RENDERER_REACTIVE_FLUSHING("use_reactive_flushing"), RENDERER_REACTIVE_FLUSHING("use_reactive_flushing"),
RENDERER_DEBUG("debug"), RENDERER_DEBUG("debug"),
PICTURE_IN_PICTURE("picture_in_picture"), PICTURE_IN_PICTURE("picture_in_picture"),
USE_CUSTOM_RTC("custom_rtc_enabled"); USE_CUSTOM_RTC("custom_rtc_enabled"),
BLACK_BACKGROUNDS("black_backgrounds"),
JOYSTICK_REL_CENTER("joystick_rel_center"),
DPAD_SLIDE("dpad_slide"),
HAPTIC_FEEDBACK("haptic_feedback"),
SHOW_PERFORMANCE_OVERLAY("show_performance_overlay"),
SHOW_INPUT_OVERLAY("show_input_overlay"),
TOUCHSCREEN("touchscreen");
override fun getBoolean(needsGlobal: Boolean): Boolean = override fun getBoolean(needsGlobal: Boolean): Boolean =
NativeConfig.getBoolean(key, needsGlobal) NativeConfig.getBoolean(key, needsGlobal)

View File

@ -19,7 +19,11 @@ enum class IntSetting(override val key: String) : AbstractIntSetting {
RENDERER_SCREEN_LAYOUT("screen_layout"), RENDERER_SCREEN_LAYOUT("screen_layout"),
RENDERER_ASPECT_RATIO("aspect_ratio"), RENDERER_ASPECT_RATIO("aspect_ratio"),
AUDIO_OUTPUT_ENGINE("output_engine"), AUDIO_OUTPUT_ENGINE("output_engine"),
MAX_ANISOTROPY("max_anisotropy"); MAX_ANISOTROPY("max_anisotropy"),
THEME("theme"),
THEME_MODE("theme_mode"),
OVERLAY_SCALE("control_scale"),
OVERLAY_OPACITY("control_opacity");
override fun getInt(needsGlobal: Boolean): Int = NativeConfig.getInt(key, needsGlobal) override fun getInt(needsGlobal: Boolean): Int = NativeConfig.getInt(key, needsGlobal)

View File

@ -15,18 +15,10 @@ object Settings {
SECTION_DEBUG(R.string.preferences_debug); SECTION_DEBUG(R.string.preferences_debug);
} }
const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch"
const val PREF_MEMORY_WARNING_SHOWN = "MemoryWarningShown" const val PREF_MEMORY_WARNING_SHOWN = "MemoryWarningShown"
const val PREF_OVERLAY_VERSION = "OverlayVersion" // Deprecated input overlay preference keys
const val PREF_LANDSCAPE_OVERLAY_VERSION = "LandscapeOverlayVersion"
const val PREF_PORTRAIT_OVERLAY_VERSION = "PortraitOverlayVersion"
const val PREF_FOLDABLE_OVERLAY_VERSION = "FoldableOverlayVersion"
val overlayLayoutPrefs = listOf(
PREF_LANDSCAPE_OVERLAY_VERSION,
PREF_PORTRAIT_OVERLAY_VERSION,
PREF_FOLDABLE_OVERLAY_VERSION
)
const val PREF_CONTROL_SCALE = "controlScale" const val PREF_CONTROL_SCALE = "controlScale"
const val PREF_CONTROL_OPACITY = "controlOpacity" const val PREF_CONTROL_OPACITY = "controlOpacity"
const val PREF_TOUCH_ENABLED = "isTouchEnabled" const val PREF_TOUCH_ENABLED = "isTouchEnabled"
@ -47,23 +39,12 @@ object Settings {
const val PREF_BUTTON_STICK_R = "buttonToggle14" const val PREF_BUTTON_STICK_R = "buttonToggle14"
const val PREF_BUTTON_HOME = "buttonToggle15" const val PREF_BUTTON_HOME = "buttonToggle15"
const val PREF_BUTTON_SCREENSHOT = "buttonToggle16" const val PREF_BUTTON_SCREENSHOT = "buttonToggle16"
const val PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER = "EmulationMenuSettings_JoystickRelCenter" const val PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER = "EmulationMenuSettings_JoystickRelCenter"
const val PREF_MENU_SETTINGS_DPAD_SLIDE = "EmulationMenuSettings_DpadSlideEnable" const val PREF_MENU_SETTINGS_DPAD_SLIDE = "EmulationMenuSettings_DpadSlideEnable"
const val PREF_MENU_SETTINGS_HAPTICS = "EmulationMenuSettings_Haptics" const val PREF_MENU_SETTINGS_HAPTICS = "EmulationMenuSettings_Haptics"
const val PREF_MENU_SETTINGS_SHOW_FPS = "EmulationMenuSettings_ShowFps" const val PREF_MENU_SETTINGS_SHOW_FPS = "EmulationMenuSettings_ShowFps"
const val PREF_MENU_SETTINGS_SHOW_OVERLAY = "EmulationMenuSettings_ShowOverlay" const val PREF_MENU_SETTINGS_SHOW_OVERLAY = "EmulationMenuSettings_ShowOverlay"
const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch"
const val PREF_THEME = "Theme"
const val PREF_THEME_MODE = "ThemeMode"
const val PREF_BLACK_BACKGROUNDS = "BlackBackgrounds"
val overlayPreferences = listOf( val overlayPreferences = listOf(
PREF_OVERLAY_VERSION,
PREF_CONTROL_SCALE,
PREF_CONTROL_OPACITY,
PREF_TOUCH_ENABLED,
PREF_BUTTON_A, PREF_BUTTON_A,
PREF_BUTTON_B, PREF_BUTTON_B,
PREF_BUTTON_X, PREF_BUTTON_X,
@ -83,6 +64,21 @@ object Settings {
PREF_BUTTON_STICK_R PREF_BUTTON_STICK_R
) )
// Deprecated layout preference keys
const val PREF_LANDSCAPE_SUFFIX = "_Landscape"
const val PREF_PORTRAIT_SUFFIX = "_Portrait"
const val PREF_FOLDABLE_SUFFIX = "_Foldable"
val overlayLayoutSuffixes = listOf(
PREF_LANDSCAPE_SUFFIX,
PREF_PORTRAIT_SUFFIX,
PREF_FOLDABLE_SUFFIX
)
// Deprecated theme preference keys
const val PREF_THEME = "Theme"
const val PREF_THEME_MODE = "ThemeMode"
const val PREF_BLACK_BACKGROUNDS = "BlackBackgrounds"
const val LayoutOption_Unspecified = 0 const val LayoutOption_Unspecified = 0
const val LayoutOption_MobilePortrait = 4 const val LayoutOption_MobilePortrait = 4
const val LayoutOption_MobileLandscape = 5 const val LayoutOption_MobileLandscape = 5

View File

@ -3,10 +3,8 @@
package org.yuzu.yuzu_emu.features.settings.ui package org.yuzu.yuzu_emu.features.settings.ui
import android.content.SharedPreferences
import android.os.Build import android.os.Build
import android.widget.Toast import android.widget.Toast
import androidx.preference.PreferenceManager
import org.yuzu.yuzu_emu.NativeLibrary import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication import org.yuzu.yuzu_emu.YuzuApplication
@ -29,9 +27,6 @@ class SettingsFragmentPresenter(
) { ) {
private var settingsList = ArrayList<SettingsItem>() private var settingsList = ArrayList<SettingsItem>()
private val preferences: SharedPreferences
get() = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
// Extension for altering settings list based on each setting's properties // Extension for altering settings list based on each setting's properties
fun ArrayList<SettingsItem>.add(key: String) { fun ArrayList<SettingsItem>.add(key: String) {
val item = SettingsItem.settingsItems[key]!! val item = SettingsItem.settingsItems[key]!!
@ -170,25 +165,19 @@ class SettingsFragmentPresenter(
private fun addThemeSettings(sl: ArrayList<SettingsItem>) { private fun addThemeSettings(sl: ArrayList<SettingsItem>) {
sl.apply { sl.apply {
val theme: AbstractIntSetting = object : AbstractIntSetting { val theme: AbstractIntSetting = object : AbstractIntSetting {
override fun getInt(needsGlobal: Boolean): Int = override fun getInt(needsGlobal: Boolean): Int = IntSetting.THEME.getInt()
preferences.getInt(Settings.PREF_THEME, 0)
override fun setInt(value: Int) { override fun setInt(value: Int) {
preferences.edit() IntSetting.THEME.setInt(value)
.putInt(Settings.PREF_THEME, value)
.apply()
settingsViewModel.setShouldRecreate(true) settingsViewModel.setShouldRecreate(true)
} }
override val key: String = Settings.PREF_THEME override val key: String = IntSetting.THEME.key
override val isRuntimeModifiable: Boolean = false override val isRuntimeModifiable: Boolean = IntSetting.THEME.isRuntimeModifiable
override fun getValueAsString(needsGlobal: Boolean): String = getInt().toString() override fun getValueAsString(needsGlobal: Boolean): String =
override val defaultValue: Int = 0 IntSetting.THEME.getValueAsString()
override fun reset() {
preferences.edit() override val defaultValue: Int = IntSetting.THEME.defaultValue
.putInt(Settings.PREF_THEME, defaultValue) override fun reset() = IntSetting.THEME.setInt(defaultValue)
.apply()
}
} }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
@ -214,24 +203,22 @@ class SettingsFragmentPresenter(
} }
val themeMode: AbstractIntSetting = object : AbstractIntSetting { val themeMode: AbstractIntSetting = object : AbstractIntSetting {
override fun getInt(needsGlobal: Boolean): Int = override fun getInt(needsGlobal: Boolean): Int = IntSetting.THEME_MODE.getInt()
preferences.getInt(Settings.PREF_THEME_MODE, -1)
override fun setInt(value: Int) { override fun setInt(value: Int) {
preferences.edit() IntSetting.THEME_MODE.setInt(value)
.putInt(Settings.PREF_THEME_MODE, value)
.apply()
settingsViewModel.setShouldRecreate(true) settingsViewModel.setShouldRecreate(true)
} }
override val key: String = Settings.PREF_THEME_MODE override val key: String = IntSetting.THEME_MODE.key
override val isRuntimeModifiable: Boolean = false override val isRuntimeModifiable: Boolean =
override fun getValueAsString(needsGlobal: Boolean): String = getInt().toString() IntSetting.THEME_MODE.isRuntimeModifiable
override val defaultValue: Int = -1
override fun getValueAsString(needsGlobal: Boolean): String =
IntSetting.THEME_MODE.getValueAsString()
override val defaultValue: Int = IntSetting.THEME_MODE.defaultValue
override fun reset() { override fun reset() {
preferences.edit() IntSetting.THEME_MODE.setInt(defaultValue)
.putInt(Settings.PREF_BLACK_BACKGROUNDS, defaultValue)
.apply()
settingsViewModel.setShouldRecreate(true) settingsViewModel.setShouldRecreate(true)
} }
} }
@ -248,25 +235,24 @@ class SettingsFragmentPresenter(
val blackBackgrounds: AbstractBooleanSetting = object : AbstractBooleanSetting { val blackBackgrounds: AbstractBooleanSetting = object : AbstractBooleanSetting {
override fun getBoolean(needsGlobal: Boolean): Boolean = override fun getBoolean(needsGlobal: Boolean): Boolean =
preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false) BooleanSetting.BLACK_BACKGROUNDS.getBoolean()
override fun setBoolean(value: Boolean) { override fun setBoolean(value: Boolean) {
preferences.edit() BooleanSetting.BLACK_BACKGROUNDS.setBoolean(value)
.putBoolean(Settings.PREF_BLACK_BACKGROUNDS, value)
.apply()
settingsViewModel.setShouldRecreate(true) settingsViewModel.setShouldRecreate(true)
} }
override val key: String = Settings.PREF_BLACK_BACKGROUNDS override val key: String = BooleanSetting.BLACK_BACKGROUNDS.key
override val isRuntimeModifiable: Boolean = false override val isRuntimeModifiable: Boolean =
override fun getValueAsString(needsGlobal: Boolean): String = BooleanSetting.BLACK_BACKGROUNDS.isRuntimeModifiable
getBoolean().toString()
override val defaultValue: Boolean = false override fun getValueAsString(needsGlobal: Boolean): String =
BooleanSetting.BLACK_BACKGROUNDS.getValueAsString()
override val defaultValue: Boolean = BooleanSetting.BLACK_BACKGROUNDS.defaultValue
override fun reset() { override fun reset() {
preferences.edit() BooleanSetting.BLACK_BACKGROUNDS
.putBoolean(Settings.PREF_BLACK_BACKGROUNDS, defaultValue) .setBoolean(BooleanSetting.BLACK_BACKGROUNDS.defaultValue)
.apply()
settingsViewModel.setShouldRecreate(true) settingsViewModel.setShouldRecreate(true)
} }
} }

View File

@ -7,7 +7,6 @@ import android.annotation.SuppressLint
import android.app.AlertDialog import android.app.AlertDialog
import android.content.Context import android.content.Context
import android.content.DialogInterface import android.content.DialogInterface
import android.content.SharedPreferences
import android.content.pm.ActivityInfo import android.content.pm.ActivityInfo
import android.content.res.Configuration import android.content.res.Configuration
import android.net.Uri import android.net.Uri
@ -33,7 +32,6 @@ import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.findNavController import androidx.navigation.findNavController
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
import androidx.preference.PreferenceManager
import androidx.window.layout.FoldingFeature import androidx.window.layout.FoldingFeature
import androidx.window.layout.WindowInfoTracker import androidx.window.layout.WindowInfoTracker
import androidx.window.layout.WindowLayoutInfo import androidx.window.layout.WindowLayoutInfo
@ -46,22 +44,22 @@ import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.HomeNavigationDirections import org.yuzu.yuzu_emu.HomeNavigationDirections
import org.yuzu.yuzu_emu.NativeLibrary import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.activities.EmulationActivity import org.yuzu.yuzu_emu.activities.EmulationActivity
import org.yuzu.yuzu_emu.databinding.DialogOverlayAdjustBinding import org.yuzu.yuzu_emu.databinding.DialogOverlayAdjustBinding
import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
import org.yuzu.yuzu_emu.features.settings.model.IntSetting import org.yuzu.yuzu_emu.features.settings.model.IntSetting
import org.yuzu.yuzu_emu.features.settings.model.Settings import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
import org.yuzu.yuzu_emu.model.DriverViewModel import org.yuzu.yuzu_emu.model.DriverViewModel
import org.yuzu.yuzu_emu.model.Game import org.yuzu.yuzu_emu.model.Game
import org.yuzu.yuzu_emu.model.EmulationViewModel import org.yuzu.yuzu_emu.model.EmulationViewModel
import org.yuzu.yuzu_emu.overlay.InputOverlay import org.yuzu.yuzu_emu.overlay.model.OverlayControl
import org.yuzu.yuzu_emu.overlay.model.OverlayLayout
import org.yuzu.yuzu_emu.utils.* import org.yuzu.yuzu_emu.utils.*
import java.lang.NullPointerException import java.lang.NullPointerException
class EmulationFragment : Fragment(), SurfaceHolder.Callback { class EmulationFragment : Fragment(), SurfaceHolder.Callback {
private lateinit var preferences: SharedPreferences
private lateinit var emulationState: EmulationState private lateinit var emulationState: EmulationState
private var emulationActivity: EmulationActivity? = null private var emulationActivity: EmulationActivity? = null
private var perfStatsUpdater: (() -> Unit)? = null private var perfStatsUpdater: (() -> Unit)? = null
@ -141,7 +139,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
// So this fragment doesn't restart on configuration changes; i.e. rotation. // So this fragment doesn't restart on configuration changes; i.e. rotation.
retainInstance = true retainInstance = true
preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
emulationState = EmulationState(game.path) emulationState = EmulationState(game.path)
} }
@ -382,24 +379,25 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
} }
updateScreenLayout() updateScreenLayout()
val showInputOverlay = BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean()
if (emulationActivity?.isInPictureInPictureMode == true) { if (emulationActivity?.isInPictureInPictureMode == true) {
if (binding.drawerLayout.isOpen) { if (binding.drawerLayout.isOpen) {
binding.drawerLayout.close() binding.drawerLayout.close()
} }
if (EmulationMenuSettings.showOverlay) { if (showInputOverlay) {
binding.surfaceInputOverlay.visibility = View.INVISIBLE binding.surfaceInputOverlay.visibility = View.INVISIBLE
} }
} else { } else {
if (EmulationMenuSettings.showOverlay && emulationViewModel.emulationStarted.value) { if (showInputOverlay && emulationViewModel.emulationStarted.value) {
binding.surfaceInputOverlay.visibility = View.VISIBLE binding.surfaceInputOverlay.visibility = View.VISIBLE
} else { } else {
binding.surfaceInputOverlay.visibility = View.INVISIBLE binding.surfaceInputOverlay.visibility = View.INVISIBLE
} }
if (!isInFoldableLayout) { if (!isInFoldableLayout) {
if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) { if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
binding.surfaceInputOverlay.layout = InputOverlay.PORTRAIT binding.surfaceInputOverlay.layout = OverlayLayout.Portrait
} else { } else {
binding.surfaceInputOverlay.layout = InputOverlay.LANDSCAPE binding.surfaceInputOverlay.layout = OverlayLayout.Landscape
} }
} }
} }
@ -423,17 +421,15 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
} }
private fun resetInputOverlay() { private fun resetInputOverlay() {
preferences.edit() IntSetting.OVERLAY_SCALE.reset()
.remove(Settings.PREF_CONTROL_SCALE) IntSetting.OVERLAY_OPACITY.reset()
.remove(Settings.PREF_CONTROL_OPACITY)
.apply()
binding.surfaceInputOverlay.post { binding.surfaceInputOverlay.post {
binding.surfaceInputOverlay.resetLayoutVisibilityAndPlacement() binding.surfaceInputOverlay.resetLayoutVisibilityAndPlacement()
} }
} }
private fun updateShowFpsOverlay() { private fun updateShowFpsOverlay() {
if (EmulationMenuSettings.showFps) { if (BooleanSetting.SHOW_PERFORMANCE_OVERLAY.getBoolean()) {
val SYSTEM_FPS = 0 val SYSTEM_FPS = 0
val FPS = 1 val FPS = 1
val FRAMETIME = 2 val FRAMETIME = 2
@ -496,7 +492,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
binding.inGameMenu.layoutParams.height = it.bounds.bottom binding.inGameMenu.layoutParams.height = it.bounds.bottom
isInFoldableLayout = true isInFoldableLayout = true
binding.surfaceInputOverlay.layout = InputOverlay.FOLDABLE binding.surfaceInputOverlay.layout = OverlayLayout.Foldable
} }
} }
it.isSeparating it.isSeparating
@ -535,18 +531,22 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
popup.menuInflater.inflate(R.menu.menu_overlay_options, popup.menu) popup.menuInflater.inflate(R.menu.menu_overlay_options, popup.menu)
popup.menu.apply { popup.menu.apply {
findItem(R.id.menu_toggle_fps).isChecked = EmulationMenuSettings.showFps findItem(R.id.menu_toggle_fps).isChecked =
findItem(R.id.menu_rel_stick_center).isChecked = EmulationMenuSettings.joystickRelCenter BooleanSetting.SHOW_PERFORMANCE_OVERLAY.getBoolean()
findItem(R.id.menu_dpad_slide).isChecked = EmulationMenuSettings.dpadSlide findItem(R.id.menu_rel_stick_center).isChecked =
findItem(R.id.menu_show_overlay).isChecked = EmulationMenuSettings.showOverlay BooleanSetting.JOYSTICK_REL_CENTER.getBoolean()
findItem(R.id.menu_haptics).isChecked = EmulationMenuSettings.hapticFeedback findItem(R.id.menu_dpad_slide).isChecked = BooleanSetting.DPAD_SLIDE.getBoolean()
findItem(R.id.menu_show_overlay).isChecked =
BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean()
findItem(R.id.menu_haptics).isChecked = BooleanSetting.HAPTIC_FEEDBACK.getBoolean()
findItem(R.id.menu_touchscreen).isChecked = BooleanSetting.TOUCHSCREEN.getBoolean()
} }
popup.setOnMenuItemClickListener { popup.setOnMenuItemClickListener {
when (it.itemId) { when (it.itemId) {
R.id.menu_toggle_fps -> { R.id.menu_toggle_fps -> {
it.isChecked = !it.isChecked it.isChecked = !it.isChecked
EmulationMenuSettings.showFps = it.isChecked BooleanSetting.SHOW_PERFORMANCE_OVERLAY.setBoolean(it.isChecked)
updateShowFpsOverlay() updateShowFpsOverlay()
true true
} }
@ -564,11 +564,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
} }
R.id.menu_toggle_controls -> { R.id.menu_toggle_controls -> {
val preferences = val overlayControlData = NativeConfig.getOverlayControlData()
PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) val optionsArray = BooleanArray(overlayControlData.size)
val optionsArray = BooleanArray(Settings.overlayPreferences.size) overlayControlData.forEachIndexed { i, _ ->
Settings.overlayPreferences.forEachIndexed { i, _ -> optionsArray[i] = overlayControlData.firstOrNull { data ->
optionsArray[i] = preferences.getBoolean("buttonToggle$i", i < 15) OverlayControl.entries[i].id == data.id
}?.enabled == true
} }
val dialog = MaterialAlertDialogBuilder(requireContext()) val dialog = MaterialAlertDialogBuilder(requireContext())
@ -577,11 +578,13 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
R.array.gamepadButtons, R.array.gamepadButtons,
optionsArray optionsArray
) { _, indexSelected, isChecked -> ) { _, indexSelected, isChecked ->
preferences.edit() overlayControlData.firstOrNull { data ->
.putBoolean("buttonToggle$indexSelected", isChecked) OverlayControl.entries[indexSelected].id == data.id
.apply() }?.enabled = isChecked
} }
.setPositiveButton(android.R.string.ok) { _, _ -> .setPositiveButton(android.R.string.ok) { _, _ ->
NativeConfig.setOverlayControlData(overlayControlData)
NativeConfig.saveGlobalConfig()
binding.surfaceInputOverlay.refreshControls() binding.surfaceInputOverlay.refreshControls()
} }
.setNegativeButton(android.R.string.cancel, null) .setNegativeButton(android.R.string.cancel, null)
@ -592,12 +595,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
dialog.getButton(AlertDialog.BUTTON_NEUTRAL) dialog.getButton(AlertDialog.BUTTON_NEUTRAL)
.setOnClickListener { .setOnClickListener {
val isChecked = !optionsArray[0] val isChecked = !optionsArray[0]
Settings.overlayPreferences.forEachIndexed { i, _ -> overlayControlData.forEachIndexed { i, _ ->
optionsArray[i] = isChecked optionsArray[i] = isChecked
dialog.listView.setItemChecked(i, isChecked) dialog.listView.setItemChecked(i, isChecked)
preferences.edit() overlayControlData[i].enabled = isChecked
.putBoolean("buttonToggle$i", isChecked)
.apply()
} }
} }
true true
@ -605,26 +606,32 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
R.id.menu_show_overlay -> { R.id.menu_show_overlay -> {
it.isChecked = !it.isChecked it.isChecked = !it.isChecked
EmulationMenuSettings.showOverlay = it.isChecked BooleanSetting.SHOW_INPUT_OVERLAY.setBoolean(it.isChecked)
binding.surfaceInputOverlay.refreshControls() binding.surfaceInputOverlay.refreshControls()
true true
} }
R.id.menu_rel_stick_center -> { R.id.menu_rel_stick_center -> {
it.isChecked = !it.isChecked it.isChecked = !it.isChecked
EmulationMenuSettings.joystickRelCenter = it.isChecked BooleanSetting.JOYSTICK_REL_CENTER.setBoolean(it.isChecked)
true true
} }
R.id.menu_dpad_slide -> { R.id.menu_dpad_slide -> {
it.isChecked = !it.isChecked it.isChecked = !it.isChecked
EmulationMenuSettings.dpadSlide = it.isChecked BooleanSetting.DPAD_SLIDE.setBoolean(it.isChecked)
true true
} }
R.id.menu_haptics -> { R.id.menu_haptics -> {
it.isChecked = !it.isChecked it.isChecked = !it.isChecked
EmulationMenuSettings.hapticFeedback = it.isChecked BooleanSetting.HAPTIC_FEEDBACK.setBoolean(it.isChecked)
true
}
R.id.menu_touchscreen -> {
it.isChecked = !it.isChecked
BooleanSetting.TOUCHSCREEN.setBoolean(it.isChecked)
true true
} }
@ -667,6 +674,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
it.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED it.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
} }
} }
NativeConfig.saveGlobalConfig()
} }
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
@ -675,7 +683,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
adjustBinding.apply { adjustBinding.apply {
inputScaleSlider.apply { inputScaleSlider.apply {
valueTo = 150F valueTo = 150F
value = preferences.getInt(Settings.PREF_CONTROL_SCALE, 50).toFloat() value = IntSetting.OVERLAY_SCALE.getInt().toFloat()
addOnChangeListener( addOnChangeListener(
Slider.OnChangeListener { _, value, _ -> Slider.OnChangeListener { _, value, _ ->
inputScaleValue.text = "${value.toInt()}%" inputScaleValue.text = "${value.toInt()}%"
@ -685,7 +693,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
} }
inputOpacitySlider.apply { inputOpacitySlider.apply {
valueTo = 100F valueTo = 100F
value = preferences.getInt(Settings.PREF_CONTROL_OPACITY, 100).toFloat() value = IntSetting.OVERLAY_OPACITY.getInt().toFloat()
addOnChangeListener( addOnChangeListener(
Slider.OnChangeListener { _, value, _ -> Slider.OnChangeListener { _, value, _ ->
inputOpacityValue.text = "${value.toInt()}%" inputOpacityValue.text = "${value.toInt()}%"
@ -709,16 +717,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
} }
private fun setControlScale(scale: Int) { private fun setControlScale(scale: Int) {
preferences.edit() IntSetting.OVERLAY_SCALE.setInt(scale)
.putInt(Settings.PREF_CONTROL_SCALE, scale)
.apply()
binding.surfaceInputOverlay.refreshControls() binding.surfaceInputOverlay.refreshControls()
} }
private fun setControlOpacity(opacity: Int) { private fun setControlOpacity(opacity: Int) {
preferences.edit() IntSetting.OVERLAY_OPACITY.setInt(opacity)
.putInt(Settings.PREF_CONTROL_OPACITY, opacity)
.apply()
binding.surfaceInputOverlay.refreshControls() binding.surfaceInputOverlay.refreshControls()
} }

View File

@ -10,6 +10,7 @@ import android.graphics.Rect
import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.BitmapDrawable
import android.view.MotionEvent import android.view.MotionEvent
import org.yuzu.yuzu_emu.NativeLibrary.ButtonState import org.yuzu.yuzu_emu.NativeLibrary.ButtonState
import org.yuzu.yuzu_emu.overlay.model.OverlayControlData
/** /**
* Custom [BitmapDrawable] that is capable * Custom [BitmapDrawable] that is capable
@ -25,7 +26,7 @@ class InputOverlayDrawableButton(
defaultStateBitmap: Bitmap, defaultStateBitmap: Bitmap,
pressedStateBitmap: Bitmap, pressedStateBitmap: Bitmap,
val buttonId: Int, val buttonId: Int,
val prefId: String val overlayControlData: OverlayControlData
) { ) {
// The ID value what motion event is tracking // The ID value what motion event is tracking
var trackId: Int var trackId: Int

View File

@ -14,7 +14,7 @@ import kotlin.math.cos
import kotlin.math.sin import kotlin.math.sin
import kotlin.math.sqrt import kotlin.math.sqrt
import org.yuzu.yuzu_emu.NativeLibrary import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.utils.EmulationMenuSettings import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
/** /**
* Custom [BitmapDrawable] that is capable * Custom [BitmapDrawable] that is capable
@ -125,7 +125,7 @@ class InputOverlayDrawableJoystick(
pressedState = true pressedState = true
outerBitmap.alpha = 0 outerBitmap.alpha = 0
boundsBoxBitmap.alpha = opacity boundsBoxBitmap.alpha = opacity
if (EmulationMenuSettings.joystickRelCenter) { if (BooleanSetting.JOYSTICK_REL_CENTER.getBoolean()) {
virtBounds.offset( virtBounds.offset(
xPosition - virtBounds.centerX(), xPosition - virtBounds.centerX(),
yPosition - virtBounds.centerY() yPosition - virtBounds.centerY()

View File

@ -0,0 +1,188 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.overlay.model
import androidx.annotation.IntegerRes
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
enum class OverlayControl(
val id: String,
val defaultVisibility: Boolean,
@IntegerRes val defaultLandscapePositionResources: Pair<Int, Int>,
@IntegerRes val defaultPortraitPositionResources: Pair<Int, Int>,
@IntegerRes val defaultFoldablePositionResources: Pair<Int, Int>
) {
BUTTON_A(
"button_a",
true,
Pair(R.integer.BUTTON_A_X, R.integer.BUTTON_A_Y),
Pair(R.integer.BUTTON_A_X_PORTRAIT, R.integer.BUTTON_A_Y_PORTRAIT),
Pair(R.integer.BUTTON_A_X_FOLDABLE, R.integer.BUTTON_A_Y_FOLDABLE)
),
BUTTON_B(
"button_b",
true,
Pair(R.integer.BUTTON_B_X, R.integer.BUTTON_B_Y),
Pair(R.integer.BUTTON_B_X_PORTRAIT, R.integer.BUTTON_B_Y_PORTRAIT),
Pair(R.integer.BUTTON_B_X_FOLDABLE, R.integer.BUTTON_B_Y_FOLDABLE)
),
BUTTON_X(
"button_x",
true,
Pair(R.integer.BUTTON_X_X, R.integer.BUTTON_X_Y),
Pair(R.integer.BUTTON_X_X_PORTRAIT, R.integer.BUTTON_X_Y_PORTRAIT),
Pair(R.integer.BUTTON_X_X_FOLDABLE, R.integer.BUTTON_X_Y_FOLDABLE)
),
BUTTON_Y(
"button_y",
true,
Pair(R.integer.BUTTON_Y_X, R.integer.BUTTON_Y_Y),
Pair(R.integer.BUTTON_Y_X_PORTRAIT, R.integer.BUTTON_Y_Y_PORTRAIT),
Pair(R.integer.BUTTON_Y_X_FOLDABLE, R.integer.BUTTON_Y_Y_FOLDABLE)
),
BUTTON_PLUS(
"button_plus",
true,
Pair(R.integer.BUTTON_PLUS_X, R.integer.BUTTON_PLUS_Y),
Pair(R.integer.BUTTON_PLUS_X_PORTRAIT, R.integer.BUTTON_PLUS_Y_PORTRAIT),
Pair(R.integer.BUTTON_PLUS_X_FOLDABLE, R.integer.BUTTON_PLUS_Y_FOLDABLE)
),
BUTTON_MINUS(
"button_minus",
true,
Pair(R.integer.BUTTON_MINUS_X, R.integer.BUTTON_MINUS_Y),
Pair(R.integer.BUTTON_MINUS_X_PORTRAIT, R.integer.BUTTON_MINUS_Y_PORTRAIT),
Pair(R.integer.BUTTON_MINUS_X_FOLDABLE, R.integer.BUTTON_MINUS_Y_FOLDABLE)
),
BUTTON_HOME(
"button_home",
false,
Pair(R.integer.BUTTON_HOME_X, R.integer.BUTTON_HOME_Y),
Pair(R.integer.BUTTON_HOME_X_PORTRAIT, R.integer.BUTTON_HOME_Y_PORTRAIT),
Pair(R.integer.BUTTON_HOME_X_FOLDABLE, R.integer.BUTTON_HOME_Y_FOLDABLE)
),
BUTTON_CAPTURE(
"button_capture",
false,
Pair(R.integer.BUTTON_CAPTURE_X, R.integer.BUTTON_CAPTURE_Y),
Pair(R.integer.BUTTON_CAPTURE_X_PORTRAIT, R.integer.BUTTON_CAPTURE_Y_PORTRAIT),
Pair(R.integer.BUTTON_CAPTURE_X_FOLDABLE, R.integer.BUTTON_CAPTURE_Y_FOLDABLE)
),
BUTTON_L(
"button_l",
true,
Pair(R.integer.BUTTON_L_X, R.integer.BUTTON_L_Y),
Pair(R.integer.BUTTON_L_X_PORTRAIT, R.integer.BUTTON_L_Y_PORTRAIT),
Pair(R.integer.BUTTON_L_X_FOLDABLE, R.integer.BUTTON_L_Y_FOLDABLE)
),
BUTTON_R(
"button_r",
true,
Pair(R.integer.BUTTON_R_X, R.integer.BUTTON_R_Y),
Pair(R.integer.BUTTON_R_X_PORTRAIT, R.integer.BUTTON_R_Y_PORTRAIT),
Pair(R.integer.BUTTON_R_X_FOLDABLE, R.integer.BUTTON_R_Y_FOLDABLE)
),
BUTTON_ZL(
"button_zl",
true,
Pair(R.integer.BUTTON_ZL_X, R.integer.BUTTON_ZL_Y),
Pair(R.integer.BUTTON_ZL_X_PORTRAIT, R.integer.BUTTON_ZL_Y_PORTRAIT),
Pair(R.integer.BUTTON_ZL_X_FOLDABLE, R.integer.BUTTON_ZL_Y_FOLDABLE)
),
BUTTON_ZR(
"button_zr",
true,
Pair(R.integer.BUTTON_ZR_X, R.integer.BUTTON_ZR_Y),
Pair(R.integer.BUTTON_ZR_X_PORTRAIT, R.integer.BUTTON_ZR_Y_PORTRAIT),
Pair(R.integer.BUTTON_ZR_X_FOLDABLE, R.integer.BUTTON_ZR_Y_FOLDABLE)
),
BUTTON_STICK_L(
"button_stick_l",
true,
Pair(R.integer.BUTTON_STICK_L_X, R.integer.BUTTON_STICK_L_Y),
Pair(R.integer.BUTTON_STICK_L_X_PORTRAIT, R.integer.BUTTON_STICK_L_Y_PORTRAIT),
Pair(R.integer.BUTTON_STICK_L_X_FOLDABLE, R.integer.BUTTON_STICK_L_Y_FOLDABLE)
),
BUTTON_STICK_R(
"button_stick_r",
true,
Pair(R.integer.BUTTON_STICK_R_X, R.integer.BUTTON_STICK_R_Y),
Pair(R.integer.BUTTON_STICK_R_X_PORTRAIT, R.integer.BUTTON_STICK_R_Y_PORTRAIT),
Pair(R.integer.BUTTON_STICK_R_X_FOLDABLE, R.integer.BUTTON_STICK_R_Y_FOLDABLE)
),
STICK_L(
"stick_l",
true,
Pair(R.integer.STICK_L_X, R.integer.STICK_L_Y),
Pair(R.integer.STICK_L_X_PORTRAIT, R.integer.STICK_L_Y_PORTRAIT),
Pair(R.integer.STICK_L_X_FOLDABLE, R.integer.STICK_L_Y_FOLDABLE)
),
STICK_R(
"stick_r",
true,
Pair(R.integer.STICK_R_X, R.integer.STICK_R_Y),
Pair(R.integer.STICK_R_X_PORTRAIT, R.integer.STICK_R_Y_PORTRAIT),
Pair(R.integer.STICK_R_X_FOLDABLE, R.integer.STICK_R_Y_FOLDABLE)
),
COMBINED_DPAD(
"combined_dpad",
true,
Pair(R.integer.COMBINED_DPAD_X, R.integer.COMBINED_DPAD_Y),
Pair(R.integer.COMBINED_DPAD_X_PORTRAIT, R.integer.COMBINED_DPAD_Y_PORTRAIT),
Pair(R.integer.COMBINED_DPAD_X_FOLDABLE, R.integer.COMBINED_DPAD_Y_FOLDABLE)
);
fun getDefaultPositionForLayout(layout: OverlayLayout): Pair<Double, Double> {
val rawResourcePair: Pair<Int, Int>
YuzuApplication.appContext.resources.apply {
rawResourcePair = when (layout) {
OverlayLayout.Landscape -> {
Pair(
getInteger(this@OverlayControl.defaultLandscapePositionResources.first),
getInteger(this@OverlayControl.defaultLandscapePositionResources.second)
)
}
OverlayLayout.Portrait -> {
Pair(
getInteger(this@OverlayControl.defaultPortraitPositionResources.first),
getInteger(this@OverlayControl.defaultPortraitPositionResources.second)
)
}
OverlayLayout.Foldable -> {
Pair(
getInteger(this@OverlayControl.defaultFoldablePositionResources.first),
getInteger(this@OverlayControl.defaultFoldablePositionResources.second)
)
}
}
}
return Pair(
rawResourcePair.first.toDouble() / 1000,
rawResourcePair.second.toDouble() / 1000
)
}
fun toOverlayControlData(): OverlayControlData =
OverlayControlData(
id,
defaultVisibility,
getDefaultPositionForLayout(OverlayLayout.Landscape),
getDefaultPositionForLayout(OverlayLayout.Portrait),
getDefaultPositionForLayout(OverlayLayout.Foldable)
)
companion object {
val map: HashMap<String, OverlayControl> by lazy {
val hashMap = hashMapOf<String, OverlayControl>()
entries.forEach { hashMap[it.id] = it }
hashMap
}
fun from(id: String): OverlayControl? = map[id]
}
}

View File

@ -0,0 +1,19 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.overlay.model
data class OverlayControlData(
val id: String,
var enabled: Boolean,
var landscapePosition: Pair<Double, Double>,
var portraitPosition: Pair<Double, Double>,
var foldablePosition: Pair<Double, Double>
) {
fun positionFromLayout(layout: OverlayLayout): Pair<Double, Double> =
when (layout) {
OverlayLayout.Landscape -> landscapePosition
OverlayLayout.Portrait -> portraitPosition
OverlayLayout.Foldable -> foldablePosition
}
}

View File

@ -0,0 +1,13 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.overlay.model
import androidx.annotation.IntegerRes
data class OverlayControlDefault(
val buttonId: String,
@IntegerRes val landscapePositionResource: Pair<Int, Int>,
@IntegerRes val portraitPositionResource: Pair<Int, Int>,
@IntegerRes val foldablePositionResource: Pair<Int, Int>
)

View File

@ -0,0 +1,10 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.overlay.model
enum class OverlayLayout(val id: String) {
Landscape("Landscape"),
Portrait("Portrait"),
Foldable("Foldable")
}

View File

@ -3,9 +3,17 @@
package org.yuzu.yuzu_emu.utils package org.yuzu.yuzu_emu.utils
import androidx.preference.PreferenceManager
import java.io.IOException import java.io.IOException
import org.yuzu.yuzu_emu.NativeLibrary import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.YuzuApplication import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.overlay.model.OverlayControlData
import org.yuzu.yuzu_emu.overlay.model.OverlayControl
import org.yuzu.yuzu_emu.overlay.model.OverlayLayout
import org.yuzu.yuzu_emu.utils.PreferenceUtil.migratePreference
object DirectoryInitialization { object DirectoryInitialization {
private var userPath: String? = null private var userPath: String? = null
@ -17,6 +25,7 @@ object DirectoryInitialization {
initializeInternalStorage() initializeInternalStorage()
NativeLibrary.initializeSystem(false) NativeLibrary.initializeSystem(false)
NativeConfig.initializeGlobalConfig() NativeConfig.initializeGlobalConfig()
migrateSettings()
areDirectoriesReady = true areDirectoriesReady = true
} }
} }
@ -35,4 +44,170 @@ object DirectoryInitialization {
e.printStackTrace() e.printStackTrace()
} }
} }
private fun migrateSettings() {
val preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
var saveConfig = false
val theme = preferences.migratePreference<Int>(Settings.PREF_THEME)
if (theme != null) {
IntSetting.THEME.setInt(theme)
saveConfig = true
}
val themeMode = preferences.migratePreference<Int>(Settings.PREF_THEME_MODE)
if (themeMode != null) {
IntSetting.THEME_MODE.setInt(themeMode)
saveConfig = true
}
val blackBackgrounds =
preferences.migratePreference<Boolean>(Settings.PREF_BLACK_BACKGROUNDS)
if (blackBackgrounds != null) {
BooleanSetting.BLACK_BACKGROUNDS.setBoolean(blackBackgrounds)
saveConfig = true
}
val joystickRelCenter =
preferences.migratePreference<Boolean>(Settings.PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER)
if (joystickRelCenter != null) {
BooleanSetting.JOYSTICK_REL_CENTER.setBoolean(joystickRelCenter)
saveConfig = true
}
val dpadSlide =
preferences.migratePreference<Boolean>(Settings.PREF_MENU_SETTINGS_DPAD_SLIDE)
if (dpadSlide != null) {
BooleanSetting.DPAD_SLIDE.setBoolean(dpadSlide)
saveConfig = true
}
val hapticFeedback =
preferences.migratePreference<Boolean>(Settings.PREF_MENU_SETTINGS_HAPTICS)
if (hapticFeedback != null) {
BooleanSetting.HAPTIC_FEEDBACK.setBoolean(hapticFeedback)
saveConfig = true
}
val showPerformanceOverlay =
preferences.migratePreference<Boolean>(Settings.PREF_MENU_SETTINGS_SHOW_FPS)
if (showPerformanceOverlay != null) {
BooleanSetting.SHOW_PERFORMANCE_OVERLAY.setBoolean(showPerformanceOverlay)
saveConfig = true
}
val showInputOverlay =
preferences.migratePreference<Boolean>(Settings.PREF_MENU_SETTINGS_SHOW_OVERLAY)
if (showInputOverlay != null) {
BooleanSetting.SHOW_INPUT_OVERLAY.setBoolean(showInputOverlay)
saveConfig = true
}
val overlayOpacity = preferences.migratePreference<Int>(Settings.PREF_CONTROL_OPACITY)
if (overlayOpacity != null) {
IntSetting.OVERLAY_OPACITY.setInt(overlayOpacity)
saveConfig = true
}
val overlayScale = preferences.migratePreference<Int>(Settings.PREF_CONTROL_SCALE)
if (overlayScale != null) {
IntSetting.OVERLAY_SCALE.setInt(overlayScale)
saveConfig = true
}
var setOverlayData = false
val overlayControlData = NativeConfig.getOverlayControlData()
if (overlayControlData.isEmpty()) {
val overlayControlDataMap =
NativeConfig.getOverlayControlData().associateBy { it.id }.toMutableMap()
for (button in Settings.overlayPreferences) {
val buttonId = convertButtonId(button)
var buttonEnabled = preferences.migratePreference<Boolean>(button)
if (buttonEnabled == null) {
buttonEnabled = OverlayControl.map[buttonId]?.defaultVisibility == true
}
var landscapeXPosition = preferences.migratePreference<Float>(
"$button-X${Settings.PREF_LANDSCAPE_SUFFIX}"
)?.toDouble()
var landscapeYPosition = preferences.migratePreference<Float>(
"$button-Y${Settings.PREF_LANDSCAPE_SUFFIX}"
)?.toDouble()
if (landscapeXPosition == null || landscapeYPosition == null) {
val landscapePosition = OverlayControl.map[buttonId]
?.getDefaultPositionForLayout(OverlayLayout.Landscape) ?: Pair(0.0, 0.0)
landscapeXPosition = landscapePosition.first
landscapeYPosition = landscapePosition.second
}
var portraitXPosition = preferences.migratePreference<Float>(
"$button-X${Settings.PREF_PORTRAIT_SUFFIX}"
)?.toDouble()
var portraitYPosition = preferences.migratePreference<Float>(
"$button-Y${Settings.PREF_PORTRAIT_SUFFIX}"
)?.toDouble()
if (portraitXPosition == null || portraitYPosition == null) {
val portraitPosition = OverlayControl.map[buttonId]
?.getDefaultPositionForLayout(OverlayLayout.Portrait) ?: Pair(0.0, 0.0)
portraitXPosition = portraitPosition.first
portraitYPosition = portraitPosition.second
}
var foldableXPosition = preferences.migratePreference<Float>(
"$button-X${Settings.PREF_FOLDABLE_SUFFIX}"
)?.toDouble()
var foldableYPosition = preferences.migratePreference<Float>(
"$button-Y${Settings.PREF_FOLDABLE_SUFFIX}"
)?.toDouble()
if (foldableXPosition == null || foldableYPosition == null) {
val foldablePosition = OverlayControl.map[buttonId]
?.getDefaultPositionForLayout(OverlayLayout.Foldable) ?: Pair(0.0, 0.0)
foldableXPosition = foldablePosition.first
foldableYPosition = foldablePosition.second
}
val controlData = OverlayControlData(
buttonId,
buttonEnabled,
Pair(landscapeXPosition, landscapeYPosition),
Pair(portraitXPosition, portraitYPosition),
Pair(foldableXPosition, foldableYPosition)
)
overlayControlDataMap[buttonId] = controlData
setOverlayData = true
}
if (setOverlayData) {
NativeConfig.setOverlayControlData(
overlayControlDataMap.map { it.value }.toTypedArray()
)
saveConfig = true
}
}
if (saveConfig) {
NativeConfig.saveGlobalConfig()
}
}
private fun convertButtonId(buttonId: String): String =
when (buttonId) {
Settings.PREF_BUTTON_A -> OverlayControl.BUTTON_A.id
Settings.PREF_BUTTON_B -> OverlayControl.BUTTON_B.id
Settings.PREF_BUTTON_X -> OverlayControl.BUTTON_X.id
Settings.PREF_BUTTON_Y -> OverlayControl.BUTTON_Y.id
Settings.PREF_BUTTON_L -> OverlayControl.BUTTON_L.id
Settings.PREF_BUTTON_R -> OverlayControl.BUTTON_R.id
Settings.PREF_BUTTON_ZL -> OverlayControl.BUTTON_ZL.id
Settings.PREF_BUTTON_ZR -> OverlayControl.BUTTON_ZR.id
Settings.PREF_BUTTON_PLUS -> OverlayControl.BUTTON_PLUS.id
Settings.PREF_BUTTON_MINUS -> OverlayControl.BUTTON_MINUS.id
Settings.PREF_BUTTON_DPAD -> OverlayControl.COMBINED_DPAD.id
Settings.PREF_STICK_L -> OverlayControl.STICK_L.id
Settings.PREF_STICK_R -> OverlayControl.STICK_R.id
Settings.PREF_BUTTON_HOME -> OverlayControl.BUTTON_HOME.id
Settings.PREF_BUTTON_SCREENSHOT -> OverlayControl.BUTTON_CAPTURE.id
Settings.PREF_BUTTON_STICK_L -> OverlayControl.BUTTON_STICK_L.id
Settings.PREF_BUTTON_STICK_R -> OverlayControl.BUTTON_STICK_R.id
else -> ""
}
} }

View File

@ -1,50 +0,0 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.utils
import androidx.preference.PreferenceManager
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.features.settings.model.Settings
object EmulationMenuSettings {
private val preferences =
PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
var joystickRelCenter: Boolean
get() = preferences.getBoolean(Settings.PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER, true)
set(value) {
preferences.edit()
.putBoolean(Settings.PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER, value)
.apply()
}
var dpadSlide: Boolean
get() = preferences.getBoolean(Settings.PREF_MENU_SETTINGS_DPAD_SLIDE, true)
set(value) {
preferences.edit()
.putBoolean(Settings.PREF_MENU_SETTINGS_DPAD_SLIDE, value)
.apply()
}
var hapticFeedback: Boolean
get() = preferences.getBoolean(Settings.PREF_MENU_SETTINGS_HAPTICS, false)
set(value) {
preferences.edit()
.putBoolean(Settings.PREF_MENU_SETTINGS_HAPTICS, value)
.apply()
}
var showFps: Boolean
get() = preferences.getBoolean(Settings.PREF_MENU_SETTINGS_SHOW_FPS, false)
set(value) {
preferences.edit()
.putBoolean(Settings.PREF_MENU_SETTINGS_SHOW_FPS, value)
.apply()
}
var showOverlay: Boolean
get() = preferences.getBoolean(Settings.PREF_MENU_SETTINGS_SHOW_OVERLAY, true)
set(value) {
preferences.edit()
.putBoolean(Settings.PREF_MENU_SETTINGS_SHOW_OVERLAY, value)
.apply()
}
}

View File

@ -4,6 +4,7 @@
package org.yuzu.yuzu_emu.utils package org.yuzu.yuzu_emu.utils
import org.yuzu.yuzu_emu.model.GameDir import org.yuzu.yuzu_emu.model.GameDir
import org.yuzu.yuzu_emu.overlay.model.OverlayControlData
object NativeConfig { object NativeConfig {
/** /**
@ -150,4 +151,21 @@ object NativeConfig {
*/ */
@Synchronized @Synchronized
external fun setDisabledAddons(programId: String, disabledAddons: Array<String>) external fun setDisabledAddons(programId: String, disabledAddons: Array<String>)
/**
* Gets an array of [OverlayControlData] from settings
*
* @return An array of [OverlayControlData]
*/
@Synchronized
external fun getOverlayControlData(): Array<OverlayControlData>
/**
* Clears the AndroidSettings::values.overlay_control_data array and replaces its values
* with [overlayControlData]
*
* @param overlayControlData Replacement array of [OverlayControlData]
*/
@Synchronized
external fun setOverlayControlData(overlayControlData: Array<OverlayControlData>)
} }

View File

@ -0,0 +1,37 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.utils
import android.content.SharedPreferences
object PreferenceUtil {
/**
* Retrieves a shared preference value and then deletes the value in storage.
* @param key Associated key for the value in this preferences instance
* @return Typed value associated with [key]. Null if no such key exists.
*/
inline fun <reified T> SharedPreferences.migratePreference(key: String): T? {
if (!this.contains(key)) {
return null
}
val value: Any = when (T::class) {
String::class -> this.getString(key, "")!!
Boolean::class -> this.getBoolean(key, false)
Int::class -> this.getInt(key, 0)
Float::class -> this.getFloat(key, 0f)
Long::class -> this.getLong(key, 0)
else -> throw IllegalStateException("Tried to migrate preference with invalid type!")
}
deletePreference(key)
return value as T
}
fun SharedPreferences.deletePreference(key: String) = this.edit().remove(key).apply()
}

View File

@ -5,38 +5,38 @@ package org.yuzu.yuzu_emu.utils
import android.content.res.Configuration import android.content.res.Configuration
import android.graphics.Color import android.graphics.Color
import android.os.Build
import androidx.annotation.ColorInt import androidx.annotation.ColorInt
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import androidx.core.view.WindowCompat import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsControllerCompat import androidx.core.view.WindowInsetsControllerCompat
import androidx.preference.PreferenceManager
import kotlin.math.roundToInt import kotlin.math.roundToInt
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
import org.yuzu.yuzu_emu.features.settings.model.Settings import org.yuzu.yuzu_emu.features.settings.model.IntSetting
import org.yuzu.yuzu_emu.ui.main.ThemeProvider import org.yuzu.yuzu_emu.ui.main.ThemeProvider
object ThemeHelper { object ThemeHelper {
const val SYSTEM_BAR_ALPHA = 0.9f const val SYSTEM_BAR_ALPHA = 0.9f
private const val DEFAULT = 0
private const val MATERIAL_YOU = 1
fun setTheme(activity: AppCompatActivity) { fun setTheme(activity: AppCompatActivity) {
val preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
setThemeMode(activity) setThemeMode(activity)
when (preferences.getInt(Settings.PREF_THEME, 0)) { when (Theme.from(IntSetting.THEME.getInt())) {
DEFAULT -> activity.setTheme(R.style.Theme_Yuzu_Main) Theme.Default -> activity.setTheme(R.style.Theme_Yuzu_Main)
MATERIAL_YOU -> activity.setTheme(R.style.Theme_Yuzu_Main_MaterialYou) Theme.MaterialYou -> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
activity.setTheme(R.style.Theme_Yuzu_Main_MaterialYou)
} else {
activity.setTheme(R.style.Theme_Yuzu_Main)
}
}
} }
// Using a specific night mode check because this could apply incorrectly when using the // Using a specific night mode check because this could apply incorrectly when using the
// light app mode, dark system mode, and black backgrounds. Launching the settings activity // light app mode, dark system mode, and black backgrounds. Launching the settings activity
// will then show light mode colors/navigation bars but with black backgrounds. // will then show light mode colors/navigation bars but with black backgrounds.
if (preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false) && if (BooleanSetting.BLACK_BACKGROUNDS.getBoolean() && isNightMode(activity)) {
isNightMode(activity)
) {
activity.setTheme(R.style.ThemeOverlay_Yuzu_Dark) activity.setTheme(R.style.ThemeOverlay_Yuzu_Dark)
} }
} }
@ -60,8 +60,7 @@ object ThemeHelper {
} }
fun setThemeMode(activity: AppCompatActivity) { fun setThemeMode(activity: AppCompatActivity) {
val themeMode = PreferenceManager.getDefaultSharedPreferences(activity.applicationContext) val themeMode = IntSetting.THEME_MODE.getInt()
.getInt(Settings.PREF_THEME_MODE, AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
activity.delegate.localNightMode = themeMode activity.delegate.localNightMode = themeMode
val windowController = WindowCompat.getInsetsController( val windowController = WindowCompat.getInsetsController(
activity.window, activity.window,
@ -95,3 +94,12 @@ object ThemeHelper {
windowController.isAppearanceLightNavigationBars = false windowController.isAppearanceLightNavigationBars = false
} }
} }
enum class Theme(val int: Int) {
Default(0),
MaterialYou(1);
companion object {
fun from(int: Int): Theme = entries.firstOrNull { it.int == int } ?: Default
}
}

View File

@ -9,6 +9,7 @@
#include <jni.h> #include <jni.h>
#include "common/string_util.h" #include "common/string_util.h"
#include "jni/id_cache.h"
std::string GetJString(JNIEnv* env, jstring jstr) { std::string GetJString(JNIEnv* env, jstring jstr) {
if (!jstr) { if (!jstr) {
@ -33,3 +34,11 @@ jstring ToJString(JNIEnv* env, std::string_view str) {
jstring ToJString(JNIEnv* env, std::u16string_view str) { jstring ToJString(JNIEnv* env, std::u16string_view str) {
return ToJString(env, Common::UTF16ToUTF8(str)); return ToJString(env, Common::UTF16ToUTF8(str));
} }
double GetJDouble(JNIEnv* env, jobject jdouble) {
return env->GetDoubleField(jdouble, IDCache::GetDoubleValueField());
}
jobject ToJDouble(JNIEnv* env, double value) {
return env->NewObject(IDCache::GetDoubleClass(), IDCache::GetDoubleConstructor(), value);
}

View File

@ -10,3 +10,6 @@
std::string GetJString(JNIEnv* env, jstring jstr); std::string GetJString(JNIEnv* env, jstring jstr);
jstring ToJString(JNIEnv* env, std::string_view str); jstring ToJString(JNIEnv* env, std::string_view str);
jstring ToJString(JNIEnv* env, std::u16string_view str); jstring ToJString(JNIEnv* env, std::u16string_view str);
double GetJDouble(JNIEnv* env, jobject jdouble);
jobject ToJDouble(JNIEnv* env, double value);

View File

@ -35,6 +35,7 @@ void AndroidConfig::ReadAndroidValues() {
if (global) { if (global) {
ReadAndroidUIValues(); ReadAndroidUIValues();
ReadUIValues(); ReadUIValues();
ReadOverlayValues();
} }
ReadDriverValues(); ReadDriverValues();
} }
@ -81,10 +82,42 @@ void AndroidConfig::ReadDriverValues() {
EndGroup(); EndGroup();
} }
void AndroidConfig::ReadOverlayValues() {
BeginGroup(Settings::TranslateCategory(Settings::Category::Overlay));
ReadCategory(Settings::Category::Overlay);
AndroidSettings::values.overlay_control_data.clear();
const int control_data_size = BeginArray("control_data");
for (int i = 0; i < control_data_size; ++i) {
SetArrayIndex(i);
AndroidSettings::OverlayControlData control_data;
control_data.id = ReadStringSetting(std::string("id"));
control_data.enabled = ReadBooleanSetting(std::string("enabled"));
control_data.landscape_position.first =
ReadDoubleSetting(std::string("landscape\\x_position"));
control_data.landscape_position.second =
ReadDoubleSetting(std::string("landscape\\y_position"));
control_data.portrait_position.first =
ReadDoubleSetting(std::string("portrait\\x_position"));
control_data.portrait_position.second =
ReadDoubleSetting(std::string("portrait\\y_position"));
control_data.foldable_position.first =
ReadDoubleSetting(std::string("foldable\\x_position"));
control_data.foldable_position.second =
ReadDoubleSetting(std::string("foldable\\y_position"));
AndroidSettings::values.overlay_control_data.push_back(control_data);
}
EndArray();
EndGroup();
}
void AndroidConfig::SaveAndroidValues() { void AndroidConfig::SaveAndroidValues() {
if (global) { if (global) {
SaveAndroidUIValues(); SaveAndroidUIValues();
SaveUIValues(); SaveUIValues();
SaveOverlayValues();
} }
SaveDriverValues(); SaveDriverValues();
@ -114,8 +147,9 @@ void AndroidConfig::SavePathValues() {
for (size_t i = 0; i < AndroidSettings::values.game_dirs.size(); ++i) { for (size_t i = 0; i < AndroidSettings::values.game_dirs.size(); ++i) {
SetArrayIndex(i); SetArrayIndex(i);
const auto& game_dir = AndroidSettings::values.game_dirs[i]; const auto& game_dir = AndroidSettings::values.game_dirs[i];
WriteSetting(std::string("path"), game_dir.path); WriteStringSetting(std::string("path"), game_dir.path);
WriteSetting(std::string("deep_scan"), game_dir.deep_scan, std::make_optional(false)); WriteBooleanSetting(std::string("deep_scan"), game_dir.deep_scan,
std::make_optional(false));
} }
EndArray(); EndArray();
@ -130,6 +164,35 @@ void AndroidConfig::SaveDriverValues() {
EndGroup(); EndGroup();
} }
void AndroidConfig::SaveOverlayValues() {
BeginGroup(Settings::TranslateCategory(Settings::Category::Overlay));
WriteCategory(Settings::Category::Overlay);
BeginArray("control_data");
for (size_t i = 0; i < AndroidSettings::values.overlay_control_data.size(); ++i) {
SetArrayIndex(i);
const auto& control_data = AndroidSettings::values.overlay_control_data[i];
WriteStringSetting(std::string("id"), control_data.id);
WriteBooleanSetting(std::string("enabled"), control_data.enabled);
WriteDoubleSetting(std::string("landscape\\x_position"),
control_data.landscape_position.first);
WriteDoubleSetting(std::string("landscape\\y_position"),
control_data.landscape_position.second);
WriteDoubleSetting(std::string("portrait\\x_position"),
control_data.portrait_position.first);
WriteDoubleSetting(std::string("portrait\\y_position"),
control_data.portrait_position.second);
WriteDoubleSetting(std::string("foldable\\x_position"),
control_data.foldable_position.first);
WriteDoubleSetting(std::string("foldable\\y_position"),
control_data.foldable_position.second);
}
EndArray();
EndGroup();
}
std::vector<Settings::BasicSetting*>& AndroidConfig::FindRelevantList(Settings::Category category) { std::vector<Settings::BasicSetting*>& AndroidConfig::FindRelevantList(Settings::Category category) {
auto& map = Settings::values.linkage.by_category; auto& map = Settings::values.linkage.by_category;
if (map.contains(category)) { if (map.contains(category)) {

View File

@ -18,6 +18,7 @@ protected:
void ReadAndroidValues(); void ReadAndroidValues();
void ReadAndroidUIValues(); void ReadAndroidUIValues();
void ReadDriverValues(); void ReadDriverValues();
void ReadOverlayValues();
void ReadHidbusValues() override {} void ReadHidbusValues() override {}
void ReadDebugControlValues() override {} void ReadDebugControlValues() override {}
void ReadPathValues() override; void ReadPathValues() override;
@ -30,6 +31,7 @@ protected:
void SaveAndroidValues(); void SaveAndroidValues();
void SaveAndroidUIValues(); void SaveAndroidUIValues();
void SaveDriverValues(); void SaveDriverValues();
void SaveOverlayValues();
void SaveHidbusValues() override {} void SaveHidbusValues() override {}
void SaveDebugControlValues() override {} void SaveDebugControlValues() override {}
void SavePathValues() override; void SavePathValues() override;

View File

@ -14,6 +14,14 @@ struct GameDir {
bool deep_scan = false; bool deep_scan = false;
}; };
struct OverlayControlData {
std::string id;
bool enabled;
std::pair<double, double> landscape_position;
std::pair<double, double> portrait_position;
std::pair<double, double> foldable_position;
};
struct Values { struct Values {
Settings::Linkage linkage; Settings::Linkage linkage;
@ -33,6 +41,28 @@ struct Values {
Settings::SwitchableSetting<std::string, false> driver_path{linkage, "", "driver_path", Settings::SwitchableSetting<std::string, false> driver_path{linkage, "", "driver_path",
Settings::Category::GpuDriver}; Settings::Category::GpuDriver};
Settings::Setting<s32> theme{linkage, 0, "theme", Settings::Category::Android};
Settings::Setting<s32> theme_mode{linkage, -1, "theme_mode", Settings::Category::Android};
Settings::Setting<bool> black_backgrounds{linkage, false, "black_backgrounds",
Settings::Category::Android};
// Input/performance overlay settings
std::vector<OverlayControlData> overlay_control_data;
Settings::Setting<s32> overlay_scale{linkage, 50, "control_scale", Settings::Category::Overlay};
Settings::Setting<s32> overlay_opacity{linkage, 100, "control_opacity",
Settings::Category::Overlay};
Settings::Setting<bool> joystick_rel_center{linkage, true, "joystick_rel_center",
Settings::Category::Overlay};
Settings::Setting<bool> dpad_slide{linkage, true, "dpad_slide", Settings::Category::Overlay};
Settings::Setting<bool> haptic_feedback{linkage, true, "haptic_feedback",
Settings::Category::Overlay};
Settings::Setting<bool> show_performance_overlay{linkage, true, "show_performance_overlay",
Settings::Category::Overlay};
Settings::Setting<bool> show_input_overlay{linkage, true, "show_input_overlay",
Settings::Category::Overlay};
Settings::Setting<bool> touchscreen{linkage, true, "touchscreen", Settings::Category::Overlay};
}; };
extern Values values; extern Values values;

View File

@ -35,6 +35,18 @@ static jmethodID s_pair_constructor;
static jfieldID s_pair_first_field; static jfieldID s_pair_first_field;
static jfieldID s_pair_second_field; static jfieldID s_pair_second_field;
static jclass s_overlay_control_data_class;
static jmethodID s_overlay_control_data_constructor;
static jfieldID s_overlay_control_data_id_field;
static jfieldID s_overlay_control_data_enabled_field;
static jfieldID s_overlay_control_data_landscape_position_field;
static jfieldID s_overlay_control_data_portrait_position_field;
static jfieldID s_overlay_control_data_foldable_position_field;
static jclass s_double_class;
static jmethodID s_double_constructor;
static jfieldID s_double_value_field;
static constexpr jint JNI_VERSION = JNI_VERSION_1_6; static constexpr jint JNI_VERSION = JNI_VERSION_1_6;
namespace IDCache { namespace IDCache {
@ -146,6 +158,46 @@ jfieldID GetPairSecondField() {
return s_pair_second_field; return s_pair_second_field;
} }
jclass GetOverlayControlDataClass() {
return s_overlay_control_data_class;
}
jmethodID GetOverlayControlDataConstructor() {
return s_overlay_control_data_constructor;
}
jfieldID GetOverlayControlDataIdField() {
return s_overlay_control_data_id_field;
}
jfieldID GetOverlayControlDataEnabledField() {
return s_overlay_control_data_enabled_field;
}
jfieldID GetOverlayControlDataLandscapePositionField() {
return s_overlay_control_data_landscape_position_field;
}
jfieldID GetOverlayControlDataPortraitPositionField() {
return s_overlay_control_data_portrait_position_field;
}
jfieldID GetOverlayControlDataFoldablePositionField() {
return s_overlay_control_data_foldable_position_field;
}
jclass GetDoubleClass() {
return s_double_class;
}
jmethodID GetDoubleConstructor() {
return s_double_constructor;
}
jfieldID GetDoubleValueField() {
return s_double_value_field;
}
} // namespace IDCache } // namespace IDCache
#ifdef __cplusplus #ifdef __cplusplus
@ -207,6 +259,31 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) {
s_pair_second_field = env->GetFieldID(pair_class, "second", "Ljava/lang/Object;"); s_pair_second_field = env->GetFieldID(pair_class, "second", "Ljava/lang/Object;");
env->DeleteLocalRef(pair_class); env->DeleteLocalRef(pair_class);
const jclass overlay_control_data_class =
env->FindClass("org/yuzu/yuzu_emu/overlay/model/OverlayControlData");
s_overlay_control_data_class =
reinterpret_cast<jclass>(env->NewGlobalRef(overlay_control_data_class));
s_overlay_control_data_constructor =
env->GetMethodID(overlay_control_data_class, "<init>",
"(Ljava/lang/String;ZLkotlin/Pair;Lkotlin/Pair;Lkotlin/Pair;)V");
s_overlay_control_data_id_field =
env->GetFieldID(overlay_control_data_class, "id", "Ljava/lang/String;");
s_overlay_control_data_enabled_field =
env->GetFieldID(overlay_control_data_class, "enabled", "Z");
s_overlay_control_data_landscape_position_field =
env->GetFieldID(overlay_control_data_class, "landscapePosition", "Lkotlin/Pair;");
s_overlay_control_data_portrait_position_field =
env->GetFieldID(overlay_control_data_class, "portraitPosition", "Lkotlin/Pair;");
s_overlay_control_data_foldable_position_field =
env->GetFieldID(overlay_control_data_class, "foldablePosition", "Lkotlin/Pair;");
env->DeleteLocalRef(overlay_control_data_class);
const jclass double_class = env->FindClass("java/lang/Double");
s_double_class = reinterpret_cast<jclass>(env->NewGlobalRef(double_class));
s_double_constructor = env->GetMethodID(double_class, "<init>", "(D)V");
s_double_value_field = env->GetFieldID(double_class, "value", "D");
env->DeleteLocalRef(double_class);
// Initialize Android Storage // Initialize Android Storage
Common::FS::Android::RegisterCallbacks(env, s_native_library_class); Common::FS::Android::RegisterCallbacks(env, s_native_library_class);
@ -231,6 +308,8 @@ void JNI_OnUnload(JavaVM* vm, void* reserved) {
env->DeleteGlobalRef(s_game_class); env->DeleteGlobalRef(s_game_class);
env->DeleteGlobalRef(s_string_class); env->DeleteGlobalRef(s_string_class);
env->DeleteGlobalRef(s_pair_class); env->DeleteGlobalRef(s_pair_class);
env->DeleteGlobalRef(s_overlay_control_data_class);
env->DeleteGlobalRef(s_double_class);
// UnInitialize applets // UnInitialize applets
SoftwareKeyboard::CleanupJNI(env); SoftwareKeyboard::CleanupJNI(env);

View File

@ -35,4 +35,16 @@ jmethodID GetPairConstructor();
jfieldID GetPairFirstField(); jfieldID GetPairFirstField();
jfieldID GetPairSecondField(); jfieldID GetPairSecondField();
jclass GetOverlayControlDataClass();
jmethodID GetOverlayControlDataConstructor();
jfieldID GetOverlayControlDataIdField();
jfieldID GetOverlayControlDataEnabledField();
jfieldID GetOverlayControlDataLandscapePositionField();
jfieldID GetOverlayControlDataPortraitPositionField();
jfieldID GetOverlayControlDataFoldablePositionField();
jclass GetDoubleClass();
jmethodID GetDoubleConstructor();
jfieldID GetDoubleValueField();
} // namespace IDCache } // namespace IDCache

View File

@ -344,4 +344,74 @@ void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setDisabledAddons(JNIEnv* env, j
Settings::values.disabled_addons[program_id] = disabled_addons; Settings::values.disabled_addons[program_id] = disabled_addons;
} }
jobjectArray Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getOverlayControlData(JNIEnv* env,
jobject obj) {
jobjectArray joverlayControlDataArray =
env->NewObjectArray(AndroidSettings::values.overlay_control_data.size(),
IDCache::GetOverlayControlDataClass(), nullptr);
for (size_t i = 0; i < AndroidSettings::values.overlay_control_data.size(); ++i) {
const auto& control_data = AndroidSettings::values.overlay_control_data[i];
jobject jlandscapePosition =
env->NewObject(IDCache::GetPairClass(), IDCache::GetPairConstructor(),
ToJDouble(env, control_data.landscape_position.first),
ToJDouble(env, control_data.landscape_position.second));
jobject jportraitPosition =
env->NewObject(IDCache::GetPairClass(), IDCache::GetPairConstructor(),
ToJDouble(env, control_data.portrait_position.first),
ToJDouble(env, control_data.portrait_position.second));
jobject jfoldablePosition =
env->NewObject(IDCache::GetPairClass(), IDCache::GetPairConstructor(),
ToJDouble(env, control_data.foldable_position.first),
ToJDouble(env, control_data.foldable_position.second));
jobject jcontrolData = env->NewObject(
IDCache::GetOverlayControlDataClass(), IDCache::GetOverlayControlDataConstructor(),
ToJString(env, control_data.id), control_data.enabled, jlandscapePosition,
jportraitPosition, jfoldablePosition);
env->SetObjectArrayElement(joverlayControlDataArray, i, jcontrolData);
}
return joverlayControlDataArray;
}
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setOverlayControlData(
JNIEnv* env, jobject obj, jobjectArray joverlayControlDataArray) {
AndroidSettings::values.overlay_control_data.clear();
int size = env->GetArrayLength(joverlayControlDataArray);
if (size == 0) {
return;
}
for (int i = 0; i < size; ++i) {
jobject joverlayControlData = env->GetObjectArrayElement(joverlayControlDataArray, i);
jstring jidString = static_cast<jstring>(
env->GetObjectField(joverlayControlData, IDCache::GetOverlayControlDataIdField()));
bool enabled = static_cast<bool>(env->GetBooleanField(
joverlayControlData, IDCache::GetOverlayControlDataEnabledField()));
jobject jlandscapePosition = env->GetObjectField(
joverlayControlData, IDCache::GetOverlayControlDataLandscapePositionField());
std::pair<double, double> landscape_position = std::make_pair(
GetJDouble(env, env->GetObjectField(jlandscapePosition, IDCache::GetPairFirstField())),
GetJDouble(env,
env->GetObjectField(jlandscapePosition, IDCache::GetPairSecondField())));
jobject jportraitPosition = env->GetObjectField(
joverlayControlData, IDCache::GetOverlayControlDataPortraitPositionField());
std::pair<double, double> portrait_position = std::make_pair(
GetJDouble(env, env->GetObjectField(jportraitPosition, IDCache::GetPairFirstField())),
GetJDouble(env, env->GetObjectField(jportraitPosition, IDCache::GetPairSecondField())));
jobject jfoldablePosition = env->GetObjectField(
joverlayControlData, IDCache::GetOverlayControlDataFoldablePositionField());
std::pair<double, double> foldable_position = std::make_pair(
GetJDouble(env, env->GetObjectField(jfoldablePosition, IDCache::GetPairFirstField())),
GetJDouble(env, env->GetObjectField(jfoldablePosition, IDCache::GetPairSecondField())));
AndroidSettings::values.overlay_control_data.push_back(AndroidSettings::OverlayControlData{
GetJString(env, jidString), enabled, landscape_position, portrait_position,
foldable_position});
}
}
} // extern "C" } // extern "C"

View File

@ -38,6 +38,11 @@
android:title="@string/emulation_haptics" android:title="@string/emulation_haptics"
android:checkable="true" /> android:checkable="true" />
<item
android:id="@+id/menu_touchscreen"
android:title="@string/touchscreen"
android:checkable="true" />
<item <item
android:id="@+id/menu_reset_overlay" android:id="@+id/menu_reset_overlay"
android:title="@string/emulation_touch_overlay_reset" /> android:title="@string/emulation_touch_overlay_reset" />

View File

@ -212,19 +212,19 @@
<item>B</item> <item>B</item>
<item>X</item> <item>X</item>
<item>Y</item> <item>Y</item>
<item>+</item>
<item>-</item>
<item>@string/gamepad_home</item>
<item>@string/gamepad_screenshot</item>
<item>L</item> <item>L</item>
<item>R</item> <item>R</item>
<item>ZL</item> <item>ZL</item>
<item>ZR</item> <item>ZR</item>
<item>+</item>
<item>-</item>
<item>@string/gamepad_d_pad</item>
<item>@string/gamepad_left_stick</item> <item>@string/gamepad_left_stick</item>
<item>@string/gamepad_right_stick</item> <item>@string/gamepad_right_stick</item>
<item>L3</item> <item>L3</item>
<item>R3</item> <item>R3</item>
<item>@string/gamepad_home</item> <item>@string/gamepad_d_pad</item>
<item>@string/gamepad_screenshot</item>
</string-array> </string-array>
<string-array name="themeEntries"> <string-array name="themeEntries">

View File

@ -3,111 +3,111 @@
<integer name="grid_columns">1</integer> <integer name="grid_columns">1</integer>
<!-- Default SWITCH landscape layout --> <!-- Default SWITCH landscape layout -->
<integer name="SWITCH_BUTTON_A_X">760</integer> <integer name="BUTTON_A_X">760</integer>
<integer name="SWITCH_BUTTON_A_Y">790</integer> <integer name="BUTTON_A_Y">790</integer>
<integer name="SWITCH_BUTTON_B_X">710</integer> <integer name="BUTTON_B_X">710</integer>
<integer name="SWITCH_BUTTON_B_Y">900</integer> <integer name="BUTTON_B_Y">900</integer>
<integer name="SWITCH_BUTTON_X_X">710</integer> <integer name="BUTTON_X_X">710</integer>
<integer name="SWITCH_BUTTON_X_Y">680</integer> <integer name="BUTTON_X_Y">680</integer>
<integer name="SWITCH_BUTTON_Y_X">660</integer> <integer name="BUTTON_Y_X">660</integer>
<integer name="SWITCH_BUTTON_Y_Y">790</integer> <integer name="BUTTON_Y_Y">790</integer>
<integer name="SWITCH_STICK_L_X">100</integer> <integer name="BUTTON_PLUS_X">540</integer>
<integer name="SWITCH_STICK_L_Y">670</integer> <integer name="BUTTON_PLUS_Y">950</integer>
<integer name="SWITCH_STICK_R_X">900</integer> <integer name="BUTTON_MINUS_X">460</integer>
<integer name="SWITCH_STICK_R_Y">670</integer> <integer name="BUTTON_MINUS_Y">950</integer>
<integer name="SWITCH_TRIGGER_L_X">70</integer> <integer name="BUTTON_HOME_X">600</integer>
<integer name="SWITCH_TRIGGER_L_Y">220</integer> <integer name="BUTTON_HOME_Y">950</integer>
<integer name="SWITCH_TRIGGER_R_X">930</integer> <integer name="BUTTON_CAPTURE_X">400</integer>
<integer name="SWITCH_TRIGGER_R_Y">220</integer> <integer name="BUTTON_CAPTURE_Y">950</integer>
<integer name="SWITCH_TRIGGER_ZL_X">70</integer> <integer name="BUTTON_L_X">70</integer>
<integer name="SWITCH_TRIGGER_ZL_Y">90</integer> <integer name="BUTTON_L_Y">220</integer>
<integer name="SWITCH_TRIGGER_ZR_X">930</integer> <integer name="BUTTON_R_X">930</integer>
<integer name="SWITCH_TRIGGER_ZR_Y">90</integer> <integer name="BUTTON_R_Y">220</integer>
<integer name="SWITCH_BUTTON_MINUS_X">460</integer> <integer name="BUTTON_ZL_X">70</integer>
<integer name="SWITCH_BUTTON_MINUS_Y">950</integer> <integer name="BUTTON_ZL_Y">90</integer>
<integer name="SWITCH_BUTTON_PLUS_X">540</integer> <integer name="BUTTON_ZR_X">930</integer>
<integer name="SWITCH_BUTTON_PLUS_Y">950</integer> <integer name="BUTTON_ZR_Y">90</integer>
<integer name="SWITCH_BUTTON_HOME_X">600</integer> <integer name="BUTTON_STICK_L_X">870</integer>
<integer name="SWITCH_BUTTON_HOME_Y">950</integer> <integer name="BUTTON_STICK_L_Y">400</integer>
<integer name="SWITCH_BUTTON_CAPTURE_X">400</integer> <integer name="BUTTON_STICK_R_X">960</integer>
<integer name="SWITCH_BUTTON_CAPTURE_Y">950</integer> <integer name="BUTTON_STICK_R_Y">430</integer>
<integer name="SWITCH_BUTTON_DPAD_X">260</integer> <integer name="STICK_L_X">100</integer>
<integer name="SWITCH_BUTTON_DPAD_Y">790</integer> <integer name="STICK_L_Y">670</integer>
<integer name="SWITCH_BUTTON_STICK_L_X">870</integer> <integer name="STICK_R_X">900</integer>
<integer name="SWITCH_BUTTON_STICK_L_Y">400</integer> <integer name="STICK_R_Y">670</integer>
<integer name="SWITCH_BUTTON_STICK_R_X">960</integer> <integer name="COMBINED_DPAD_X">260</integer>
<integer name="SWITCH_BUTTON_STICK_R_Y">430</integer> <integer name="COMBINED_DPAD_Y">790</integer>
<!-- Default SWITCH portrait layout --> <!-- Default SWITCH portrait layout -->
<integer name="SWITCH_BUTTON_A_X_PORTRAIT">840</integer> <integer name="BUTTON_A_X_PORTRAIT">840</integer>
<integer name="SWITCH_BUTTON_A_Y_PORTRAIT">840</integer> <integer name="BUTTON_A_Y_PORTRAIT">840</integer>
<integer name="SWITCH_BUTTON_B_X_PORTRAIT">740</integer> <integer name="BUTTON_B_X_PORTRAIT">740</integer>
<integer name="SWITCH_BUTTON_B_Y_PORTRAIT">880</integer> <integer name="BUTTON_B_Y_PORTRAIT">880</integer>
<integer name="SWITCH_BUTTON_X_X_PORTRAIT">740</integer> <integer name="BUTTON_X_X_PORTRAIT">740</integer>
<integer name="SWITCH_BUTTON_X_Y_PORTRAIT">800</integer> <integer name="BUTTON_X_Y_PORTRAIT">800</integer>
<integer name="SWITCH_BUTTON_Y_X_PORTRAIT">640</integer> <integer name="BUTTON_Y_X_PORTRAIT">640</integer>
<integer name="SWITCH_BUTTON_Y_Y_PORTRAIT">840</integer> <integer name="BUTTON_Y_Y_PORTRAIT">840</integer>
<integer name="SWITCH_STICK_L_X_PORTRAIT">180</integer> <integer name="BUTTON_PLUS_Y_PORTRAIT">950</integer>
<integer name="SWITCH_STICK_L_Y_PORTRAIT">660</integer> <integer name="BUTTON_MINUS_X_PORTRAIT">440</integer>
<integer name="SWITCH_STICK_R_X_PORTRAIT">820</integer> <integer name="BUTTON_MINUS_Y_PORTRAIT">950</integer>
<integer name="SWITCH_STICK_R_Y_PORTRAIT">660</integer> <integer name="BUTTON_HOME_X_PORTRAIT">680</integer>
<integer name="SWITCH_TRIGGER_L_X_PORTRAIT">140</integer> <integer name="BUTTON_HOME_Y_PORTRAIT">950</integer>
<integer name="SWITCH_TRIGGER_L_Y_PORTRAIT">260</integer> <integer name="BUTTON_CAPTURE_X_PORTRAIT">320</integer>
<integer name="SWITCH_TRIGGER_R_X_PORTRAIT">860</integer> <integer name="BUTTON_CAPTURE_Y_PORTRAIT">950</integer>
<integer name="SWITCH_TRIGGER_R_Y_PORTRAIT">260</integer> <integer name="BUTTON_L_X_PORTRAIT">140</integer>
<integer name="SWITCH_TRIGGER_ZL_X_PORTRAIT">140</integer> <integer name="BUTTON_L_Y_PORTRAIT">260</integer>
<integer name="SWITCH_TRIGGER_ZL_Y_PORTRAIT">200</integer> <integer name="BUTTON_R_X_PORTRAIT">860</integer>
<integer name="SWITCH_TRIGGER_ZR_X_PORTRAIT">860</integer> <integer name="BUTTON_R_Y_PORTRAIT">260</integer>
<integer name="SWITCH_TRIGGER_ZR_Y_PORTRAIT">200</integer> <integer name="BUTTON_ZL_X_PORTRAIT">140</integer>
<integer name="SWITCH_BUTTON_MINUS_X_PORTRAIT">440</integer> <integer name="BUTTON_ZL_Y_PORTRAIT">200</integer>
<integer name="SWITCH_BUTTON_MINUS_Y_PORTRAIT">950</integer> <integer name="BUTTON_ZR_X_PORTRAIT">860</integer>
<integer name="SWITCH_BUTTON_PLUS_X_PORTRAIT">560</integer> <integer name="BUTTON_ZR_Y_PORTRAIT">200</integer>
<integer name="SWITCH_BUTTON_PLUS_Y_PORTRAIT">950</integer> <integer name="BUTTON_PLUS_X_PORTRAIT">560</integer>
<integer name="SWITCH_BUTTON_HOME_X_PORTRAIT">680</integer> <integer name="BUTTON_STICK_L_X_PORTRAIT">730</integer>
<integer name="SWITCH_BUTTON_HOME_Y_PORTRAIT">950</integer> <integer name="BUTTON_STICK_L_Y_PORTRAIT">510</integer>
<integer name="SWITCH_BUTTON_CAPTURE_X_PORTRAIT">320</integer> <integer name="BUTTON_STICK_R_X_PORTRAIT">900</integer>
<integer name="SWITCH_BUTTON_CAPTURE_Y_PORTRAIT">950</integer> <integer name="BUTTON_STICK_R_Y_PORTRAIT">540</integer>
<integer name="SWITCH_BUTTON_DPAD_X_PORTRAIT">240</integer> <integer name="STICK_L_X_PORTRAIT">180</integer>
<integer name="SWITCH_BUTTON_DPAD_Y_PORTRAIT">840</integer> <integer name="STICK_L_Y_PORTRAIT">660</integer>
<integer name="SWITCH_BUTTON_STICK_L_X_PORTRAIT">730</integer> <integer name="STICK_R_X_PORTRAIT">820</integer>
<integer name="SWITCH_BUTTON_STICK_L_Y_PORTRAIT">510</integer> <integer name="STICK_R_Y_PORTRAIT">660</integer>
<integer name="SWITCH_BUTTON_STICK_R_X_PORTRAIT">900</integer> <integer name="COMBINED_DPAD_X_PORTRAIT">240</integer>
<integer name="SWITCH_BUTTON_STICK_R_Y_PORTRAIT">540</integer> <integer name="COMBINED_DPAD_Y_PORTRAIT">840</integer>
<!-- Default SWITCH foldable layout --> <!-- Default SWITCH foldable layout -->
<integer name="SWITCH_BUTTON_A_X_FOLDABLE">840</integer> <integer name="BUTTON_A_X_FOLDABLE">840</integer>
<integer name="SWITCH_BUTTON_A_Y_FOLDABLE">390</integer> <integer name="BUTTON_A_Y_FOLDABLE">390</integer>
<integer name="SWITCH_BUTTON_B_X_FOLDABLE">740</integer> <integer name="BUTTON_B_X_FOLDABLE">740</integer>
<integer name="SWITCH_BUTTON_B_Y_FOLDABLE">430</integer> <integer name="BUTTON_B_Y_FOLDABLE">430</integer>
<integer name="SWITCH_BUTTON_X_X_FOLDABLE">740</integer> <integer name="BUTTON_X_X_FOLDABLE">740</integer>
<integer name="SWITCH_BUTTON_X_Y_FOLDABLE">350</integer> <integer name="BUTTON_X_Y_FOLDABLE">350</integer>
<integer name="SWITCH_BUTTON_Y_X_FOLDABLE">640</integer> <integer name="BUTTON_Y_X_FOLDABLE">640</integer>
<integer name="SWITCH_BUTTON_Y_Y_FOLDABLE">390</integer> <integer name="BUTTON_Y_Y_FOLDABLE">390</integer>
<integer name="SWITCH_STICK_L_X_FOLDABLE">180</integer> <integer name="BUTTON_PLUS_X_FOLDABLE">560</integer>
<integer name="SWITCH_STICK_L_Y_FOLDABLE">250</integer> <integer name="BUTTON_PLUS_Y_FOLDABLE">470</integer>
<integer name="SWITCH_STICK_R_X_FOLDABLE">820</integer> <integer name="BUTTON_MINUS_X_FOLDABLE">440</integer>
<integer name="SWITCH_STICK_R_Y_FOLDABLE">250</integer> <integer name="BUTTON_MINUS_Y_FOLDABLE">470</integer>
<integer name="SWITCH_TRIGGER_L_X_FOLDABLE">140</integer> <integer name="BUTTON_HOME_X_FOLDABLE">680</integer>
<integer name="SWITCH_TRIGGER_L_Y_FOLDABLE">130</integer> <integer name="BUTTON_HOME_Y_FOLDABLE">470</integer>
<integer name="SWITCH_TRIGGER_R_X_FOLDABLE">860</integer> <integer name="BUTTON_CAPTURE_X_FOLDABLE">320</integer>
<integer name="SWITCH_TRIGGER_R_Y_FOLDABLE">130</integer> <integer name="BUTTON_CAPTURE_Y_FOLDABLE">470</integer>
<integer name="SWITCH_TRIGGER_ZL_X_FOLDABLE">140</integer> <integer name="BUTTON_L_X_FOLDABLE">140</integer>
<integer name="SWITCH_TRIGGER_ZL_Y_FOLDABLE">70</integer> <integer name="BUTTON_L_Y_FOLDABLE">130</integer>
<integer name="SWITCH_TRIGGER_ZR_X_FOLDABLE">860</integer> <integer name="BUTTON_R_X_FOLDABLE">860</integer>
<integer name="SWITCH_TRIGGER_ZR_Y_FOLDABLE">70</integer> <integer name="BUTTON_R_Y_FOLDABLE">130</integer>
<integer name="SWITCH_BUTTON_MINUS_X_FOLDABLE">440</integer> <integer name="BUTTON_ZL_X_FOLDABLE">140</integer>
<integer name="SWITCH_BUTTON_MINUS_Y_FOLDABLE">470</integer> <integer name="BUTTON_ZL_Y_FOLDABLE">70</integer>
<integer name="SWITCH_BUTTON_PLUS_X_FOLDABLE">560</integer> <integer name="BUTTON_ZR_X_FOLDABLE">860</integer>
<integer name="SWITCH_BUTTON_PLUS_Y_FOLDABLE">470</integer> <integer name="BUTTON_ZR_Y_FOLDABLE">70</integer>
<integer name="SWITCH_BUTTON_HOME_X_FOLDABLE">680</integer> <integer name="BUTTON_STICK_L_X_FOLDABLE">550</integer>
<integer name="SWITCH_BUTTON_HOME_Y_FOLDABLE">470</integer> <integer name="BUTTON_STICK_L_Y_FOLDABLE">210</integer>
<integer name="SWITCH_BUTTON_CAPTURE_X_FOLDABLE">320</integer> <integer name="BUTTON_STICK_R_X_FOLDABLE">550</integer>
<integer name="SWITCH_BUTTON_CAPTURE_Y_FOLDABLE">470</integer> <integer name="BUTTON_STICK_R_Y_FOLDABLE">280</integer>
<integer name="SWITCH_BUTTON_DPAD_X_FOLDABLE">240</integer> <integer name="STICK_L_X_FOLDABLE">180</integer>
<integer name="SWITCH_BUTTON_DPAD_Y_FOLDABLE">390</integer> <integer name="STICK_L_Y_FOLDABLE">250</integer>
<integer name="SWITCH_BUTTON_STICK_L_X_FOLDABLE">550</integer> <integer name="STICK_R_X_FOLDABLE">820</integer>
<integer name="SWITCH_BUTTON_STICK_L_Y_FOLDABLE">210</integer> <integer name="STICK_R_Y_FOLDABLE">250</integer>
<integer name="SWITCH_BUTTON_STICK_R_X_FOLDABLE">550</integer> <integer name="COMBINED_DPAD_X_FOLDABLE">240</integer>
<integer name="SWITCH_BUTTON_STICK_R_Y_FOLDABLE">280</integer> <integer name="COMBINED_DPAD_Y_FOLDABLE">390</integer>
</resources> </resources>

View File

@ -366,6 +366,7 @@
<string name="emulation_pause">Pause emulation</string> <string name="emulation_pause">Pause emulation</string>
<string name="emulation_unpause">Unpause emulation</string> <string name="emulation_unpause">Unpause emulation</string>
<string name="emulation_input_overlay">Overlay options</string> <string name="emulation_input_overlay">Overlay options</string>
<string name="touchscreen">Touchscreen</string>
<string name="load_settings">Loading settings…</string> <string name="load_settings">Loading settings…</string>

View File

@ -5,7 +5,7 @@
plugins { plugins {
id("com.android.application") version "8.1.2" apply false id("com.android.application") version "8.1.2" apply false
id("com.android.library") version "8.1.2" apply false id("com.android.library") version "8.1.2" apply false
id("org.jetbrains.kotlin.android") version "1.8.21" apply false id("org.jetbrains.kotlin.android") version "1.9.20" apply false
} }
tasks.register("clean").configure { tasks.register("clean").configure {

View File

@ -18,9 +18,7 @@ constexpr auto INCREMENT_TIME{5ms};
DeviceSession::DeviceSession(Core::System& system_) DeviceSession::DeviceSession(Core::System& system_)
: system{system_}, thread_event{Core::Timing::CreateEvent( : system{system_}, thread_event{Core::Timing::CreateEvent(
"AudioOutSampleTick", "AudioOutSampleTick",
[this](std::uintptr_t, s64 time, std::chrono::nanoseconds) { [this](s64 time, std::chrono::nanoseconds) { return ThreadFunc(); })} {}
return ThreadFunc();
})} {}
DeviceSession::~DeviceSession() { DeviceSession::~DeviceSession() {
Finalize(); Finalize();

View File

@ -64,6 +64,8 @@ add_library(common STATIC
fs/path_util.cpp fs/path_util.cpp
fs/path_util.h fs/path_util.h
hash.h hash.h
heap_tracker.cpp
heap_tracker.h
hex_util.cpp hex_util.cpp
hex_util.h hex_util.h
host_memory.cpp host_memory.cpp

281
src/common/heap_tracker.cpp Normal file
View File

@ -0,0 +1,281 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <fstream>
#include <vector>
#include "common/heap_tracker.h"
#include "common/logging/log.h"
namespace Common {
namespace {
s64 GetMaxPermissibleResidentMapCount() {
// Default value.
s64 value = 65530;
// Try to read how many mappings we can make.
std::ifstream s("/proc/sys/vm/max_map_count");
s >> value;
// Print, for debug.
LOG_INFO(HW_Memory, "Current maximum map count: {}", value);
// Allow 20000 maps for other code and to account for split inaccuracy.
return std::max<s64>(value - 20000, 0);
}
} // namespace
HeapTracker::HeapTracker(Common::HostMemory& buffer)
: m_buffer(buffer), m_max_resident_map_count(GetMaxPermissibleResidentMapCount()) {}
HeapTracker::~HeapTracker() = default;
void HeapTracker::Map(size_t virtual_offset, size_t host_offset, size_t length,
MemoryPermission perm, bool is_separate_heap) {
// When mapping other memory, map pages immediately.
if (!is_separate_heap) {
m_buffer.Map(virtual_offset, host_offset, length, perm, false);
return;
}
{
// We are mapping part of a separate heap.
std::scoped_lock lk{m_lock};
auto* const map = new SeparateHeapMap{
.vaddr = virtual_offset,
.paddr = host_offset,
.size = length,
.tick = m_tick++,
.perm = perm,
.is_resident = false,
};
// Insert into mappings.
m_map_count++;
m_mappings.insert(*map);
}
// Finally, map.
this->DeferredMapSeparateHeap(virtual_offset);
}
void HeapTracker::Unmap(size_t virtual_offset, size_t size, bool is_separate_heap) {
// If this is a separate heap...
if (is_separate_heap) {
std::scoped_lock lk{m_lock};
const SeparateHeapMap key{
.vaddr = virtual_offset,
};
// Split at the boundaries of the region we are removing.
this->SplitHeapMapLocked(virtual_offset);
this->SplitHeapMapLocked(virtual_offset + size);
// Erase all mappings in range.
auto it = m_mappings.find(key);
while (it != m_mappings.end() && it->vaddr < virtual_offset + size) {
// Get underlying item.
auto* const item = std::addressof(*it);
// If resident, erase from resident map.
if (item->is_resident) {
ASSERT(--m_resident_map_count >= 0);
m_resident_mappings.erase(m_resident_mappings.iterator_to(*item));
}
// Erase from map.
ASSERT(--m_map_count >= 0);
it = m_mappings.erase(it);
// Free the item.
delete item;
}
}
// Unmap pages.
m_buffer.Unmap(virtual_offset, size, false);
}
void HeapTracker::Protect(size_t virtual_offset, size_t size, MemoryPermission perm) {
// Ensure no rebuild occurs while reprotecting.
std::shared_lock lk{m_rebuild_lock};
// Split at the boundaries of the region we are reprotecting.
this->SplitHeapMap(virtual_offset, size);
// Declare tracking variables.
const VAddr end = virtual_offset + size;
VAddr cur = virtual_offset;
while (cur < end) {
VAddr next = cur;
bool should_protect = false;
{
std::scoped_lock lk2{m_lock};
const SeparateHeapMap key{
.vaddr = next,
};
// Try to get the next mapping corresponding to this address.
const auto it = m_mappings.nfind(key);
if (it == m_mappings.end()) {
// There are no separate heap mappings remaining.
next = end;
should_protect = true;
} else if (it->vaddr == cur) {
// We are in range.
// Update permission bits.
it->perm = perm;
// Determine next address and whether we should protect.
next = cur + it->size;
should_protect = it->is_resident;
} else /* if (it->vaddr > cur) */ {
// We weren't in range, but there is a block coming up that will be.
next = it->vaddr;
should_protect = true;
}
}
// Clamp to end.
next = std::min(next, end);
// Reprotect, if we need to.
if (should_protect) {
m_buffer.Protect(cur, next - cur, perm);
}
// Advance.
cur = next;
}
}
bool HeapTracker::DeferredMapSeparateHeap(u8* fault_address) {
if (m_buffer.IsInVirtualRange(fault_address)) {
return this->DeferredMapSeparateHeap(fault_address - m_buffer.VirtualBasePointer());
}
return false;
}
bool HeapTracker::DeferredMapSeparateHeap(size_t virtual_offset) {
bool rebuild_required = false;
{
std::scoped_lock lk{m_lock};
// Check to ensure this was a non-resident separate heap mapping.
const auto it = this->GetNearestHeapMapLocked(virtual_offset);
if (it == m_mappings.end() || it->is_resident) {
return false;
}
// Update tick before possible rebuild.
it->tick = m_tick++;
// Check if we need to rebuild.
if (m_resident_map_count > m_max_resident_map_count) {
rebuild_required = true;
}
// Map the area.
m_buffer.Map(it->vaddr, it->paddr, it->size, it->perm, false);
// This map is now resident.
it->is_resident = true;
m_resident_map_count++;
m_resident_mappings.insert(*it);
}
if (rebuild_required) {
// A rebuild was required, so perform it now.
this->RebuildSeparateHeapAddressSpace();
}
return true;
}
void HeapTracker::RebuildSeparateHeapAddressSpace() {
std::scoped_lock lk{m_rebuild_lock, m_lock};
ASSERT(!m_resident_mappings.empty());
// Dump half of the mappings.
//
// Despite being worse in theory, this has proven to be better in practice than more
// regularly dumping a smaller amount, because it significantly reduces average case
// lock contention.
const size_t desired_count = std::min(m_resident_map_count, m_max_resident_map_count) / 2;
const size_t evict_count = m_resident_map_count - desired_count;
auto it = m_resident_mappings.begin();
for (size_t i = 0; i < evict_count && it != m_resident_mappings.end(); i++) {
// Unmark and unmap.
it->is_resident = false;
m_buffer.Unmap(it->vaddr, it->size, false);
// Advance.
ASSERT(--m_resident_map_count >= 0);
it = m_resident_mappings.erase(it);
}
}
void HeapTracker::SplitHeapMap(VAddr offset, size_t size) {
std::scoped_lock lk{m_lock};
this->SplitHeapMapLocked(offset);
this->SplitHeapMapLocked(offset + size);
}
void HeapTracker::SplitHeapMapLocked(VAddr offset) {
const auto it = this->GetNearestHeapMapLocked(offset);
if (it == m_mappings.end() || it->vaddr == offset) {
// Not contained or no split required.
return;
}
// Cache the original values.
auto* const left = std::addressof(*it);
const size_t orig_size = left->size;
// Adjust the left map.
const size_t left_size = offset - left->vaddr;
left->size = left_size;
// Create the new right map.
auto* const right = new SeparateHeapMap{
.vaddr = left->vaddr + left_size,
.paddr = left->paddr + left_size,
.size = orig_size - left_size,
.tick = left->tick,
.perm = left->perm,
.is_resident = left->is_resident,
};
// Insert the new right map.
m_map_count++;
m_mappings.insert(*right);
// If resident, also insert into resident map.
if (right->is_resident) {
m_resident_map_count++;
m_resident_mappings.insert(*right);
}
}
HeapTracker::AddrTree::iterator HeapTracker::GetNearestHeapMapLocked(VAddr offset) {
const SeparateHeapMap key{
.vaddr = offset,
};
return m_mappings.find(key);
}
} // namespace Common

98
src/common/heap_tracker.h Normal file
View File

@ -0,0 +1,98 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <atomic>
#include <mutex>
#include <set>
#include <shared_mutex>
#include "common/host_memory.h"
#include "common/intrusive_red_black_tree.h"
namespace Common {
struct SeparateHeapMap {
Common::IntrusiveRedBlackTreeNode addr_node{};
Common::IntrusiveRedBlackTreeNode tick_node{};
VAddr vaddr{};
PAddr paddr{};
size_t size{};
size_t tick{};
MemoryPermission perm{};
bool is_resident{};
};
struct SeparateHeapMapAddrComparator {
static constexpr int Compare(const SeparateHeapMap& lhs, const SeparateHeapMap& rhs) {
if (lhs.vaddr < rhs.vaddr) {
return -1;
} else if (lhs.vaddr <= (rhs.vaddr + rhs.size - 1)) {
return 0;
} else {
return 1;
}
}
};
struct SeparateHeapMapTickComparator {
static constexpr int Compare(const SeparateHeapMap& lhs, const SeparateHeapMap& rhs) {
if (lhs.tick < rhs.tick) {
return -1;
} else if (lhs.tick > rhs.tick) {
return 1;
} else {
return SeparateHeapMapAddrComparator::Compare(lhs, rhs);
}
}
};
class HeapTracker {
public:
explicit HeapTracker(Common::HostMemory& buffer);
~HeapTracker();
void Map(size_t virtual_offset, size_t host_offset, size_t length, MemoryPermission perm,
bool is_separate_heap);
void Unmap(size_t virtual_offset, size_t size, bool is_separate_heap);
void Protect(size_t virtual_offset, size_t length, MemoryPermission perm);
u8* VirtualBasePointer() {
return m_buffer.VirtualBasePointer();
}
bool DeferredMapSeparateHeap(u8* fault_address);
bool DeferredMapSeparateHeap(size_t virtual_offset);
private:
using AddrTreeTraits =
Common::IntrusiveRedBlackTreeMemberTraitsDeferredAssert<&SeparateHeapMap::addr_node>;
using AddrTree = AddrTreeTraits::TreeType<SeparateHeapMapAddrComparator>;
using TickTreeTraits =
Common::IntrusiveRedBlackTreeMemberTraitsDeferredAssert<&SeparateHeapMap::tick_node>;
using TickTree = TickTreeTraits::TreeType<SeparateHeapMapTickComparator>;
AddrTree m_mappings{};
TickTree m_resident_mappings{};
private:
void SplitHeapMap(VAddr offset, size_t size);
void SplitHeapMapLocked(VAddr offset);
AddrTree::iterator GetNearestHeapMapLocked(VAddr offset);
void RebuildSeparateHeapAddressSpace();
private:
Common::HostMemory& m_buffer;
const s64 m_max_resident_map_count;
std::shared_mutex m_rebuild_lock{};
std::mutex m_lock{};
s64 m_map_count{};
s64 m_resident_map_count{};
size_t m_tick{};
};
} // namespace Common

View File

@ -679,7 +679,7 @@ HostMemory::HostMemory(HostMemory&&) noexcept = default;
HostMemory& HostMemory::operator=(HostMemory&&) noexcept = default; HostMemory& HostMemory::operator=(HostMemory&&) noexcept = default;
void HostMemory::Map(size_t virtual_offset, size_t host_offset, size_t length, void HostMemory::Map(size_t virtual_offset, size_t host_offset, size_t length,
MemoryPermission perms) { MemoryPermission perms, bool separate_heap) {
ASSERT(virtual_offset % PageAlignment == 0); ASSERT(virtual_offset % PageAlignment == 0);
ASSERT(host_offset % PageAlignment == 0); ASSERT(host_offset % PageAlignment == 0);
ASSERT(length % PageAlignment == 0); ASSERT(length % PageAlignment == 0);
@ -691,7 +691,7 @@ void HostMemory::Map(size_t virtual_offset, size_t host_offset, size_t length,
impl->Map(virtual_offset + virtual_base_offset, host_offset, length, perms); impl->Map(virtual_offset + virtual_base_offset, host_offset, length, perms);
} }
void HostMemory::Unmap(size_t virtual_offset, size_t length) { void HostMemory::Unmap(size_t virtual_offset, size_t length, bool separate_heap) {
ASSERT(virtual_offset % PageAlignment == 0); ASSERT(virtual_offset % PageAlignment == 0);
ASSERT(length % PageAlignment == 0); ASSERT(length % PageAlignment == 0);
ASSERT(virtual_offset + length <= virtual_size); ASSERT(virtual_offset + length <= virtual_size);
@ -701,14 +701,16 @@ void HostMemory::Unmap(size_t virtual_offset, size_t length) {
impl->Unmap(virtual_offset + virtual_base_offset, length); impl->Unmap(virtual_offset + virtual_base_offset, length);
} }
void HostMemory::Protect(size_t virtual_offset, size_t length, bool read, bool write, void HostMemory::Protect(size_t virtual_offset, size_t length, MemoryPermission perm) {
bool execute) {
ASSERT(virtual_offset % PageAlignment == 0); ASSERT(virtual_offset % PageAlignment == 0);
ASSERT(length % PageAlignment == 0); ASSERT(length % PageAlignment == 0);
ASSERT(virtual_offset + length <= virtual_size); ASSERT(virtual_offset + length <= virtual_size);
if (length == 0 || !virtual_base || !impl) { if (length == 0 || !virtual_base || !impl) {
return; return;
} }
const bool read = True(perm & MemoryPermission::Read);
const bool write = True(perm & MemoryPermission::Write);
const bool execute = True(perm & MemoryPermission::Execute);
impl->Protect(virtual_offset + virtual_base_offset, length, read, write, execute); impl->Protect(virtual_offset + virtual_base_offset, length, read, write, execute);
} }

View File

@ -40,11 +40,12 @@ public:
HostMemory(HostMemory&& other) noexcept; HostMemory(HostMemory&& other) noexcept;
HostMemory& operator=(HostMemory&& other) noexcept; HostMemory& operator=(HostMemory&& other) noexcept;
void Map(size_t virtual_offset, size_t host_offset, size_t length, MemoryPermission perms); void Map(size_t virtual_offset, size_t host_offset, size_t length, MemoryPermission perms,
bool separate_heap);
void Unmap(size_t virtual_offset, size_t length); void Unmap(size_t virtual_offset, size_t length, bool separate_heap);
void Protect(size_t virtual_offset, size_t length, bool read, bool write, bool execute = false); void Protect(size_t virtual_offset, size_t length, MemoryPermission perms);
void EnableDirectMappedAddress(); void EnableDirectMappedAddress();
@ -64,6 +65,10 @@ public:
return virtual_base; return virtual_base;
} }
bool IsInVirtualRange(void* address) const noexcept {
return address >= virtual_base && address < virtual_base + virtual_size;
}
private: private:
size_t backing_size{}; size_t backing_size{};
size_t virtual_size{}; size_t virtual_size{};

View File

@ -199,6 +199,8 @@ const char* TranslateCategory(Category category) {
case Category::CpuDebug: case Category::CpuDebug:
case Category::CpuUnsafe: case Category::CpuUnsafe:
return "Cpu"; return "Cpu";
case Category::Overlay:
return "Overlay";
case Category::Renderer: case Category::Renderer:
case Category::RendererAdvanced: case Category::RendererAdvanced:
case Category::RendererDebug: case Category::RendererDebug:

View File

@ -18,6 +18,7 @@ enum class Category : u32 {
Cpu, Cpu,
CpuDebug, CpuDebug,
CpuUnsafe, CpuUnsafe,
Overlay,
Renderer, Renderer,
RendererAdvanced, RendererAdvanced,
RendererDebug, RendererDebug,

View File

@ -978,6 +978,7 @@ endif()
if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64) if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64)
target_sources(core PRIVATE target_sources(core PRIVATE
arm/dynarmic/arm_dynarmic.cpp
arm/dynarmic/arm_dynarmic.h arm/dynarmic/arm_dynarmic.h
arm/dynarmic/arm_dynarmic_64.cpp arm/dynarmic/arm_dynarmic_64.cpp
arm/dynarmic/arm_dynarmic_64.h arm/dynarmic/arm_dynarmic_64.h
@ -987,6 +988,8 @@ if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64)
arm/dynarmic/dynarmic_cp15.h arm/dynarmic/dynarmic_cp15.h
arm/dynarmic/dynarmic_exclusive_monitor.cpp arm/dynarmic/dynarmic_exclusive_monitor.cpp
arm/dynarmic/dynarmic_exclusive_monitor.h arm/dynarmic/dynarmic_exclusive_monitor.h
hle/service/jit/jit_code_memory.cpp
hle/service/jit/jit_code_memory.h
hle/service/jit/jit_context.cpp hle/service/jit/jit_context.cpp
hle/service/jit/jit_context.h hle/service/jit/jit_context.h
hle/service/jit/jit.cpp hle/service/jit/jit.cpp

View File

@ -0,0 +1,49 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#ifdef __linux__
#include "common/signal_chain.h"
#include "core/arm/dynarmic/arm_dynarmic.h"
#include "core/hle/kernel/k_process.h"
#include "core/memory.h"
namespace Core {
namespace {
thread_local Core::Memory::Memory* g_current_memory{};
std::once_flag g_registered{};
struct sigaction g_old_segv {};
void HandleSigSegv(int sig, siginfo_t* info, void* ctx) {
if (g_current_memory && g_current_memory->InvalidateSeparateHeap(info->si_addr)) {
return;
}
return g_old_segv.sa_sigaction(sig, info, ctx);
}
} // namespace
ScopedJitExecution::ScopedJitExecution(Kernel::KProcess* process) {
g_current_memory = std::addressof(process->GetMemory());
}
ScopedJitExecution::~ScopedJitExecution() {
g_current_memory = nullptr;
}
void ScopedJitExecution::RegisterHandler() {
std::call_once(g_registered, [] {
struct sigaction sa {};
sa.sa_sigaction = &HandleSigSegv;
sa.sa_flags = SA_SIGINFO | SA_ONSTACK;
Common::SigAction(SIGSEGV, std::addressof(sa), std::addressof(g_old_segv));
});
}
} // namespace Core
#endif

View File

@ -26,4 +26,24 @@ constexpr HaltReason TranslateHaltReason(Dynarmic::HaltReason hr) {
return static_cast<HaltReason>(hr); return static_cast<HaltReason>(hr);
} }
#ifdef __linux__
class ScopedJitExecution {
public:
explicit ScopedJitExecution(Kernel::KProcess* process);
~ScopedJitExecution();
static void RegisterHandler();
};
#else
class ScopedJitExecution {
public:
explicit ScopedJitExecution(Kernel::KProcess* process) {}
~ScopedJitExecution() {}
static void RegisterHandler() {}
};
#endif
} // namespace Core } // namespace Core

View File

@ -331,11 +331,15 @@ bool ArmDynarmic32::IsInThumbMode() const {
} }
HaltReason ArmDynarmic32::RunThread(Kernel::KThread* thread) { HaltReason ArmDynarmic32::RunThread(Kernel::KThread* thread) {
ScopedJitExecution sj(thread->GetOwnerProcess());
m_jit->ClearExclusiveState(); m_jit->ClearExclusiveState();
return TranslateHaltReason(m_jit->Run()); return TranslateHaltReason(m_jit->Run());
} }
HaltReason ArmDynarmic32::StepThread(Kernel::KThread* thread) { HaltReason ArmDynarmic32::StepThread(Kernel::KThread* thread) {
ScopedJitExecution sj(thread->GetOwnerProcess());
m_jit->ClearExclusiveState(); m_jit->ClearExclusiveState();
return TranslateHaltReason(m_jit->Step()); return TranslateHaltReason(m_jit->Step());
} }
@ -377,6 +381,7 @@ ArmDynarmic32::ArmDynarmic32(System& system, bool uses_wall_clock, Kernel::KProc
m_cp15(std::make_shared<DynarmicCP15>(*this)), m_core_index{core_index} { m_cp15(std::make_shared<DynarmicCP15>(*this)), m_core_index{core_index} {
auto& page_table_impl = process->GetPageTable().GetBasePageTable().GetImpl(); auto& page_table_impl = process->GetPageTable().GetBasePageTable().GetImpl();
m_jit = MakeJit(&page_table_impl); m_jit = MakeJit(&page_table_impl);
ScopedJitExecution::RegisterHandler();
} }
ArmDynarmic32::~ArmDynarmic32() = default; ArmDynarmic32::~ArmDynarmic32() = default;

View File

@ -362,11 +362,15 @@ std::shared_ptr<Dynarmic::A64::Jit> ArmDynarmic64::MakeJit(Common::PageTable* pa
} }
HaltReason ArmDynarmic64::RunThread(Kernel::KThread* thread) { HaltReason ArmDynarmic64::RunThread(Kernel::KThread* thread) {
ScopedJitExecution sj(thread->GetOwnerProcess());
m_jit->ClearExclusiveState(); m_jit->ClearExclusiveState();
return TranslateHaltReason(m_jit->Run()); return TranslateHaltReason(m_jit->Run());
} }
HaltReason ArmDynarmic64::StepThread(Kernel::KThread* thread) { HaltReason ArmDynarmic64::StepThread(Kernel::KThread* thread) {
ScopedJitExecution sj(thread->GetOwnerProcess());
m_jit->ClearExclusiveState(); m_jit->ClearExclusiveState();
return TranslateHaltReason(m_jit->Step()); return TranslateHaltReason(m_jit->Step());
} }
@ -406,6 +410,7 @@ ArmDynarmic64::ArmDynarmic64(System& system, bool uses_wall_clock, Kernel::KProc
auto& page_table = process->GetPageTable().GetBasePageTable(); auto& page_table = process->GetPageTable().GetBasePageTable();
auto& page_table_impl = page_table.GetImpl(); auto& page_table_impl = page_table.GetImpl();
m_jit = MakeJit(&page_table_impl, page_table.GetAddressSpaceWidth()); m_jit = MakeJit(&page_table_impl, page_table.GetAddressSpaceWidth());
ScopedJitExecution::RegisterHandler();
} }
ArmDynarmic64::~ArmDynarmic64() = default; ArmDynarmic64::~ArmDynarmic64() = default;

View File

@ -29,7 +29,6 @@ std::shared_ptr<EventType> CreateEvent(std::string name, TimedCallback&& callbac
struct CoreTiming::Event { struct CoreTiming::Event {
s64 time; s64 time;
u64 fifo_order; u64 fifo_order;
std::uintptr_t user_data;
std::weak_ptr<EventType> type; std::weak_ptr<EventType> type;
s64 reschedule_time; s64 reschedule_time;
heap_t::handle_type handle{}; heap_t::handle_type handle{};
@ -67,17 +66,15 @@ void CoreTiming::Initialize(std::function<void()>&& on_thread_init_) {
event_fifo_id = 0; event_fifo_id = 0;
shutting_down = false; shutting_down = false;
cpu_ticks = 0; cpu_ticks = 0;
const auto empty_timed_callback = [](std::uintptr_t, u64, std::chrono::nanoseconds)
-> std::optional<std::chrono::nanoseconds> { return std::nullopt; };
ev_lost = CreateEvent("_lost_event", empty_timed_callback);
if (is_multicore) { if (is_multicore) {
timer_thread = std::make_unique<std::jthread>(ThreadEntry, std::ref(*this)); timer_thread = std::make_unique<std::jthread>(ThreadEntry, std::ref(*this));
} }
} }
void CoreTiming::ClearPendingEvents() { void CoreTiming::ClearPendingEvents() {
std::scoped_lock lock{basic_lock}; std::scoped_lock lock{advance_lock, basic_lock};
event_queue.clear(); event_queue.clear();
event.Set();
} }
void CoreTiming::Pause(bool is_paused) { void CoreTiming::Pause(bool is_paused) {
@ -119,14 +116,12 @@ bool CoreTiming::HasPendingEvents() const {
} }
void CoreTiming::ScheduleEvent(std::chrono::nanoseconds ns_into_future, void CoreTiming::ScheduleEvent(std::chrono::nanoseconds ns_into_future,
const std::shared_ptr<EventType>& event_type, const std::shared_ptr<EventType>& event_type, bool absolute_time) {
std::uintptr_t user_data, bool absolute_time) {
{ {
std::scoped_lock scope{basic_lock}; std::scoped_lock scope{basic_lock};
const auto next_time{absolute_time ? ns_into_future : GetGlobalTimeNs() + ns_into_future}; const auto next_time{absolute_time ? ns_into_future : GetGlobalTimeNs() + ns_into_future};
auto h{event_queue.emplace( auto h{event_queue.emplace(Event{next_time.count(), event_fifo_id++, event_type, 0})};
Event{next_time.count(), event_fifo_id++, user_data, event_type, 0})};
(*h).handle = h; (*h).handle = h;
} }
@ -136,13 +131,13 @@ void CoreTiming::ScheduleEvent(std::chrono::nanoseconds ns_into_future,
void CoreTiming::ScheduleLoopingEvent(std::chrono::nanoseconds start_time, void CoreTiming::ScheduleLoopingEvent(std::chrono::nanoseconds start_time,
std::chrono::nanoseconds resched_time, std::chrono::nanoseconds resched_time,
const std::shared_ptr<EventType>& event_type, const std::shared_ptr<EventType>& event_type,
std::uintptr_t user_data, bool absolute_time) { bool absolute_time) {
{ {
std::scoped_lock scope{basic_lock}; std::scoped_lock scope{basic_lock};
const auto next_time{absolute_time ? start_time : GetGlobalTimeNs() + start_time}; const auto next_time{absolute_time ? start_time : GetGlobalTimeNs() + start_time};
auto h{event_queue.emplace(Event{next_time.count(), event_fifo_id++, user_data, event_type, auto h{event_queue.emplace(
resched_time.count()})}; Event{next_time.count(), event_fifo_id++, event_type, resched_time.count()})};
(*h).handle = h; (*h).handle = h;
} }
@ -150,14 +145,14 @@ void CoreTiming::ScheduleLoopingEvent(std::chrono::nanoseconds start_time,
} }
void CoreTiming::UnscheduleEvent(const std::shared_ptr<EventType>& event_type, void CoreTiming::UnscheduleEvent(const std::shared_ptr<EventType>& event_type,
std::uintptr_t user_data, bool wait) { UnscheduleEventType type) {
{ {
std::scoped_lock lk{basic_lock}; std::scoped_lock lk{basic_lock};
std::vector<heap_t::handle_type> to_remove; std::vector<heap_t::handle_type> to_remove;
for (auto itr = event_queue.begin(); itr != event_queue.end(); itr++) { for (auto itr = event_queue.begin(); itr != event_queue.end(); itr++) {
const Event& e = *itr; const Event& e = *itr;
if (e.type.lock().get() == event_type.get() && e.user_data == user_data) { if (e.type.lock().get() == event_type.get()) {
to_remove.push_back(itr->handle); to_remove.push_back(itr->handle);
} }
} }
@ -165,10 +160,12 @@ void CoreTiming::UnscheduleEvent(const std::shared_ptr<EventType>& event_type,
for (auto h : to_remove) { for (auto h : to_remove) {
event_queue.erase(h); event_queue.erase(h);
} }
event_type->sequence_number++;
} }
// Force any in-progress events to finish // Force any in-progress events to finish
if (wait) { if (type == UnscheduleEventType::Wait) {
std::scoped_lock lk{advance_lock}; std::scoped_lock lk{advance_lock};
} }
} }
@ -208,28 +205,31 @@ std::optional<s64> CoreTiming::Advance() {
const Event& evt = event_queue.top(); const Event& evt = event_queue.top();
if (const auto event_type{evt.type.lock()}) { if (const auto event_type{evt.type.lock()}) {
if (evt.reschedule_time == 0) { const auto evt_time = evt.time;
const auto evt_user_data = evt.user_data; const auto evt_sequence_num = event_type->sequence_number;
const auto evt_time = evt.time;
if (evt.reschedule_time == 0) {
event_queue.pop(); event_queue.pop();
basic_lock.unlock(); basic_lock.unlock();
event_type->callback( event_type->callback(
evt_user_data, evt_time, evt_time, std::chrono::nanoseconds{GetGlobalTimeNs().count() - evt_time});
std::chrono::nanoseconds{GetGlobalTimeNs().count() - evt_time});
basic_lock.lock(); basic_lock.lock();
} else { } else {
basic_lock.unlock(); basic_lock.unlock();
const auto new_schedule_time{event_type->callback( const auto new_schedule_time{event_type->callback(
evt.user_data, evt.time, evt_time, std::chrono::nanoseconds{GetGlobalTimeNs().count() - evt_time})};
std::chrono::nanoseconds{GetGlobalTimeNs().count() - evt.time})};
basic_lock.lock(); basic_lock.lock();
if (evt_sequence_num != event_type->sequence_number) {
// Heap handle is invalidated after external modification.
continue;
}
const auto next_schedule_time{new_schedule_time.has_value() const auto next_schedule_time{new_schedule_time.has_value()
? new_schedule_time.value().count() ? new_schedule_time.value().count()
: evt.reschedule_time}; : evt.reschedule_time};
@ -241,8 +241,8 @@ std::optional<s64> CoreTiming::Advance() {
next_time = pause_end_time + next_schedule_time; next_time = pause_end_time + next_schedule_time;
} }
event_queue.update(evt.handle, Event{next_time, event_fifo_id++, evt.user_data, event_queue.update(evt.handle, Event{next_time, event_fifo_id++, evt.type,
evt.type, next_schedule_time, evt.handle}); next_schedule_time, evt.handle});
} }
} }

View File

@ -22,17 +22,25 @@ namespace Core::Timing {
/// A callback that may be scheduled for a particular core timing event. /// A callback that may be scheduled for a particular core timing event.
using TimedCallback = std::function<std::optional<std::chrono::nanoseconds>( using TimedCallback = std::function<std::optional<std::chrono::nanoseconds>(
std::uintptr_t user_data, s64 time, std::chrono::nanoseconds ns_late)>; s64 time, std::chrono::nanoseconds ns_late)>;
/// Contains the characteristics of a particular event. /// Contains the characteristics of a particular event.
struct EventType { struct EventType {
explicit EventType(TimedCallback&& callback_, std::string&& name_) explicit EventType(TimedCallback&& callback_, std::string&& name_)
: callback{std::move(callback_)}, name{std::move(name_)} {} : callback{std::move(callback_)}, name{std::move(name_)}, sequence_number{0} {}
/// The event's callback function. /// The event's callback function.
TimedCallback callback; TimedCallback callback;
/// A pointer to the name of the event. /// A pointer to the name of the event.
const std::string name; const std::string name;
/// A monotonic sequence number, incremented when this event is
/// changed externally.
size_t sequence_number;
};
enum class UnscheduleEventType {
Wait,
NoWait,
}; };
/** /**
@ -89,23 +97,17 @@ public:
/// Schedules an event in core timing /// Schedules an event in core timing
void ScheduleEvent(std::chrono::nanoseconds ns_into_future, void ScheduleEvent(std::chrono::nanoseconds ns_into_future,
const std::shared_ptr<EventType>& event_type, std::uintptr_t user_data = 0, const std::shared_ptr<EventType>& event_type, bool absolute_time = false);
bool absolute_time = false);
/// Schedules an event which will automatically re-schedule itself with the given time, until /// Schedules an event which will automatically re-schedule itself with the given time, until
/// unscheduled /// unscheduled
void ScheduleLoopingEvent(std::chrono::nanoseconds start_time, void ScheduleLoopingEvent(std::chrono::nanoseconds start_time,
std::chrono::nanoseconds resched_time, std::chrono::nanoseconds resched_time,
const std::shared_ptr<EventType>& event_type, const std::shared_ptr<EventType>& event_type,
std::uintptr_t user_data = 0, bool absolute_time = false); bool absolute_time = false);
void UnscheduleEvent(const std::shared_ptr<EventType>& event_type, std::uintptr_t user_data, void UnscheduleEvent(const std::shared_ptr<EventType>& event_type,
bool wait = true); UnscheduleEventType type = UnscheduleEventType::Wait);
void UnscheduleEventWithoutWait(const std::shared_ptr<EventType>& event_type,
std::uintptr_t user_data) {
UnscheduleEvent(event_type, user_data, false);
}
void AddTicks(u64 ticks_to_add); void AddTicks(u64 ticks_to_add);
@ -158,7 +160,6 @@ private:
heap_t event_queue; heap_t event_queue;
u64 event_fifo_id = 0; u64 event_fifo_id = 0;
std::shared_ptr<EventType> ev_lost;
Common::Event event{}; Common::Event event{};
Common::Event pause_event{}; Common::Event pause_event{};
mutable std::mutex basic_lock; mutable std::mutex basic_lock;

View File

@ -73,6 +73,9 @@ VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips) {
return nullptr; return nullptr;
auto in_data = in->ReadAllBytes(); auto in_data = in->ReadAllBytes();
if (in_data.size() == 0) {
return nullptr;
}
std::vector<u8> temp(type == IPSFileType::IPS ? 3 : 4); std::vector<u8> temp(type == IPSFileType::IPS ? 3 : 4);
u64 offset = 5; // After header u64 offset = 5; // After header
@ -88,6 +91,10 @@ VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips) {
else else
real_offset = (temp[0] << 16) | (temp[1] << 8) | temp[2]; real_offset = (temp[0] << 16) | (temp[1] << 8) | temp[2];
if (real_offset > in_data.size()) {
return nullptr;
}
u16 data_size{}; u16 data_size{};
if (ips->ReadObject(&data_size, offset) != sizeof(u16)) if (ips->ReadObject(&data_size, offset) != sizeof(u16))
return nullptr; return nullptr;

View File

@ -10,15 +10,15 @@ namespace Kernel {
void KHardwareTimer::Initialize() { void KHardwareTimer::Initialize() {
// Create the timing callback to register with CoreTiming. // Create the timing callback to register with CoreTiming.
m_event_type = Core::Timing::CreateEvent( m_event_type = Core::Timing::CreateEvent("KHardwareTimer::Callback",
"KHardwareTimer::Callback", [](std::uintptr_t timer_handle, s64, std::chrono::nanoseconds) { [this](s64, std::chrono::nanoseconds) {
reinterpret_cast<KHardwareTimer*>(timer_handle)->DoTask(); this->DoTask();
return std::nullopt; return std::nullopt;
}); });
} }
void KHardwareTimer::Finalize() { void KHardwareTimer::Finalize() {
m_kernel.System().CoreTiming().UnscheduleEvent(m_event_type, reinterpret_cast<uintptr_t>(this)); m_kernel.System().CoreTiming().UnscheduleEvent(m_event_type);
m_wakeup_time = std::numeric_limits<s64>::max(); m_wakeup_time = std::numeric_limits<s64>::max();
m_event_type.reset(); m_event_type.reset();
} }
@ -57,13 +57,12 @@ void KHardwareTimer::EnableInterrupt(s64 wakeup_time) {
m_wakeup_time = wakeup_time; m_wakeup_time = wakeup_time;
m_kernel.System().CoreTiming().ScheduleEvent(std::chrono::nanoseconds{m_wakeup_time}, m_kernel.System().CoreTiming().ScheduleEvent(std::chrono::nanoseconds{m_wakeup_time},
m_event_type, reinterpret_cast<uintptr_t>(this), m_event_type, true);
true);
} }
void KHardwareTimer::DisableInterrupt() { void KHardwareTimer::DisableInterrupt() {
m_kernel.System().CoreTiming().UnscheduleEventWithoutWait(m_event_type, m_kernel.System().CoreTiming().UnscheduleEvent(m_event_type,
reinterpret_cast<uintptr_t>(this)); Core::Timing::UnscheduleEventType::NoWait);
m_wakeup_time = std::numeric_limits<s64>::max(); m_wakeup_time = std::numeric_limits<s64>::max();
} }

View File

@ -434,7 +434,7 @@ Result KPageTableBase::InitializeForProcess(Svc::CreateProcessFlag as_type, bool
void KPageTableBase::Finalize() { void KPageTableBase::Finalize() {
auto HostUnmapCallback = [&](KProcessAddress addr, u64 size) { auto HostUnmapCallback = [&](KProcessAddress addr, u64 size) {
if (Settings::IsFastmemEnabled()) { if (Settings::IsFastmemEnabled()) {
m_system.DeviceMemory().buffer.Unmap(GetInteger(addr), size); m_system.DeviceMemory().buffer.Unmap(GetInteger(addr), size, false);
} }
}; };
@ -5243,7 +5243,7 @@ Result KPageTableBase::MapPhysicalMemory(KProcessAddress address, size_t size) {
// Unmap. // Unmap.
R_ASSERT(this->Operate(updater.GetPageList(), cur_address, R_ASSERT(this->Operate(updater.GetPageList(), cur_address,
cur_pages, 0, false, unmap_properties, cur_pages, 0, false, unmap_properties,
OperationType::Unmap, true)); OperationType::UnmapPhysical, true));
} }
// Check if we're done. // Check if we're done.
@ -5326,7 +5326,7 @@ Result KPageTableBase::MapPhysicalMemory(KProcessAddress address, size_t size) {
// Map the papges. // Map the papges.
R_TRY(this->Operate(updater.GetPageList(), cur_address, map_pages, R_TRY(this->Operate(updater.GetPageList(), cur_address, map_pages,
cur_pg, map_properties, cur_pg, map_properties,
OperationType::MapFirstGroup, false)); OperationType::MapFirstGroupPhysical, false));
} }
} }
@ -5480,7 +5480,7 @@ Result KPageTableBase::UnmapPhysicalMemory(KProcessAddress address, size_t size)
// Unmap. // Unmap.
R_ASSERT(this->Operate(updater.GetPageList(), cur_address, cur_pages, 0, false, R_ASSERT(this->Operate(updater.GetPageList(), cur_address, cur_pages, 0, false,
unmap_properties, OperationType::Unmap, false)); unmap_properties, OperationType::UnmapPhysical, false));
} }
// Check if we're done. // Check if we're done.
@ -5655,7 +5655,10 @@ Result KPageTableBase::Operate(PageLinkedList* page_list, KProcessAddress virt_a
// or free them to the page list, and so it goes unused (along with page properties). // or free them to the page list, and so it goes unused (along with page properties).
switch (operation) { switch (operation) {
case OperationType::Unmap: { case OperationType::Unmap:
case OperationType::UnmapPhysical: {
const bool separate_heap = operation == OperationType::UnmapPhysical;
// Ensure that any pages we track are closed on exit. // Ensure that any pages we track are closed on exit.
KPageGroup pages_to_close(m_kernel, this->GetBlockInfoManager()); KPageGroup pages_to_close(m_kernel, this->GetBlockInfoManager());
SCOPE_EXIT({ pages_to_close.CloseAndReset(); }); SCOPE_EXIT({ pages_to_close.CloseAndReset(); });
@ -5664,7 +5667,7 @@ Result KPageTableBase::Operate(PageLinkedList* page_list, KProcessAddress virt_a
this->MakePageGroup(pages_to_close, virt_addr, num_pages); this->MakePageGroup(pages_to_close, virt_addr, num_pages);
// Unmap. // Unmap.
m_memory->UnmapRegion(*m_impl, virt_addr, num_pages * PageSize); m_memory->UnmapRegion(*m_impl, virt_addr, num_pages * PageSize, separate_heap);
R_SUCCEED(); R_SUCCEED();
} }
@ -5672,7 +5675,7 @@ Result KPageTableBase::Operate(PageLinkedList* page_list, KProcessAddress virt_a
ASSERT(virt_addr != 0); ASSERT(virt_addr != 0);
ASSERT(Common::IsAligned(GetInteger(virt_addr), PageSize)); ASSERT(Common::IsAligned(GetInteger(virt_addr), PageSize));
m_memory->MapMemoryRegion(*m_impl, virt_addr, num_pages * PageSize, phys_addr, m_memory->MapMemoryRegion(*m_impl, virt_addr, num_pages * PageSize, phys_addr,
ConvertToMemoryPermission(properties.perm)); ConvertToMemoryPermission(properties.perm), false);
// Open references to pages, if we should. // Open references to pages, if we should.
if (this->IsHeapPhysicalAddress(phys_addr)) { if (this->IsHeapPhysicalAddress(phys_addr)) {
@ -5711,16 +5714,19 @@ Result KPageTableBase::Operate(PageLinkedList* page_list, KProcessAddress virt_a
switch (operation) { switch (operation) {
case OperationType::MapGroup: case OperationType::MapGroup:
case OperationType::MapFirstGroup: { case OperationType::MapFirstGroup:
case OperationType::MapFirstGroupPhysical: {
const bool separate_heap = operation == OperationType::MapFirstGroupPhysical;
// We want to maintain a new reference to every page in the group. // We want to maintain a new reference to every page in the group.
KScopedPageGroup spg(page_group, operation != OperationType::MapFirstGroup); KScopedPageGroup spg(page_group, operation == OperationType::MapGroup);
for (const auto& node : page_group) { for (const auto& node : page_group) {
const size_t size{node.GetNumPages() * PageSize}; const size_t size{node.GetNumPages() * PageSize};
// Map the pages. // Map the pages.
m_memory->MapMemoryRegion(*m_impl, virt_addr, size, node.GetAddress(), m_memory->MapMemoryRegion(*m_impl, virt_addr, size, node.GetAddress(),
ConvertToMemoryPermission(properties.perm)); ConvertToMemoryPermission(properties.perm), separate_heap);
virt_addr += size; virt_addr += size;
} }

View File

@ -104,6 +104,9 @@ protected:
ChangePermissionsAndRefresh = 5, ChangePermissionsAndRefresh = 5,
ChangePermissionsAndRefreshAndFlush = 6, ChangePermissionsAndRefreshAndFlush = 6,
Separate = 7, Separate = 7,
MapFirstGroupPhysical = 65000,
UnmapPhysical = 65001,
}; };
static constexpr size_t MaxPhysicalMapAlignment = 1_GiB; static constexpr size_t MaxPhysicalMapAlignment = 1_GiB;

View File

@ -1237,8 +1237,10 @@ void KProcess::LoadModule(CodeSet code_set, KProcessAddress base_addr) {
auto& buffer = m_kernel.System().DeviceMemory().buffer; auto& buffer = m_kernel.System().DeviceMemory().buffer;
const auto& code = code_set.CodeSegment(); const auto& code = code_set.CodeSegment();
const auto& patch = code_set.PatchSegment(); const auto& patch = code_set.PatchSegment();
buffer.Protect(GetInteger(base_addr + code.addr), code.size, true, true, true); buffer.Protect(GetInteger(base_addr + code.addr), code.size,
buffer.Protect(GetInteger(base_addr + patch.addr), patch.size, true, true, true); Common::MemoryPermission::Read | Common::MemoryPermission::Execute);
buffer.Protect(GetInteger(base_addr + patch.addr), patch.size,
Common::MemoryPermission::Read | Common::MemoryPermission::Execute);
ReprotectSegment(code_set.PatchSegment(), Svc::MemoryPermission::None); ReprotectSegment(code_set.PatchSegment(), Svc::MemoryPermission::None);
} }
#endif #endif

View File

@ -238,7 +238,7 @@ struct KernelCore::Impl {
void InitializePreemption(KernelCore& kernel) { void InitializePreemption(KernelCore& kernel) {
preemption_event = Core::Timing::CreateEvent( preemption_event = Core::Timing::CreateEvent(
"PreemptionCallback", "PreemptionCallback",
[this, &kernel](std::uintptr_t, s64 time, [this, &kernel](s64 time,
std::chrono::nanoseconds) -> std::optional<std::chrono::nanoseconds> { std::chrono::nanoseconds) -> std::optional<std::chrono::nanoseconds> {
{ {
KScopedSchedulerLock lock(kernel); KScopedSchedulerLock lock(kernel);

View File

@ -49,10 +49,10 @@ HidBus::HidBus(Core::System& system_)
// Register update callbacks // Register update callbacks
hidbus_update_event = Core::Timing::CreateEvent( hidbus_update_event = Core::Timing::CreateEvent(
"Hidbus::UpdateCallback", "Hidbus::UpdateCallback",
[this](std::uintptr_t user_data, s64 time, [this](s64 time,
std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> { std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
const auto guard = LockService(); const auto guard = LockService();
UpdateHidbus(user_data, ns_late); UpdateHidbus(ns_late);
return std::nullopt; return std::nullopt;
}); });
@ -61,10 +61,10 @@ HidBus::HidBus(Core::System& system_)
} }
HidBus::~HidBus() { HidBus::~HidBus() {
system.CoreTiming().UnscheduleEvent(hidbus_update_event, 0); system.CoreTiming().UnscheduleEvent(hidbus_update_event);
} }
void HidBus::UpdateHidbus(std::uintptr_t user_data, std::chrono::nanoseconds ns_late) { void HidBus::UpdateHidbus(std::chrono::nanoseconds ns_late) {
if (is_hidbus_enabled) { if (is_hidbus_enabled) {
for (std::size_t i = 0; i < devices.size(); ++i) { for (std::size_t i = 0; i < devices.size(); ++i) {
if (!devices[i].is_device_initializated) { if (!devices[i].is_device_initializated) {

View File

@ -108,7 +108,7 @@ private:
void DisableJoyPollingReceiveMode(HLERequestContext& ctx); void DisableJoyPollingReceiveMode(HLERequestContext& ctx);
void SetStatusManagerType(HLERequestContext& ctx); void SetStatusManagerType(HLERequestContext& ctx);
void UpdateHidbus(std::uintptr_t user_data, std::chrono::nanoseconds ns_late); void UpdateHidbus(std::chrono::nanoseconds ns_late);
std::optional<std::size_t> GetDeviceIndexFromHandle(BusHandle handle) const; std::optional<std::size_t> GetDeviceIndexFromHandle(BusHandle handle) const;
template <typename T> template <typename T>

View File

@ -227,8 +227,7 @@ void ResourceManager::EnableTouchScreen(u64 aruid, bool is_enabled) {
applet_resource->EnableTouchScreen(aruid, is_enabled); applet_resource->EnableTouchScreen(aruid, is_enabled);
} }
void ResourceManager::UpdateControllers(std::uintptr_t user_data, void ResourceManager::UpdateControllers(std::chrono::nanoseconds ns_late) {
std::chrono::nanoseconds ns_late) {
auto& core_timing = system.CoreTiming(); auto& core_timing = system.CoreTiming();
debug_pad->OnUpdate(core_timing); debug_pad->OnUpdate(core_timing);
digitizer->OnUpdate(core_timing); digitizer->OnUpdate(core_timing);
@ -241,20 +240,19 @@ void ResourceManager::UpdateControllers(std::uintptr_t user_data,
capture_button->OnUpdate(core_timing); capture_button->OnUpdate(core_timing);
} }
void ResourceManager::UpdateNpad(std::uintptr_t user_data, std::chrono::nanoseconds ns_late) { void ResourceManager::UpdateNpad(std::chrono::nanoseconds ns_late) {
auto& core_timing = system.CoreTiming(); auto& core_timing = system.CoreTiming();
npad->OnUpdate(core_timing); npad->OnUpdate(core_timing);
} }
void ResourceManager::UpdateMouseKeyboard(std::uintptr_t user_data, void ResourceManager::UpdateMouseKeyboard(std::chrono::nanoseconds ns_late) {
std::chrono::nanoseconds ns_late) {
auto& core_timing = system.CoreTiming(); auto& core_timing = system.CoreTiming();
mouse->OnUpdate(core_timing); mouse->OnUpdate(core_timing);
debug_mouse->OnUpdate(core_timing); debug_mouse->OnUpdate(core_timing);
keyboard->OnUpdate(core_timing); keyboard->OnUpdate(core_timing);
} }
void ResourceManager::UpdateMotion(std::uintptr_t user_data, std::chrono::nanoseconds ns_late) { void ResourceManager::UpdateMotion(std::chrono::nanoseconds ns_late) {
auto& core_timing = system.CoreTiming(); auto& core_timing = system.CoreTiming();
six_axis->OnUpdate(core_timing); six_axis->OnUpdate(core_timing);
seven_six_axis->OnUpdate(core_timing); seven_six_axis->OnUpdate(core_timing);
@ -273,34 +271,34 @@ IAppletResource::IAppletResource(Core::System& system_, std::shared_ptr<Resource
// Register update callbacks // Register update callbacks
npad_update_event = Core::Timing::CreateEvent( npad_update_event = Core::Timing::CreateEvent(
"HID::UpdatePadCallback", "HID::UpdatePadCallback",
[this, resource](std::uintptr_t user_data, s64 time, std::chrono::nanoseconds ns_late) [this, resource](
-> std::optional<std::chrono::nanoseconds> { s64 time, std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
const auto guard = LockService(); const auto guard = LockService();
resource->UpdateNpad(user_data, ns_late); resource->UpdateNpad(ns_late);
return std::nullopt; return std::nullopt;
}); });
default_update_event = Core::Timing::CreateEvent( default_update_event = Core::Timing::CreateEvent(
"HID::UpdateDefaultCallback", "HID::UpdateDefaultCallback",
[this, resource](std::uintptr_t user_data, s64 time, std::chrono::nanoseconds ns_late) [this, resource](
-> std::optional<std::chrono::nanoseconds> { s64 time, std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
const auto guard = LockService(); const auto guard = LockService();
resource->UpdateControllers(user_data, ns_late); resource->UpdateControllers(ns_late);
return std::nullopt; return std::nullopt;
}); });
mouse_keyboard_update_event = Core::Timing::CreateEvent( mouse_keyboard_update_event = Core::Timing::CreateEvent(
"HID::UpdateMouseKeyboardCallback", "HID::UpdateMouseKeyboardCallback",
[this, resource](std::uintptr_t user_data, s64 time, std::chrono::nanoseconds ns_late) [this, resource](
-> std::optional<std::chrono::nanoseconds> { s64 time, std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
const auto guard = LockService(); const auto guard = LockService();
resource->UpdateMouseKeyboard(user_data, ns_late); resource->UpdateMouseKeyboard(ns_late);
return std::nullopt; return std::nullopt;
}); });
motion_update_event = Core::Timing::CreateEvent( motion_update_event = Core::Timing::CreateEvent(
"HID::UpdateMotionCallback", "HID::UpdateMotionCallback",
[this, resource](std::uintptr_t user_data, s64 time, std::chrono::nanoseconds ns_late) [this, resource](
-> std::optional<std::chrono::nanoseconds> { s64 time, std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
const auto guard = LockService(); const auto guard = LockService();
resource->UpdateMotion(user_data, ns_late); resource->UpdateMotion(ns_late);
return std::nullopt; return std::nullopt;
}); });
@ -314,10 +312,10 @@ IAppletResource::IAppletResource(Core::System& system_, std::shared_ptr<Resource
} }
IAppletResource::~IAppletResource() { IAppletResource::~IAppletResource() {
system.CoreTiming().UnscheduleEvent(npad_update_event, 0); system.CoreTiming().UnscheduleEvent(npad_update_event);
system.CoreTiming().UnscheduleEvent(default_update_event, 0); system.CoreTiming().UnscheduleEvent(default_update_event);
system.CoreTiming().UnscheduleEvent(mouse_keyboard_update_event, 0); system.CoreTiming().UnscheduleEvent(mouse_keyboard_update_event);
system.CoreTiming().UnscheduleEvent(motion_update_event, 0); system.CoreTiming().UnscheduleEvent(motion_update_event);
resource_manager->FreeAppletResourceId(aruid); resource_manager->FreeAppletResourceId(aruid);
} }

View File

@ -81,10 +81,10 @@ public:
void EnablePadInput(u64 aruid, bool is_enabled); void EnablePadInput(u64 aruid, bool is_enabled);
void EnableTouchScreen(u64 aruid, bool is_enabled); void EnableTouchScreen(u64 aruid, bool is_enabled);
void UpdateControllers(std::uintptr_t user_data, std::chrono::nanoseconds ns_late); void UpdateControllers(std::chrono::nanoseconds ns_late);
void UpdateNpad(std::uintptr_t user_data, std::chrono::nanoseconds ns_late); void UpdateNpad(std::chrono::nanoseconds ns_late);
void UpdateMouseKeyboard(std::uintptr_t user_data, std::chrono::nanoseconds ns_late); void UpdateMouseKeyboard(std::chrono::nanoseconds ns_late);
void UpdateMotion(std::uintptr_t user_data, std::chrono::nanoseconds ns_late); void UpdateMotion(std::chrono::nanoseconds ns_late);
private: private:
Result CreateAppletResourceImpl(u64 aruid); Result CreateAppletResourceImpl(u64 aruid);

View File

@ -4,11 +4,11 @@
#include "core/arm/debug.h" #include "core/arm/debug.h"
#include "core/arm/symbols.h" #include "core/arm/symbols.h"
#include "core/core.h" #include "core/core.h"
#include "core/hle/kernel/k_code_memory.h"
#include "core/hle/kernel/k_transfer_memory.h" #include "core/hle/kernel/k_transfer_memory.h"
#include "core/hle/result.h" #include "core/hle/result.h"
#include "core/hle/service/ipc_helpers.h" #include "core/hle/service/ipc_helpers.h"
#include "core/hle/service/jit/jit.h" #include "core/hle/service/jit/jit.h"
#include "core/hle/service/jit/jit_code_memory.h"
#include "core/hle/service/jit/jit_context.h" #include "core/hle/service/jit/jit_context.h"
#include "core/hle/service/server_manager.h" #include "core/hle/service/server_manager.h"
#include "core/hle/service/service.h" #include "core/hle/service/service.h"
@ -23,10 +23,12 @@ struct CodeRange {
class IJitEnvironment final : public ServiceFramework<IJitEnvironment> { class IJitEnvironment final : public ServiceFramework<IJitEnvironment> {
public: public:
explicit IJitEnvironment(Core::System& system_, Kernel::KProcess& process_, CodeRange user_rx, explicit IJitEnvironment(Core::System& system_,
CodeRange user_ro) Kernel::KScopedAutoObject<Kernel::KProcess>&& process_,
: ServiceFramework{system_, "IJitEnvironment"}, process{&process_}, CodeMemory&& user_rx_, CodeMemory&& user_ro_)
context{process->GetMemory()} { : ServiceFramework{system_, "IJitEnvironment"}, process{std::move(process_)},
user_rx{std::move(user_rx_)}, user_ro{std::move(user_ro_)},
context{system_.ApplicationMemory()} {
// clang-format off // clang-format off
static const FunctionInfo functions[] = { static const FunctionInfo functions[] = {
{0, &IJitEnvironment::GenerateCode, "GenerateCode"}, {0, &IJitEnvironment::GenerateCode, "GenerateCode"},
@ -39,10 +41,13 @@ public:
RegisterHandlers(functions); RegisterHandlers(functions);
// Identity map user code range into sysmodule context // Identity map user code range into sysmodule context
configuration.user_ro_memory = user_ro; configuration.user_rx_memory.size = user_rx.GetSize();
configuration.user_rx_memory = user_rx; configuration.user_rx_memory.offset = user_rx.GetAddress();
configuration.sys_ro_memory = user_ro; configuration.user_ro_memory.size = user_ro.GetSize();
configuration.sys_rx_memory = user_rx; configuration.user_ro_memory.offset = user_ro.GetAddress();
configuration.sys_rx_memory = configuration.user_rx_memory;
configuration.sys_ro_memory = configuration.user_ro_memory;
} }
void GenerateCode(HLERequestContext& ctx) { void GenerateCode(HLERequestContext& ctx) {
@ -318,6 +323,8 @@ private:
} }
Kernel::KScopedAutoObject<Kernel::KProcess> process; Kernel::KScopedAutoObject<Kernel::KProcess> process;
CodeMemory user_rx;
CodeMemory user_ro;
GuestCallbacks callbacks; GuestCallbacks callbacks;
JITConfiguration configuration; JITConfiguration configuration;
JITContext context; JITContext context;
@ -335,6 +342,7 @@ public:
RegisterHandlers(functions); RegisterHandlers(functions);
} }
private:
void CreateJitEnvironment(HLERequestContext& ctx) { void CreateJitEnvironment(HLERequestContext& ctx) {
LOG_DEBUG(Service_JIT, "called"); LOG_DEBUG(Service_JIT, "called");
@ -380,20 +388,35 @@ public:
return; return;
} }
const CodeRange user_rx{ CodeMemory rx, ro;
.offset = GetInteger(rx_mem->GetSourceAddress()), Result res;
.size = parameters.rx_size,
};
const CodeRange user_ro{ res = rx.Initialize(*process, *rx_mem, parameters.rx_size,
.offset = GetInteger(ro_mem->GetSourceAddress()), Kernel::Svc::MemoryPermission::ReadExecute, generate_random);
.size = parameters.ro_size, if (R_FAILED(res)) {
}; LOG_ERROR(Service_JIT, "rx_mem could not be mapped for handle=0x{:08X}", rx_mem_handle);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(res);
return;
}
res = ro.Initialize(*process, *ro_mem, parameters.ro_size,
Kernel::Svc::MemoryPermission::Read, generate_random);
if (R_FAILED(res)) {
LOG_ERROR(Service_JIT, "ro_mem could not be mapped for handle=0x{:08X}", ro_mem_handle);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(res);
return;
}
IPC::ResponseBuilder rb{ctx, 2, 0, 1}; IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.PushIpcInterface<IJitEnvironment>(system, *process, user_rx, user_ro); rb.PushIpcInterface<IJitEnvironment>(system, std::move(process), std::move(rx),
std::move(ro));
} }
private:
std::mt19937_64 generate_random{};
}; };
void LoopProcess(Core::System& system) { void LoopProcess(Core::System& system) {

View File

@ -0,0 +1,54 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/service/jit/jit_code_memory.h"
namespace Service::JIT {
Result CodeMemory::Initialize(Kernel::KProcess& process, Kernel::KCodeMemory& code_memory,
size_t size, Kernel::Svc::MemoryPermission perm,
std::mt19937_64& generate_random) {
auto& page_table = process.GetPageTable();
const u64 alias_code_start =
GetInteger(page_table.GetAliasCodeRegionStart()) / Kernel::PageSize;
const u64 alias_code_size = page_table.GetAliasCodeRegionSize() / Kernel::PageSize;
// NOTE: This will retry indefinitely until mapping the code memory succeeds.
while (true) {
// Generate a new trial address.
const u64 mapped_address =
(alias_code_start + (generate_random() % alias_code_size)) * Kernel::PageSize;
// Try to map the address
R_TRY_CATCH(code_memory.MapToOwner(mapped_address, size, perm)) {
R_CATCH(Kernel::ResultInvalidMemoryRegion) {
// If we could not map here, retry.
continue;
}
}
R_END_TRY_CATCH;
// Set members.
m_code_memory = std::addressof(code_memory);
m_size = size;
m_address = mapped_address;
m_perm = perm;
// Open a new reference to the code memory.
m_code_memory->Open();
// We succeeded.
R_SUCCEED();
}
}
void CodeMemory::Finalize() {
if (m_code_memory) {
R_ASSERT(m_code_memory->UnmapFromOwner(m_address, m_size));
m_code_memory->Close();
}
m_code_memory = nullptr;
}
} // namespace Service::JIT

View File

@ -0,0 +1,49 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <random>
#include "core/hle/kernel/k_code_memory.h"
namespace Service::JIT {
class CodeMemory {
public:
YUZU_NON_COPYABLE(CodeMemory);
explicit CodeMemory() = default;
CodeMemory(CodeMemory&& rhs) {
std::swap(m_code_memory, rhs.m_code_memory);
std::swap(m_size, rhs.m_size);
std::swap(m_address, rhs.m_address);
std::swap(m_perm, rhs.m_perm);
}
~CodeMemory() {
this->Finalize();
}
public:
Result Initialize(Kernel::KProcess& process, Kernel::KCodeMemory& code_memory, size_t size,
Kernel::Svc::MemoryPermission perm, std::mt19937_64& generate_random);
void Finalize();
size_t GetSize() const {
return m_size;
}
u64 GetAddress() const {
return m_address;
}
private:
Kernel::KCodeMemory* m_code_memory{};
size_t m_size{};
u64 m_address{};
Kernel::Svc::MemoryPermission m_perm{};
};
} // namespace Service::JIT

View File

@ -67,7 +67,7 @@ Nvnflinger::Nvnflinger(Core::System& system_, HosBinderDriverServer& hos_binder_
// Schedule the screen composition events // Schedule the screen composition events
multi_composition_event = Core::Timing::CreateEvent( multi_composition_event = Core::Timing::CreateEvent(
"ScreenComposition", "ScreenComposition",
[this](std::uintptr_t, s64 time, [this](s64 time,
std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> { std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
vsync_signal.Set(); vsync_signal.Set();
return std::chrono::nanoseconds(GetNextTicks()); return std::chrono::nanoseconds(GetNextTicks());
@ -75,7 +75,7 @@ Nvnflinger::Nvnflinger(Core::System& system_, HosBinderDriverServer& hos_binder_
single_composition_event = Core::Timing::CreateEvent( single_composition_event = Core::Timing::CreateEvent(
"ScreenComposition", "ScreenComposition",
[this](std::uintptr_t, s64 time, [this](s64 time,
std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> { std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
const auto lock_guard = Lock(); const auto lock_guard = Lock();
Compose(); Compose();
@ -93,11 +93,11 @@ Nvnflinger::Nvnflinger(Core::System& system_, HosBinderDriverServer& hos_binder_
Nvnflinger::~Nvnflinger() { Nvnflinger::~Nvnflinger() {
if (system.IsMulticore()) { if (system.IsMulticore()) {
system.CoreTiming().UnscheduleEvent(multi_composition_event, {}); system.CoreTiming().UnscheduleEvent(multi_composition_event);
vsync_thread.request_stop(); vsync_thread.request_stop();
vsync_signal.Set(); vsync_signal.Set();
} else { } else {
system.CoreTiming().UnscheduleEvent(single_composition_event, {}); system.CoreTiming().UnscheduleEvent(single_composition_event);
} }
ShutdownLayers(); ShutdownLayers();

View File

@ -10,6 +10,7 @@
#include "common/assert.h" #include "common/assert.h"
#include "common/atomic_ops.h" #include "common/atomic_ops.h"
#include "common/common_types.h" #include "common/common_types.h"
#include "common/heap_tracker.h"
#include "common/logging/log.h" #include "common/logging/log.h"
#include "common/page_table.h" #include "common/page_table.h"
#include "common/scope_exit.h" #include "common/scope_exit.h"
@ -52,10 +53,18 @@ struct Memory::Impl {
} else { } else {
current_page_table->fastmem_arena = nullptr; current_page_table->fastmem_arena = nullptr;
} }
#ifdef __linux__
heap_tracker.emplace(system.DeviceMemory().buffer);
buffer = std::addressof(*heap_tracker);
#else
buffer = std::addressof(system.DeviceMemory().buffer);
#endif
} }
void MapMemoryRegion(Common::PageTable& page_table, Common::ProcessAddress base, u64 size, void MapMemoryRegion(Common::PageTable& page_table, Common::ProcessAddress base, u64 size,
Common::PhysicalAddress target, Common::MemoryPermission perms) { Common::PhysicalAddress target, Common::MemoryPermission perms,
bool separate_heap) {
ASSERT_MSG((size & YUZU_PAGEMASK) == 0, "non-page aligned size: {:016X}", size); ASSERT_MSG((size & YUZU_PAGEMASK) == 0, "non-page aligned size: {:016X}", size);
ASSERT_MSG((base & YUZU_PAGEMASK) == 0, "non-page aligned base: {:016X}", GetInteger(base)); ASSERT_MSG((base & YUZU_PAGEMASK) == 0, "non-page aligned base: {:016X}", GetInteger(base));
ASSERT_MSG(target >= DramMemoryMap::Base, "Out of bounds target: {:016X}", ASSERT_MSG(target >= DramMemoryMap::Base, "Out of bounds target: {:016X}",
@ -64,19 +73,20 @@ struct Memory::Impl {
Common::PageType::Memory); Common::PageType::Memory);
if (current_page_table->fastmem_arena) { if (current_page_table->fastmem_arena) {
system.DeviceMemory().buffer.Map(GetInteger(base), buffer->Map(GetInteger(base), GetInteger(target) - DramMemoryMap::Base, size, perms,
GetInteger(target) - DramMemoryMap::Base, size, perms); separate_heap);
} }
} }
void UnmapRegion(Common::PageTable& page_table, Common::ProcessAddress base, u64 size) { void UnmapRegion(Common::PageTable& page_table, Common::ProcessAddress base, u64 size,
bool separate_heap) {
ASSERT_MSG((size & YUZU_PAGEMASK) == 0, "non-page aligned size: {:016X}", size); ASSERT_MSG((size & YUZU_PAGEMASK) == 0, "non-page aligned size: {:016X}", size);
ASSERT_MSG((base & YUZU_PAGEMASK) == 0, "non-page aligned base: {:016X}", GetInteger(base)); ASSERT_MSG((base & YUZU_PAGEMASK) == 0, "non-page aligned base: {:016X}", GetInteger(base));
MapPages(page_table, base / YUZU_PAGESIZE, size / YUZU_PAGESIZE, 0, MapPages(page_table, base / YUZU_PAGESIZE, size / YUZU_PAGESIZE, 0,
Common::PageType::Unmapped); Common::PageType::Unmapped);
if (current_page_table->fastmem_arena) { if (current_page_table->fastmem_arena) {
system.DeviceMemory().buffer.Unmap(GetInteger(base), size); buffer->Unmap(GetInteger(base), size, separate_heap);
} }
} }
@ -89,11 +99,6 @@ struct Memory::Impl {
return; return;
} }
const bool is_r = True(perms & Common::MemoryPermission::Read);
const bool is_w = True(perms & Common::MemoryPermission::Write);
const bool is_x =
True(perms & Common::MemoryPermission::Execute) && Settings::IsNceEnabled();
u64 protect_bytes{}; u64 protect_bytes{};
u64 protect_begin{}; u64 protect_begin{};
for (u64 addr = vaddr; addr < vaddr + size; addr += YUZU_PAGESIZE) { for (u64 addr = vaddr; addr < vaddr + size; addr += YUZU_PAGESIZE) {
@ -102,8 +107,7 @@ struct Memory::Impl {
switch (page_type) { switch (page_type) {
case Common::PageType::RasterizerCachedMemory: case Common::PageType::RasterizerCachedMemory:
if (protect_bytes > 0) { if (protect_bytes > 0) {
system.DeviceMemory().buffer.Protect(protect_begin, protect_bytes, is_r, is_w, buffer->Protect(protect_begin, protect_bytes, perms);
is_x);
protect_bytes = 0; protect_bytes = 0;
} }
break; break;
@ -116,7 +120,7 @@ struct Memory::Impl {
} }
if (protect_bytes > 0) { if (protect_bytes > 0) {
system.DeviceMemory().buffer.Protect(protect_begin, protect_bytes, is_r, is_w, is_x); buffer->Protect(protect_begin, protect_bytes, perms);
} }
} }
@ -486,7 +490,9 @@ struct Memory::Impl {
} }
if (current_page_table->fastmem_arena) { if (current_page_table->fastmem_arena) {
system.DeviceMemory().buffer.Protect(vaddr, size, !debug, !debug); const auto perm{debug ? Common::MemoryPermission{}
: Common::MemoryPermission::ReadWrite};
buffer->Protect(vaddr, size, perm);
} }
// Iterate over a contiguous CPU address space, marking/unmarking the region. // Iterate over a contiguous CPU address space, marking/unmarking the region.
@ -543,9 +549,14 @@ struct Memory::Impl {
} }
if (current_page_table->fastmem_arena) { if (current_page_table->fastmem_arena) {
const bool is_read_enable = Common::MemoryPermission perm{};
!Settings::values.use_reactive_flushing.GetValue() || !cached; if (!Settings::values.use_reactive_flushing.GetValue() || !cached) {
system.DeviceMemory().buffer.Protect(vaddr, size, is_read_enable, !cached); perm |= Common::MemoryPermission::Read;
}
if (!cached) {
perm |= Common::MemoryPermission::Write;
}
buffer->Protect(vaddr, size, perm);
} }
// Iterate over a contiguous CPU address space, which corresponds to the specified GPU // Iterate over a contiguous CPU address space, which corresponds to the specified GPU
@ -856,6 +867,13 @@ struct Memory::Impl {
std::array<GPUDirtyState, Core::Hardware::NUM_CPU_CORES> rasterizer_write_areas{}; std::array<GPUDirtyState, Core::Hardware::NUM_CPU_CORES> rasterizer_write_areas{};
std::span<Core::GPUDirtyMemoryManager> gpu_dirty_managers; std::span<Core::GPUDirtyMemoryManager> gpu_dirty_managers;
std::mutex sys_core_guard; std::mutex sys_core_guard;
std::optional<Common::HeapTracker> heap_tracker;
#ifdef __linux__
Common::HeapTracker* buffer{};
#else
Common::HostMemory* buffer{};
#endif
}; };
Memory::Memory(Core::System& system_) : system{system_} { Memory::Memory(Core::System& system_) : system{system_} {
@ -873,12 +891,14 @@ void Memory::SetCurrentPageTable(Kernel::KProcess& process) {
} }
void Memory::MapMemoryRegion(Common::PageTable& page_table, Common::ProcessAddress base, u64 size, void Memory::MapMemoryRegion(Common::PageTable& page_table, Common::ProcessAddress base, u64 size,
Common::PhysicalAddress target, Common::MemoryPermission perms) { Common::PhysicalAddress target, Common::MemoryPermission perms,
impl->MapMemoryRegion(page_table, base, size, target, perms); bool separate_heap) {
impl->MapMemoryRegion(page_table, base, size, target, perms, separate_heap);
} }
void Memory::UnmapRegion(Common::PageTable& page_table, Common::ProcessAddress base, u64 size) { void Memory::UnmapRegion(Common::PageTable& page_table, Common::ProcessAddress base, u64 size,
impl->UnmapRegion(page_table, base, size); bool separate_heap) {
impl->UnmapRegion(page_table, base, size, separate_heap);
} }
void Memory::ProtectRegion(Common::PageTable& page_table, Common::ProcessAddress vaddr, u64 size, void Memory::ProtectRegion(Common::PageTable& page_table, Common::ProcessAddress vaddr, u64 size,
@ -1048,7 +1068,9 @@ void Memory::FlushRegion(Common::ProcessAddress dest_addr, size_t size) {
} }
bool Memory::InvalidateNCE(Common::ProcessAddress vaddr, size_t size) { bool Memory::InvalidateNCE(Common::ProcessAddress vaddr, size_t size) {
bool mapped = true; [[maybe_unused]] bool mapped = true;
[[maybe_unused]] bool rasterizer = false;
u8* const ptr = impl->GetPointerImpl( u8* const ptr = impl->GetPointerImpl(
GetInteger(vaddr), GetInteger(vaddr),
[&] { [&] {
@ -1056,8 +1078,26 @@ bool Memory::InvalidateNCE(Common::ProcessAddress vaddr, size_t size) {
GetInteger(vaddr)); GetInteger(vaddr));
mapped = false; mapped = false;
}, },
[&] { impl->system.GPU().InvalidateRegion(GetInteger(vaddr), size); }); [&] {
impl->system.GPU().InvalidateRegion(GetInteger(vaddr), size);
rasterizer = true;
});
#ifdef __linux__
if (!rasterizer && mapped) {
impl->buffer->DeferredMapSeparateHeap(GetInteger(vaddr));
}
#endif
return mapped && ptr != nullptr; return mapped && ptr != nullptr;
} }
bool Memory::InvalidateSeparateHeap(void* fault_address) {
#ifdef __linux__
return impl->buffer->DeferredMapSeparateHeap(static_cast<u8*>(fault_address));
#else
return false;
#endif
}
} // namespace Core::Memory } // namespace Core::Memory

View File

@ -86,7 +86,8 @@ public:
* @param perms The permissions to map the memory with. * @param perms The permissions to map the memory with.
*/ */
void MapMemoryRegion(Common::PageTable& page_table, Common::ProcessAddress base, u64 size, void MapMemoryRegion(Common::PageTable& page_table, Common::ProcessAddress base, u64 size,
Common::PhysicalAddress target, Common::MemoryPermission perms); Common::PhysicalAddress target, Common::MemoryPermission perms,
bool separate_heap);
/** /**
* Unmaps a region of the emulated process address space. * Unmaps a region of the emulated process address space.
@ -95,7 +96,8 @@ public:
* @param base The address to begin unmapping at. * @param base The address to begin unmapping at.
* @param size The amount of bytes to unmap. * @param size The amount of bytes to unmap.
*/ */
void UnmapRegion(Common::PageTable& page_table, Common::ProcessAddress base, u64 size); void UnmapRegion(Common::PageTable& page_table, Common::ProcessAddress base, u64 size,
bool separate_heap);
/** /**
* Protects a region of the emulated process address space with the new permissions. * Protects a region of the emulated process address space with the new permissions.
@ -486,6 +488,7 @@ public:
void SetGPUDirtyManagers(std::span<Core::GPUDirtyMemoryManager> managers); void SetGPUDirtyManagers(std::span<Core::GPUDirtyMemoryManager> managers);
void InvalidateRegion(Common::ProcessAddress dest_addr, size_t size); void InvalidateRegion(Common::ProcessAddress dest_addr, size_t size);
bool InvalidateNCE(Common::ProcessAddress vaddr, size_t size); bool InvalidateNCE(Common::ProcessAddress vaddr, size_t size);
bool InvalidateSeparateHeap(void* fault_address);
void FlushRegion(Common::ProcessAddress dest_addr, size_t size); void FlushRegion(Common::ProcessAddress dest_addr, size_t size);
private: private:
@ -683,7 +686,8 @@ public:
} else { } else {
this->m_memory.WriteBlockUnsafe(this->m_addr, this->data(), this->size_bytes()); this->m_memory.WriteBlockUnsafe(this->m_addr, this->data(), this->size_bytes());
} }
} else if constexpr (FLAGS & GuestMemoryFlags::Safe) { } else if constexpr ((FLAGS & GuestMemoryFlags::Safe) ||
(FLAGS & GuestMemoryFlags::Cached)) {
this->m_memory.InvalidateRegion(this->m_addr, this->size_bytes()); this->m_memory.InvalidateRegion(this->m_addr, this->size_bytes());
} }
} }

View File

@ -190,15 +190,15 @@ CheatEngine::CheatEngine(System& system_, std::vector<CheatEntry> cheats_,
} }
CheatEngine::~CheatEngine() { CheatEngine::~CheatEngine() {
core_timing.UnscheduleEvent(event, 0); core_timing.UnscheduleEvent(event);
} }
void CheatEngine::Initialize() { void CheatEngine::Initialize() {
event = Core::Timing::CreateEvent( event = Core::Timing::CreateEvent(
"CheatEngine::FrameCallback::" + Common::HexToString(metadata.main_nso_build_id), "CheatEngine::FrameCallback::" + Common::HexToString(metadata.main_nso_build_id),
[this](std::uintptr_t user_data, s64 time, [this](s64 time,
std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> { std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
FrameCallback(user_data, ns_late); FrameCallback(ns_late);
return std::nullopt; return std::nullopt;
}); });
core_timing.ScheduleLoopingEvent(CHEAT_ENGINE_NS, CHEAT_ENGINE_NS, event); core_timing.ScheduleLoopingEvent(CHEAT_ENGINE_NS, CHEAT_ENGINE_NS, event);
@ -239,7 +239,7 @@ void CheatEngine::Reload(std::vector<CheatEntry> reload_cheats) {
MICROPROFILE_DEFINE(Cheat_Engine, "Add-Ons", "Cheat Engine", MP_RGB(70, 200, 70)); MICROPROFILE_DEFINE(Cheat_Engine, "Add-Ons", "Cheat Engine", MP_RGB(70, 200, 70));
void CheatEngine::FrameCallback(std::uintptr_t, std::chrono::nanoseconds ns_late) { void CheatEngine::FrameCallback(std::chrono::nanoseconds ns_late) {
if (is_pending_reload.exchange(false)) { if (is_pending_reload.exchange(false)) {
vm.LoadProgram(cheats); vm.LoadProgram(cheats);
} }

View File

@ -70,7 +70,7 @@ public:
void Reload(std::vector<CheatEntry> reload_cheats); void Reload(std::vector<CheatEntry> reload_cheats);
private: private:
void FrameCallback(std::uintptr_t user_data, std::chrono::nanoseconds ns_late); void FrameCallback(std::chrono::nanoseconds ns_late);
DmntCheatVm vm; DmntCheatVm vm;
CheatProcessMetadata metadata; CheatProcessMetadata metadata;

View File

@ -51,18 +51,17 @@ void MemoryWriteWidth(Core::Memory::Memory& memory, u32 width, VAddr addr, u64 v
Freezer::Freezer(Core::Timing::CoreTiming& core_timing_, Core::Memory::Memory& memory_) Freezer::Freezer(Core::Timing::CoreTiming& core_timing_, Core::Memory::Memory& memory_)
: core_timing{core_timing_}, memory{memory_} { : core_timing{core_timing_}, memory{memory_} {
event = Core::Timing::CreateEvent( event = Core::Timing::CreateEvent("MemoryFreezer::FrameCallback",
"MemoryFreezer::FrameCallback", [this](s64 time, std::chrono::nanoseconds ns_late)
[this](std::uintptr_t user_data, s64 time, -> std::optional<std::chrono::nanoseconds> {
std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> { FrameCallback(ns_late);
FrameCallback(user_data, ns_late); return std::nullopt;
return std::nullopt; });
});
core_timing.ScheduleEvent(memory_freezer_ns, event); core_timing.ScheduleEvent(memory_freezer_ns, event);
} }
Freezer::~Freezer() { Freezer::~Freezer() {
core_timing.UnscheduleEvent(event, 0); core_timing.UnscheduleEvent(event);
} }
void Freezer::SetActive(bool is_active) { void Freezer::SetActive(bool is_active) {
@ -159,7 +158,7 @@ Freezer::Entries::const_iterator Freezer::FindEntry(VAddr address) const {
[address](const Entry& entry) { return entry.address == address; }); [address](const Entry& entry) { return entry.address == address; });
} }
void Freezer::FrameCallback(std::uintptr_t, std::chrono::nanoseconds ns_late) { void Freezer::FrameCallback(std::chrono::nanoseconds ns_late) {
if (!IsActive()) { if (!IsActive()) {
LOG_DEBUG(Common_Memory, "Memory freezer has been deactivated, ending callback events."); LOG_DEBUG(Common_Memory, "Memory freezer has been deactivated, ending callback events.");
return; return;

View File

@ -77,7 +77,7 @@ private:
Entries::iterator FindEntry(VAddr address); Entries::iterator FindEntry(VAddr address);
Entries::const_iterator FindEntry(VAddr address) const; Entries::const_iterator FindEntry(VAddr address) const;
void FrameCallback(std::uintptr_t user_data, std::chrono::nanoseconds ns_late); void FrameCallback(std::chrono::nanoseconds ns_late);
void FillEntryReads(); void FillEntryReads();
std::atomic_bool active{false}; std::atomic_bool active{false};

View File

@ -403,59 +403,63 @@ void Config::SavePlayerValues(const std::size_t player_index) {
// No custom profile selected // No custom profile selected
return; return;
} }
WriteSetting(std::string(player_prefix).append("profile_name"), player.profile_name, WriteStringSetting(std::string(player_prefix).append("profile_name"), player.profile_name,
std::make_optional(std::string(""))); std::make_optional(std::string("")));
} }
WriteSetting(std::string(player_prefix).append("type"), static_cast<u8>(player.controller_type), WriteIntegerSetting(
std::make_optional(static_cast<u8>(Settings::ControllerType::ProController))); std::string(player_prefix).append("type"), static_cast<u8>(player.controller_type),
std::make_optional(static_cast<u8>(Settings::ControllerType::ProController)));
if (!player_prefix.empty() || !Settings::IsConfiguringGlobal()) { if (!player_prefix.empty() || !Settings::IsConfiguringGlobal()) {
WriteSetting(std::string(player_prefix).append("connected"), player.connected, WriteBooleanSetting(std::string(player_prefix).append("connected"), player.connected,
std::make_optional(player_index == 0)); std::make_optional(player_index == 0));
WriteSetting(std::string(player_prefix).append("vibration_enabled"), WriteIntegerSetting(std::string(player_prefix).append("vibration_enabled"),
player.vibration_enabled, std::make_optional(true)); player.vibration_enabled, std::make_optional(true));
WriteSetting(std::string(player_prefix).append("vibration_strength"), WriteIntegerSetting(std::string(player_prefix).append("vibration_strength"),
player.vibration_strength, std::make_optional(100)); player.vibration_strength, std::make_optional(100));
WriteSetting(std::string(player_prefix).append("body_color_left"), player.body_color_left, WriteIntegerSetting(std::string(player_prefix).append("body_color_left"),
std::make_optional(Settings::JOYCON_BODY_NEON_BLUE)); player.body_color_left,
WriteSetting(std::string(player_prefix).append("body_color_right"), player.body_color_right, std::make_optional(Settings::JOYCON_BODY_NEON_BLUE));
std::make_optional(Settings::JOYCON_BODY_NEON_RED)); WriteIntegerSetting(std::string(player_prefix).append("body_color_right"),
WriteSetting(std::string(player_prefix).append("button_color_left"), player.body_color_right,
player.button_color_left, std::make_optional(Settings::JOYCON_BODY_NEON_RED));
std::make_optional(Settings::JOYCON_BUTTONS_NEON_BLUE)); WriteIntegerSetting(std::string(player_prefix).append("button_color_left"),
WriteSetting(std::string(player_prefix).append("button_color_right"), player.button_color_left,
player.button_color_right, std::make_optional(Settings::JOYCON_BUTTONS_NEON_BLUE));
std::make_optional(Settings::JOYCON_BUTTONS_NEON_RED)); WriteIntegerSetting(std::string(player_prefix).append("button_color_right"),
player.button_color_right,
std::make_optional(Settings::JOYCON_BUTTONS_NEON_RED));
} }
} }
void Config::SaveTouchscreenValues() { void Config::SaveTouchscreenValues() {
const auto& touchscreen = Settings::values.touchscreen; const auto& touchscreen = Settings::values.touchscreen;
WriteSetting(std::string("touchscreen_enabled"), touchscreen.enabled, std::make_optional(true)); WriteBooleanSetting(std::string("touchscreen_enabled"), touchscreen.enabled,
std::make_optional(true));
WriteSetting(std::string("touchscreen_angle"), touchscreen.rotation_angle, WriteIntegerSetting(std::string("touchscreen_angle"), touchscreen.rotation_angle,
std::make_optional(static_cast<u32>(0))); std::make_optional(static_cast<u32>(0)));
WriteSetting(std::string("touchscreen_diameter_x"), touchscreen.diameter_x, WriteIntegerSetting(std::string("touchscreen_diameter_x"), touchscreen.diameter_x,
std::make_optional(static_cast<u32>(15))); std::make_optional(static_cast<u32>(15)));
WriteSetting(std::string("touchscreen_diameter_y"), touchscreen.diameter_y, WriteIntegerSetting(std::string("touchscreen_diameter_y"), touchscreen.diameter_y,
std::make_optional(static_cast<u32>(15))); std::make_optional(static_cast<u32>(15)));
} }
void Config::SaveMotionTouchValues() { void Config::SaveMotionTouchValues() {
BeginArray(std::string("touch_from_button_maps")); BeginArray(std::string("touch_from_button_maps"));
for (std::size_t p = 0; p < Settings::values.touch_from_button_maps.size(); ++p) { for (std::size_t p = 0; p < Settings::values.touch_from_button_maps.size(); ++p) {
SetArrayIndex(static_cast<int>(p)); SetArrayIndex(static_cast<int>(p));
WriteSetting(std::string("name"), Settings::values.touch_from_button_maps[p].name, WriteStringSetting(std::string("name"), Settings::values.touch_from_button_maps[p].name,
std::make_optional(std::string("default"))); std::make_optional(std::string("default")));
BeginArray(std::string("entries")); BeginArray(std::string("entries"));
for (std::size_t q = 0; q < Settings::values.touch_from_button_maps[p].buttons.size(); for (std::size_t q = 0; q < Settings::values.touch_from_button_maps[p].buttons.size();
++q) { ++q) {
SetArrayIndex(static_cast<int>(q)); SetArrayIndex(static_cast<int>(q));
WriteSetting(std::string("bind"), WriteStringSetting(std::string("bind"),
Settings::values.touch_from_button_maps[p].buttons[q]); Settings::values.touch_from_button_maps[p].buttons[q]);
} }
EndArray(); // entries EndArray(); // entries
} }
@ -520,16 +524,16 @@ void Config::SaveCoreValues() {
void Config::SaveDataStorageValues() { void Config::SaveDataStorageValues() {
BeginGroup(Settings::TranslateCategory(Settings::Category::DataStorage)); BeginGroup(Settings::TranslateCategory(Settings::Category::DataStorage));
WriteSetting(std::string("nand_directory"), FS::GetYuzuPathString(FS::YuzuPath::NANDDir), WriteStringSetting(std::string("nand_directory"), FS::GetYuzuPathString(FS::YuzuPath::NANDDir),
std::make_optional(FS::GetYuzuPathString(FS::YuzuPath::NANDDir))); std::make_optional(FS::GetYuzuPathString(FS::YuzuPath::NANDDir)));
WriteSetting(std::string("sdmc_directory"), FS::GetYuzuPathString(FS::YuzuPath::SDMCDir), WriteStringSetting(std::string("sdmc_directory"), FS::GetYuzuPathString(FS::YuzuPath::SDMCDir),
std::make_optional(FS::GetYuzuPathString(FS::YuzuPath::SDMCDir))); std::make_optional(FS::GetYuzuPathString(FS::YuzuPath::SDMCDir)));
WriteSetting(std::string("load_directory"), FS::GetYuzuPathString(FS::YuzuPath::LoadDir), WriteStringSetting(std::string("load_directory"), FS::GetYuzuPathString(FS::YuzuPath::LoadDir),
std::make_optional(FS::GetYuzuPathString(FS::YuzuPath::LoadDir))); std::make_optional(FS::GetYuzuPathString(FS::YuzuPath::LoadDir)));
WriteSetting(std::string("dump_directory"), FS::GetYuzuPathString(FS::YuzuPath::DumpDir), WriteStringSetting(std::string("dump_directory"), FS::GetYuzuPathString(FS::YuzuPath::DumpDir),
std::make_optional(FS::GetYuzuPathString(FS::YuzuPath::DumpDir))); std::make_optional(FS::GetYuzuPathString(FS::YuzuPath::DumpDir)));
WriteSetting(std::string("tas_directory"), FS::GetYuzuPathString(FS::YuzuPath::TASDir), WriteStringSetting(std::string("tas_directory"), FS::GetYuzuPathString(FS::YuzuPath::TASDir),
std::make_optional(FS::GetYuzuPathString(FS::YuzuPath::TASDir))); std::make_optional(FS::GetYuzuPathString(FS::YuzuPath::TASDir)));
WriteCategory(Settings::Category::DataStorage); WriteCategory(Settings::Category::DataStorage);
@ -540,7 +544,7 @@ void Config::SaveDebuggingValues() {
BeginGroup(Settings::TranslateCategory(Settings::Category::Debugging)); BeginGroup(Settings::TranslateCategory(Settings::Category::Debugging));
// Intentionally not using the QT default setting as this is intended to be changed in the ini // Intentionally not using the QT default setting as this is intended to be changed in the ini
WriteSetting(std::string("record_frame_times"), Settings::values.record_frame_times); WriteBooleanSetting(std::string("record_frame_times"), Settings::values.record_frame_times);
WriteCategory(Settings::Category::Debugging); WriteCategory(Settings::Category::Debugging);
WriteCategory(Settings::Category::DebuggingGraphics); WriteCategory(Settings::Category::DebuggingGraphics);
@ -564,11 +568,13 @@ void Config::SaveDisabledAddOnValues() {
BeginArray(std::string("")); BeginArray(std::string(""));
for (const auto& elem : Settings::values.disabled_addons) { for (const auto& elem : Settings::values.disabled_addons) {
SetArrayIndex(i); SetArrayIndex(i);
WriteSetting(std::string("title_id"), elem.first, std::make_optional(static_cast<u64>(0))); WriteIntegerSetting(std::string("title_id"), elem.first,
std::make_optional(static_cast<u64>(0)));
BeginArray(std::string("disabled")); BeginArray(std::string("disabled"));
for (std::size_t j = 0; j < elem.second.size(); ++j) { for (std::size_t j = 0; j < elem.second.size(); ++j) {
SetArrayIndex(static_cast<int>(j)); SetArrayIndex(static_cast<int>(j));
WriteSetting(std::string("d"), elem.second[j], std::make_optional(std::string(""))); WriteStringSetting(std::string("d"), elem.second[j],
std::make_optional(std::string("")));
} }
EndArray(); // disabled EndArray(); // disabled
++i; ++i;
@ -609,8 +615,8 @@ void Config::SaveRendererValues() {
void Config::SaveScreenshotValues() { void Config::SaveScreenshotValues() {
BeginGroup(Settings::TranslateCategory(Settings::Category::Screenshots)); BeginGroup(Settings::TranslateCategory(Settings::Category::Screenshots));
WriteSetting(std::string("screenshot_path"), WriteStringSetting(std::string("screenshot_path"),
FS::GetYuzuPathString(FS::YuzuPath::ScreenshotsDir)); FS::GetYuzuPathString(FS::YuzuPath::ScreenshotsDir));
WriteCategory(Settings::Category::Screenshots); WriteCategory(Settings::Category::Screenshots);
EndGroup(); EndGroup();
@ -746,46 +752,70 @@ bool Config::Exists(const std::string& section, const std::string& key) const {
return !value.empty(); return !value.empty();
} }
template <typename Type> void Config::WriteBooleanSetting(const std::string& key, const bool& value,
void Config::WriteSetting(const std::string& key, const Type& value, const std::optional<bool>& default_value,
const std::optional<Type>& default_value, const std::optional<bool>& use_global) {
const std::optional<bool>& use_global) { std::optional<std::string> string_default = std::nullopt;
std::string full_key = GetFullKey(key, false); if (default_value.has_value()) {
string_default = std::make_optional(ToString(default_value.value()));
std::string saved_value;
std::string string_default;
if constexpr (std::is_same_v<Type, std::string>) {
saved_value.append(AdjustOutputString(value));
if (default_value.has_value()) {
string_default.append(AdjustOutputString(default_value.value()));
}
} else {
saved_value.append(AdjustOutputString(ToString(value)));
if (default_value.has_value()) {
string_default.append(ToString(default_value.value()));
}
} }
WritePreparedSetting(key, AdjustOutputString(ToString(value)), string_default, use_global);
}
if (default_value.has_value() && use_global.has_value()) { template <typename T>
std::enable_if_t<std::is_integral_v<T>> Config::WriteIntegerSetting(
const std::string& key, const T& value, const std::optional<T>& default_value,
const std::optional<bool>& use_global) {
std::optional<std::string> string_default = std::nullopt;
if (default_value.has_value()) {
string_default = std::make_optional(ToString(default_value.value()));
}
WritePreparedSetting(key, AdjustOutputString(ToString(value)), string_default, use_global);
}
void Config::WriteDoubleSetting(const std::string& key, const double& value,
const std::optional<double>& default_value,
const std::optional<bool>& use_global) {
std::optional<std::string> string_default = std::nullopt;
if (default_value.has_value()) {
string_default = std::make_optional(ToString(default_value.value()));
}
WritePreparedSetting(key, AdjustOutputString(ToString(value)), string_default, use_global);
}
void Config::WriteStringSetting(const std::string& key, const std::string& value,
const std::optional<std::string>& default_value,
const std::optional<bool>& use_global) {
std::optional string_default = default_value;
if (default_value.has_value()) {
string_default.value().append(AdjustOutputString(default_value.value()));
}
WritePreparedSetting(key, AdjustOutputString(value), string_default, use_global);
}
void Config::WritePreparedSetting(const std::string& key, const std::string& adjusted_value,
const std::optional<std::string>& adjusted_default_value,
const std::optional<bool>& use_global) {
std::string full_key = GetFullKey(key, false);
if (adjusted_default_value.has_value() && use_global.has_value()) {
if (!global) { if (!global) {
WriteSettingInternal(std::string(full_key).append("\\global"), WriteString(std::string(full_key).append("\\global"), ToString(use_global.value()));
ToString(use_global.value()));
} }
if (global || use_global.value() == false) { if (global || use_global.value() == false) {
WriteSettingInternal(std::string(full_key).append("\\default"), WriteString(std::string(full_key).append("\\default"),
ToString(string_default == saved_value)); ToString(adjusted_default_value == adjusted_value));
WriteSettingInternal(full_key, saved_value); WriteString(full_key, adjusted_value);
} }
} else if (default_value.has_value() && !use_global.has_value()) { } else if (adjusted_default_value.has_value() && !use_global.has_value()) {
WriteSettingInternal(std::string(full_key).append("\\default"), WriteString(std::string(full_key).append("\\default"),
ToString(string_default == saved_value)); ToString(adjusted_default_value == adjusted_value));
WriteSettingInternal(full_key, saved_value); WriteString(full_key, adjusted_value);
} else { } else {
WriteSettingInternal(full_key, saved_value); WriteString(full_key, adjusted_value);
} }
} }
void Config::WriteSettingInternal(const std::string& key, const std::string& value) { void Config::WriteString(const std::string& key, const std::string& value) {
config->SetValue(GetSection().c_str(), key.c_str(), value.c_str()); config->SetValue(GetSection().c_str(), key.c_str(), value.c_str());
} }
@ -861,17 +891,17 @@ void Config::WriteSettingGeneric(const Settings::BasicSetting* const setting) {
std::string key = AdjustKey(setting->GetLabel()); std::string key = AdjustKey(setting->GetLabel());
if (setting->Switchable()) { if (setting->Switchable()) {
if (!global) { if (!global) {
WriteSetting(std::string(key).append("\\use_global"), setting->UsingGlobal()); WriteBooleanSetting(std::string(key).append("\\use_global"), setting->UsingGlobal());
} }
if (global || !setting->UsingGlobal()) { if (global || !setting->UsingGlobal()) {
WriteSetting(std::string(key).append("\\default"), WriteBooleanSetting(std::string(key).append("\\default"),
setting->ToString() == setting->DefaultToString()); setting->ToString() == setting->DefaultToString());
WriteSetting(key, setting->ToString()); WriteStringSetting(key, setting->ToString());
} }
} else if (global) { } else if (global) {
WriteSetting(std::string(key).append("\\default"), WriteBooleanSetting(std::string(key).append("\\default"),
setting->ToString() == setting->DefaultToString()); setting->ToString() == setting->DefaultToString());
WriteSetting(key, setting->ToString()); WriteStringSetting(key, setting->ToString());
} }
} }

View File

@ -154,11 +154,20 @@ protected:
* @param use_global Specifies if the custom or global config should be in use, for custom * @param use_global Specifies if the custom or global config should be in use, for custom
* configs * configs
*/ */
template <typename Type = int> void WriteBooleanSetting(const std::string& key, const bool& value,
void WriteSetting(const std::string& key, const Type& value, const std::optional<bool>& default_value = std::nullopt,
const std::optional<Type>& default_value = std::nullopt, const std::optional<bool>& use_global = std::nullopt);
const std::optional<bool>& use_global = std::nullopt); template <typename T>
void WriteSettingInternal(const std::string& key, const std::string& value); std::enable_if_t<std::is_integral_v<T>> WriteIntegerSetting(
const std::string& key, const T& value,
const std::optional<T>& default_value = std::nullopt,
const std::optional<bool>& use_global = std::nullopt);
void WriteDoubleSetting(const std::string& key, const double& value,
const std::optional<double>& default_value = std::nullopt,
const std::optional<bool>& use_global = std::nullopt);
void WriteStringSetting(const std::string& key, const std::string& value,
const std::optional<std::string>& default_value = std::nullopt,
const std::optional<bool>& use_global = std::nullopt);
void ReadCategory(Settings::Category category); void ReadCategory(Settings::Category category);
void WriteCategory(Settings::Category category); void WriteCategory(Settings::Category category);
@ -175,8 +184,10 @@ protected:
return value_ ? "true" : "false"; return value_ ? "true" : "false";
} else if constexpr (std::is_same_v<T, u64>) { } else if constexpr (std::is_same_v<T, u64>) {
return std::to_string(static_cast<u64>(value_)); return std::to_string(static_cast<u64>(value_));
} else { } else if constexpr (std::is_same_v<T, s64>) {
return std::to_string(static_cast<s64>(value_)); return std::to_string(static_cast<s64>(value_));
} else {
return std::to_string(value_);
} }
} }
@ -197,9 +208,13 @@ protected:
const bool global; const bool global;
private: private:
inline static std::array<char, 19> special_characters = {'!', '#', '$', '%', '^', '&', '*', void WritePreparedSetting(const std::string& key, const std::string& adjusted_value,
'|', ';', '\'', '\"', ',', '<', '.', const std::optional<std::string>& adjusted_default_value,
'>', '?', '`', '~', '='}; const std::optional<bool>& use_global);
void WriteString(const std::string& key, const std::string& value);
inline static std::array<char, 18> special_characters = {
'!', '#', '$', '%', '^', '&', '*', '|', ';', '\'', '\"', ',', '<', '>', '?', '`', '~', '='};
struct ConfigArray { struct ConfigArray {
std::string name; std::string name;

View File

@ -12,6 +12,7 @@ using namespace Common::Literals;
static constexpr size_t VIRTUAL_SIZE = 1ULL << 39; static constexpr size_t VIRTUAL_SIZE = 1ULL << 39;
static constexpr size_t BACKING_SIZE = 4_GiB; static constexpr size_t BACKING_SIZE = 4_GiB;
static constexpr auto PERMS = Common::MemoryPermission::ReadWrite; static constexpr auto PERMS = Common::MemoryPermission::ReadWrite;
static constexpr auto HEAP = false;
TEST_CASE("HostMemory: Initialize and deinitialize", "[common]") { TEST_CASE("HostMemory: Initialize and deinitialize", "[common]") {
{ HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); } { HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); }
@ -20,7 +21,7 @@ TEST_CASE("HostMemory: Initialize and deinitialize", "[common]") {
TEST_CASE("HostMemory: Simple map", "[common]") { TEST_CASE("HostMemory: Simple map", "[common]") {
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
mem.Map(0x5000, 0x8000, 0x1000, PERMS); mem.Map(0x5000, 0x8000, 0x1000, PERMS, HEAP);
volatile u8* const data = mem.VirtualBasePointer() + 0x5000; volatile u8* const data = mem.VirtualBasePointer() + 0x5000;
data[0] = 50; data[0] = 50;
@ -29,8 +30,8 @@ TEST_CASE("HostMemory: Simple map", "[common]") {
TEST_CASE("HostMemory: Simple mirror map", "[common]") { TEST_CASE("HostMemory: Simple mirror map", "[common]") {
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
mem.Map(0x5000, 0x3000, 0x2000, PERMS); mem.Map(0x5000, 0x3000, 0x2000, PERMS, HEAP);
mem.Map(0x8000, 0x4000, 0x1000, PERMS); mem.Map(0x8000, 0x4000, 0x1000, PERMS, HEAP);
volatile u8* const mirror_a = mem.VirtualBasePointer() + 0x5000; volatile u8* const mirror_a = mem.VirtualBasePointer() + 0x5000;
volatile u8* const mirror_b = mem.VirtualBasePointer() + 0x8000; volatile u8* const mirror_b = mem.VirtualBasePointer() + 0x8000;
@ -40,116 +41,116 @@ TEST_CASE("HostMemory: Simple mirror map", "[common]") {
TEST_CASE("HostMemory: Simple unmap", "[common]") { TEST_CASE("HostMemory: Simple unmap", "[common]") {
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
mem.Map(0x5000, 0x3000, 0x2000, PERMS); mem.Map(0x5000, 0x3000, 0x2000, PERMS, HEAP);
volatile u8* const data = mem.VirtualBasePointer() + 0x5000; volatile u8* const data = mem.VirtualBasePointer() + 0x5000;
data[75] = 50; data[75] = 50;
REQUIRE(data[75] == 50); REQUIRE(data[75] == 50);
mem.Unmap(0x5000, 0x2000); mem.Unmap(0x5000, 0x2000, HEAP);
} }
TEST_CASE("HostMemory: Simple unmap and remap", "[common]") { TEST_CASE("HostMemory: Simple unmap and remap", "[common]") {
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
mem.Map(0x5000, 0x3000, 0x2000, PERMS); mem.Map(0x5000, 0x3000, 0x2000, PERMS, HEAP);
volatile u8* const data = mem.VirtualBasePointer() + 0x5000; volatile u8* const data = mem.VirtualBasePointer() + 0x5000;
data[0] = 50; data[0] = 50;
REQUIRE(data[0] == 50); REQUIRE(data[0] == 50);
mem.Unmap(0x5000, 0x2000); mem.Unmap(0x5000, 0x2000, HEAP);
mem.Map(0x5000, 0x3000, 0x2000, PERMS); mem.Map(0x5000, 0x3000, 0x2000, PERMS, HEAP);
REQUIRE(data[0] == 50); REQUIRE(data[0] == 50);
mem.Map(0x7000, 0x2000, 0x5000, PERMS); mem.Map(0x7000, 0x2000, 0x5000, PERMS, HEAP);
REQUIRE(data[0x3000] == 50); REQUIRE(data[0x3000] == 50);
} }
TEST_CASE("HostMemory: Nieche allocation", "[common]") { TEST_CASE("HostMemory: Nieche allocation", "[common]") {
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
mem.Map(0x0000, 0, 0x20000, PERMS); mem.Map(0x0000, 0, 0x20000, PERMS, HEAP);
mem.Unmap(0x0000, 0x4000); mem.Unmap(0x0000, 0x4000, HEAP);
mem.Map(0x1000, 0, 0x2000, PERMS); mem.Map(0x1000, 0, 0x2000, PERMS, HEAP);
mem.Map(0x3000, 0, 0x1000, PERMS); mem.Map(0x3000, 0, 0x1000, PERMS, HEAP);
mem.Map(0, 0, 0x1000, PERMS); mem.Map(0, 0, 0x1000, PERMS, HEAP);
} }
TEST_CASE("HostMemory: Full unmap", "[common]") { TEST_CASE("HostMemory: Full unmap", "[common]") {
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
mem.Map(0x8000, 0, 0x4000, PERMS); mem.Map(0x8000, 0, 0x4000, PERMS, HEAP);
mem.Unmap(0x8000, 0x4000); mem.Unmap(0x8000, 0x4000, HEAP);
mem.Map(0x6000, 0, 0x16000, PERMS); mem.Map(0x6000, 0, 0x16000, PERMS, HEAP);
} }
TEST_CASE("HostMemory: Right out of bounds unmap", "[common]") { TEST_CASE("HostMemory: Right out of bounds unmap", "[common]") {
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
mem.Map(0x0000, 0, 0x4000, PERMS); mem.Map(0x0000, 0, 0x4000, PERMS, HEAP);
mem.Unmap(0x2000, 0x4000); mem.Unmap(0x2000, 0x4000, HEAP);
mem.Map(0x2000, 0x80000, 0x4000, PERMS); mem.Map(0x2000, 0x80000, 0x4000, PERMS, HEAP);
} }
TEST_CASE("HostMemory: Left out of bounds unmap", "[common]") { TEST_CASE("HostMemory: Left out of bounds unmap", "[common]") {
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
mem.Map(0x8000, 0, 0x4000, PERMS); mem.Map(0x8000, 0, 0x4000, PERMS, HEAP);
mem.Unmap(0x6000, 0x4000); mem.Unmap(0x6000, 0x4000, HEAP);
mem.Map(0x8000, 0, 0x2000, PERMS); mem.Map(0x8000, 0, 0x2000, PERMS, HEAP);
} }
TEST_CASE("HostMemory: Multiple placeholder unmap", "[common]") { TEST_CASE("HostMemory: Multiple placeholder unmap", "[common]") {
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
mem.Map(0x0000, 0, 0x4000, PERMS); mem.Map(0x0000, 0, 0x4000, PERMS, HEAP);
mem.Map(0x4000, 0, 0x1b000, PERMS); mem.Map(0x4000, 0, 0x1b000, PERMS, HEAP);
mem.Unmap(0x3000, 0x1c000); mem.Unmap(0x3000, 0x1c000, HEAP);
mem.Map(0x3000, 0, 0x20000, PERMS); mem.Map(0x3000, 0, 0x20000, PERMS, HEAP);
} }
TEST_CASE("HostMemory: Unmap between placeholders", "[common]") { TEST_CASE("HostMemory: Unmap between placeholders", "[common]") {
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
mem.Map(0x0000, 0, 0x4000, PERMS); mem.Map(0x0000, 0, 0x4000, PERMS, HEAP);
mem.Map(0x4000, 0, 0x4000, PERMS); mem.Map(0x4000, 0, 0x4000, PERMS, HEAP);
mem.Unmap(0x2000, 0x4000); mem.Unmap(0x2000, 0x4000, HEAP);
mem.Map(0x2000, 0, 0x4000, PERMS); mem.Map(0x2000, 0, 0x4000, PERMS, HEAP);
} }
TEST_CASE("HostMemory: Unmap to origin", "[common]") { TEST_CASE("HostMemory: Unmap to origin", "[common]") {
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
mem.Map(0x4000, 0, 0x4000, PERMS); mem.Map(0x4000, 0, 0x4000, PERMS, HEAP);
mem.Map(0x8000, 0, 0x4000, PERMS); mem.Map(0x8000, 0, 0x4000, PERMS, HEAP);
mem.Unmap(0x4000, 0x4000); mem.Unmap(0x4000, 0x4000, HEAP);
mem.Map(0, 0, 0x4000, PERMS); mem.Map(0, 0, 0x4000, PERMS, HEAP);
mem.Map(0x4000, 0, 0x4000, PERMS); mem.Map(0x4000, 0, 0x4000, PERMS, HEAP);
} }
TEST_CASE("HostMemory: Unmap to right", "[common]") { TEST_CASE("HostMemory: Unmap to right", "[common]") {
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
mem.Map(0x4000, 0, 0x4000, PERMS); mem.Map(0x4000, 0, 0x4000, PERMS, HEAP);
mem.Map(0x8000, 0, 0x4000, PERMS); mem.Map(0x8000, 0, 0x4000, PERMS, HEAP);
mem.Unmap(0x8000, 0x4000); mem.Unmap(0x8000, 0x4000, HEAP);
mem.Map(0x8000, 0, 0x4000, PERMS); mem.Map(0x8000, 0, 0x4000, PERMS, HEAP);
} }
TEST_CASE("HostMemory: Partial right unmap check bindings", "[common]") { TEST_CASE("HostMemory: Partial right unmap check bindings", "[common]") {
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
mem.Map(0x4000, 0x10000, 0x4000, PERMS); mem.Map(0x4000, 0x10000, 0x4000, PERMS, HEAP);
volatile u8* const ptr = mem.VirtualBasePointer() + 0x4000; volatile u8* const ptr = mem.VirtualBasePointer() + 0x4000;
ptr[0x1000] = 17; ptr[0x1000] = 17;
mem.Unmap(0x6000, 0x2000); mem.Unmap(0x6000, 0x2000, HEAP);
REQUIRE(ptr[0x1000] == 17); REQUIRE(ptr[0x1000] == 17);
} }
TEST_CASE("HostMemory: Partial left unmap check bindings", "[common]") { TEST_CASE("HostMemory: Partial left unmap check bindings", "[common]") {
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
mem.Map(0x4000, 0x10000, 0x4000, PERMS); mem.Map(0x4000, 0x10000, 0x4000, PERMS, HEAP);
volatile u8* const ptr = mem.VirtualBasePointer() + 0x4000; volatile u8* const ptr = mem.VirtualBasePointer() + 0x4000;
ptr[0x3000] = 19; ptr[0x3000] = 19;
ptr[0x3fff] = 12; ptr[0x3fff] = 12;
mem.Unmap(0x4000, 0x2000); mem.Unmap(0x4000, 0x2000, HEAP);
REQUIRE(ptr[0x3000] == 19); REQUIRE(ptr[0x3000] == 19);
REQUIRE(ptr[0x3fff] == 12); REQUIRE(ptr[0x3fff] == 12);
@ -157,13 +158,13 @@ TEST_CASE("HostMemory: Partial left unmap check bindings", "[common]") {
TEST_CASE("HostMemory: Partial middle unmap check bindings", "[common]") { TEST_CASE("HostMemory: Partial middle unmap check bindings", "[common]") {
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
mem.Map(0x4000, 0x10000, 0x4000, PERMS); mem.Map(0x4000, 0x10000, 0x4000, PERMS, HEAP);
volatile u8* const ptr = mem.VirtualBasePointer() + 0x4000; volatile u8* const ptr = mem.VirtualBasePointer() + 0x4000;
ptr[0x0000] = 19; ptr[0x0000] = 19;
ptr[0x3fff] = 12; ptr[0x3fff] = 12;
mem.Unmap(0x1000, 0x2000); mem.Unmap(0x1000, 0x2000, HEAP);
REQUIRE(ptr[0x0000] == 19); REQUIRE(ptr[0x0000] == 19);
REQUIRE(ptr[0x3fff] == 12); REQUIRE(ptr[0x3fff] == 12);
@ -171,14 +172,14 @@ TEST_CASE("HostMemory: Partial middle unmap check bindings", "[common]") {
TEST_CASE("HostMemory: Partial sparse middle unmap and check bindings", "[common]") { TEST_CASE("HostMemory: Partial sparse middle unmap and check bindings", "[common]") {
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
mem.Map(0x4000, 0x10000, 0x2000, PERMS); mem.Map(0x4000, 0x10000, 0x2000, PERMS, HEAP);
mem.Map(0x6000, 0x20000, 0x2000, PERMS); mem.Map(0x6000, 0x20000, 0x2000, PERMS, HEAP);
volatile u8* const ptr = mem.VirtualBasePointer() + 0x4000; volatile u8* const ptr = mem.VirtualBasePointer() + 0x4000;
ptr[0x0000] = 19; ptr[0x0000] = 19;
ptr[0x3fff] = 12; ptr[0x3fff] = 12;
mem.Unmap(0x5000, 0x2000); mem.Unmap(0x5000, 0x2000, HEAP);
REQUIRE(ptr[0x0000] == 19); REQUIRE(ptr[0x0000] == 19);
REQUIRE(ptr[0x3fff] == 12); REQUIRE(ptr[0x3fff] == 12);

View File

@ -16,20 +16,16 @@
namespace { namespace {
// Numbers are chosen randomly to make sure the correct one is given. // Numbers are chosen randomly to make sure the correct one is given.
constexpr std::array<u64, 5> CB_IDS{{42, 144, 93, 1026, UINT64_C(0xFFFF7FFFF7FFFF)}};
constexpr std::array<u64, 5> calls_order{{2, 0, 1, 4, 3}}; constexpr std::array<u64, 5> calls_order{{2, 0, 1, 4, 3}};
std::array<s64, 5> delays{}; std::array<s64, 5> delays{};
std::bitset<5> callbacks_ran_flags;
std::bitset<CB_IDS.size()> callbacks_ran_flags;
u64 expected_callback = 0; u64 expected_callback = 0;
template <unsigned int IDX> template <unsigned int IDX>
std::optional<std::chrono::nanoseconds> HostCallbackTemplate(std::uintptr_t user_data, s64 time, std::optional<std::chrono::nanoseconds> HostCallbackTemplate(s64 time,
std::chrono::nanoseconds ns_late) { std::chrono::nanoseconds ns_late) {
static_assert(IDX < CB_IDS.size(), "IDX out of range"); static_assert(IDX < callbacks_ran_flags.size(), "IDX out of range");
callbacks_ran_flags.set(IDX); callbacks_ran_flags.set(IDX);
REQUIRE(CB_IDS[IDX] == user_data);
REQUIRE(CB_IDS[IDX] == CB_IDS[calls_order[expected_callback]]);
delays[IDX] = ns_late.count(); delays[IDX] = ns_late.count();
++expected_callback; ++expected_callback;
return std::nullopt; return std::nullopt;
@ -76,7 +72,7 @@ TEST_CASE("CoreTiming[BasicOrder]", "[core]") {
const u64 order = calls_order[i]; const u64 order = calls_order[i];
const auto future_ns = std::chrono::nanoseconds{static_cast<s64>(i * one_micro + 100)}; const auto future_ns = std::chrono::nanoseconds{static_cast<s64>(i * one_micro + 100)};
core_timing.ScheduleEvent(future_ns, events[order], CB_IDS[order]); core_timing.ScheduleEvent(future_ns, events[order]);
} }
/// test pause /// test pause
REQUIRE(callbacks_ran_flags.none()); REQUIRE(callbacks_ran_flags.none());
@ -118,7 +114,7 @@ TEST_CASE("CoreTiming[BasicOrderNoPausing]", "[core]") {
for (std::size_t i = 0; i < events.size(); i++) { for (std::size_t i = 0; i < events.size(); i++) {
const u64 order = calls_order[i]; const u64 order = calls_order[i];
const auto future_ns = std::chrono::nanoseconds{static_cast<s64>(i * one_micro + 100)}; const auto future_ns = std::chrono::nanoseconds{static_cast<s64>(i * one_micro + 100)};
core_timing.ScheduleEvent(future_ns, events[order], CB_IDS[order]); core_timing.ScheduleEvent(future_ns, events[order]);
} }
const u64 end = core_timing.GetGlobalTimeNs().count(); const u64 end = core_timing.GetGlobalTimeNs().count();

View File

@ -228,7 +228,7 @@ void MaxwellDMA::CopyBlockLinearToPitch() {
Core::Memory::GpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::SafeRead> tmp_read_buffer( Core::Memory::GpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::SafeRead> tmp_read_buffer(
memory_manager, src_operand.address, src_size, &read_buffer); memory_manager, src_operand.address, src_size, &read_buffer);
Core::Memory::GpuGuestMemoryScoped<u8, Core::Memory::GuestMemoryFlags::SafeReadCachedWrite> Core::Memory::GpuGuestMemoryScoped<u8, Core::Memory::GuestMemoryFlags::UnsafeReadCachedWrite>
tmp_write_buffer(memory_manager, dst_operand.address, dst_size, &write_buffer); tmp_write_buffer(memory_manager, dst_operand.address, dst_size, &write_buffer);
UnswizzleSubrect(tmp_write_buffer, tmp_read_buffer, bytes_per_pixel, width, height, depth, UnswizzleSubrect(tmp_write_buffer, tmp_read_buffer, bytes_per_pixel, width, height, depth,
@ -292,7 +292,7 @@ void MaxwellDMA::CopyPitchToBlockLinear() {
GPUVAddr dst_addr = regs.offset_out; GPUVAddr dst_addr = regs.offset_out;
Core::Memory::GpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::SafeRead> tmp_read_buffer( Core::Memory::GpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::SafeRead> tmp_read_buffer(
memory_manager, src_addr, src_size, &read_buffer); memory_manager, src_addr, src_size, &read_buffer);
Core::Memory::GpuGuestMemoryScoped<u8, Core::Memory::GuestMemoryFlags::SafeReadCachedWrite> Core::Memory::GpuGuestMemoryScoped<u8, Core::Memory::GuestMemoryFlags::UnsafeReadCachedWrite>
tmp_write_buffer(memory_manager, dst_addr, dst_size, &write_buffer); tmp_write_buffer(memory_manager, dst_addr, dst_size, &write_buffer);
// If the input is linear and the output is tiled, swizzle the input and copy it over. // If the input is linear and the output is tiled, swizzle the input and copy it over.

View File

@ -329,7 +329,7 @@ void PresentManager::CopyToSwapchainImpl(Frame* frame) {
// to account for that. // to account for that.
const bool is_suboptimal = swapchain.NeedsRecreation(); const bool is_suboptimal = swapchain.NeedsRecreation();
const bool size_changed = const bool size_changed =
swapchain.GetWidth() != frame->width || swapchain.GetHeight() != frame->height; swapchain.GetWidth() < frame->width || swapchain.GetHeight() < frame->height;
if (is_suboptimal || size_changed) { if (is_suboptimal || size_changed) {
RecreateSwapchain(frame); RecreateSwapchain(frame);
} }

View File

@ -348,43 +348,45 @@ void QtConfig::SaveQtPlayerValues(const std::size_t player_index) {
for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
WriteSetting(std::string(player_prefix).append(Settings::NativeButton::mapping[i]), WriteStringSetting(std::string(player_prefix).append(Settings::NativeButton::mapping[i]),
player.buttons[i], std::make_optional(default_param)); player.buttons[i], std::make_optional(default_param));
} }
for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
default_analogs[i][3], default_stick_mod[i], 0.5f); default_analogs[i][3], default_stick_mod[i], 0.5f);
WriteSetting(std::string(player_prefix).append(Settings::NativeAnalog::mapping[i]), WriteStringSetting(std::string(player_prefix).append(Settings::NativeAnalog::mapping[i]),
player.analogs[i], std::make_optional(default_param)); player.analogs[i], std::make_optional(default_param));
} }
for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) { for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) {
const std::string default_param = InputCommon::GenerateKeyboardParam(default_motions[i]); const std::string default_param = InputCommon::GenerateKeyboardParam(default_motions[i]);
WriteSetting(std::string(player_prefix).append(Settings::NativeMotion::mapping[i]), WriteStringSetting(std::string(player_prefix).append(Settings::NativeMotion::mapping[i]),
player.motions[i], std::make_optional(default_param)); player.motions[i], std::make_optional(default_param));
} }
} }
void QtConfig::SaveDebugControlValues() { void QtConfig::SaveDebugControlValues() {
for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
WriteSetting(std::string("debug_pad_").append(Settings::NativeButton::mapping[i]), WriteStringSetting(std::string("debug_pad_").append(Settings::NativeButton::mapping[i]),
Settings::values.debug_pad_buttons[i], std::make_optional(default_param)); Settings::values.debug_pad_buttons[i],
std::make_optional(default_param));
} }
for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
default_analogs[i][3], default_stick_mod[i], 0.5f); default_analogs[i][3], default_stick_mod[i], 0.5f);
WriteSetting(std::string("debug_pad_").append(Settings::NativeAnalog::mapping[i]), WriteStringSetting(std::string("debug_pad_").append(Settings::NativeAnalog::mapping[i]),
Settings::values.debug_pad_analogs[i], std::make_optional(default_param)); Settings::values.debug_pad_analogs[i],
std::make_optional(default_param));
} }
} }
void QtConfig::SaveHidbusValues() { void QtConfig::SaveHidbusValues() {
const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
0, 0, default_ringcon_analogs[0], default_ringcon_analogs[1], 0, 0.05f); 0, 0, default_ringcon_analogs[0], default_ringcon_analogs[1], 0, 0.05f);
WriteSetting(std::string("ring_controller"), Settings::values.ringcon_analogs, WriteStringSetting(std::string("ring_controller"), Settings::values.ringcon_analogs,
std::make_optional(default_param)); std::make_optional(default_param));
} }
void QtConfig::SaveQtControlValues() { void QtConfig::SaveQtControlValues() {
@ -409,19 +411,20 @@ void QtConfig::SavePathValues() {
WriteCategory(Settings::Category::Paths); WriteCategory(Settings::Category::Paths);
WriteSetting(std::string("romsPath"), UISettings::values.roms_path); WriteStringSetting(std::string("romsPath"), UISettings::values.roms_path);
BeginArray(std::string("gamedirs")); BeginArray(std::string("gamedirs"));
for (int i = 0; i < UISettings::values.game_dirs.size(); ++i) { for (int i = 0; i < UISettings::values.game_dirs.size(); ++i) {
SetArrayIndex(i); SetArrayIndex(i);
const auto& game_dir = UISettings::values.game_dirs[i]; const auto& game_dir = UISettings::values.game_dirs[i];
WriteSetting(std::string("path"), game_dir.path); WriteStringSetting(std::string("path"), game_dir.path);
WriteSetting(std::string("deep_scan"), game_dir.deep_scan, std::make_optional(false)); WriteBooleanSetting(std::string("deep_scan"), game_dir.deep_scan,
WriteSetting(std::string("expanded"), game_dir.expanded, std::make_optional(true)); std::make_optional(false));
WriteBooleanSetting(std::string("expanded"), game_dir.expanded, std::make_optional(true));
} }
EndArray(); EndArray();
WriteSetting(std::string("recentFiles"), WriteStringSetting(std::string("recentFiles"),
UISettings::values.recent_files.join(QStringLiteral(", ")).toStdString()); UISettings::values.recent_files.join(QStringLiteral(", ")).toStdString());
EndGroup(); EndGroup();
} }
@ -438,14 +441,14 @@ void QtConfig::SaveShortcutValues() {
BeginGroup(group); BeginGroup(group);
BeginGroup(name); BeginGroup(name);
WriteSetting(std::string("KeySeq"), shortcut.keyseq, WriteStringSetting(std::string("KeySeq"), shortcut.keyseq,
std::make_optional(default_hotkey.keyseq)); std::make_optional(default_hotkey.keyseq));
WriteSetting(std::string("Controller_KeySeq"), shortcut.controller_keyseq, WriteStringSetting(std::string("Controller_KeySeq"), shortcut.controller_keyseq,
std::make_optional(default_hotkey.controller_keyseq)); std::make_optional(default_hotkey.controller_keyseq));
WriteSetting(std::string("Context"), shortcut.context, WriteIntegerSetting(std::string("Context"), shortcut.context,
std::make_optional(default_hotkey.context)); std::make_optional(default_hotkey.context));
WriteSetting(std::string("Repeat"), shortcut.repeat, WriteBooleanSetting(std::string("Repeat"), shortcut.repeat,
std::make_optional(default_hotkey.repeat)); std::make_optional(default_hotkey.repeat));
EndGroup(); // name EndGroup(); // name
EndGroup(); // group EndGroup(); // group
@ -460,9 +463,10 @@ void QtConfig::SaveUIValues() {
WriteCategory(Settings::Category::Ui); WriteCategory(Settings::Category::Ui);
WriteCategory(Settings::Category::UiGeneral); WriteCategory(Settings::Category::UiGeneral);
WriteSetting(std::string("theme"), UISettings::values.theme, WriteStringSetting(
std::make_optional(std::string( std::string("theme"), UISettings::values.theme,
UISettings::themes[static_cast<size_t>(UISettings::default_theme)].second))); std::make_optional(std::string(
UISettings::themes[static_cast<size_t>(UISettings::default_theme)].second)));
SaveUIGamelistValues(); SaveUIGamelistValues();
SaveUILayoutValues(); SaveUILayoutValues();
@ -482,7 +486,7 @@ void QtConfig::SaveUIGamelistValues() {
BeginArray(std::string("favorites")); BeginArray(std::string("favorites"));
for (int i = 0; i < UISettings::values.favorited_ids.size(); i++) { for (int i = 0; i < UISettings::values.favorited_ids.size(); i++) {
SetArrayIndex(i); SetArrayIndex(i);
WriteSetting(std::string("program_id"), UISettings::values.favorited_ids[i]); WriteIntegerSetting(std::string("program_id"), UISettings::values.favorited_ids[i]);
} }
EndArray(); // favorites EndArray(); // favorites
@ -506,14 +510,15 @@ void QtConfig::SaveMultiplayerValues() {
BeginArray(std::string("username_ban_list")); BeginArray(std::string("username_ban_list"));
for (std::size_t i = 0; i < UISettings::values.multiplayer_ban_list.first.size(); ++i) { for (std::size_t i = 0; i < UISettings::values.multiplayer_ban_list.first.size(); ++i) {
SetArrayIndex(static_cast<int>(i)); SetArrayIndex(static_cast<int>(i));
WriteSetting(std::string("username"), UISettings::values.multiplayer_ban_list.first[i]); WriteStringSetting(std::string("username"),
UISettings::values.multiplayer_ban_list.first[i]);
} }
EndArray(); // username_ban_list EndArray(); // username_ban_list
BeginArray(std::string("ip_ban_list")); BeginArray(std::string("ip_ban_list"));
for (std::size_t i = 0; i < UISettings::values.multiplayer_ban_list.second.size(); ++i) { for (std::size_t i = 0; i < UISettings::values.multiplayer_ban_list.second.size(); ++i) {
SetArrayIndex(static_cast<int>(i)); SetArrayIndex(static_cast<int>(i));
WriteSetting(std::string("ip"), UISettings::values.multiplayer_ban_list.second[i]); WriteStringSetting(std::string("ip"), UISettings::values.multiplayer_ban_list.second[i]);
} }
EndArray(); // ip_ban_list EndArray(); // ip_ban_list

View File

@ -213,43 +213,45 @@ void SdlConfig::SaveSdlPlayerValues(const std::size_t player_index) {
for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
WriteSetting(std::string(player_prefix).append(Settings::NativeButton::mapping[i]), WriteStringSetting(std::string(player_prefix).append(Settings::NativeButton::mapping[i]),
player.buttons[i], std::make_optional(default_param)); player.buttons[i], std::make_optional(default_param));
} }
for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
default_analogs[i][3], default_stick_mod[i], 0.5f); default_analogs[i][3], default_stick_mod[i], 0.5f);
WriteSetting(std::string(player_prefix).append(Settings::NativeAnalog::mapping[i]), WriteStringSetting(std::string(player_prefix).append(Settings::NativeAnalog::mapping[i]),
player.analogs[i], std::make_optional(default_param)); player.analogs[i], std::make_optional(default_param));
} }
for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) { for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) {
const std::string default_param = InputCommon::GenerateKeyboardParam(default_motions[i]); const std::string default_param = InputCommon::GenerateKeyboardParam(default_motions[i]);
WriteSetting(std::string(player_prefix).append(Settings::NativeMotion::mapping[i]), WriteStringSetting(std::string(player_prefix).append(Settings::NativeMotion::mapping[i]),
player.motions[i], std::make_optional(default_param)); player.motions[i], std::make_optional(default_param));
} }
} }
void SdlConfig::SaveDebugControlValues() { void SdlConfig::SaveDebugControlValues() {
for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
WriteSetting(std::string("debug_pad_").append(Settings::NativeButton::mapping[i]), WriteStringSetting(std::string("debug_pad_").append(Settings::NativeButton::mapping[i]),
Settings::values.debug_pad_buttons[i], std::make_optional(default_param)); Settings::values.debug_pad_buttons[i],
std::make_optional(default_param));
} }
for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
default_analogs[i][3], default_stick_mod[i], 0.5f); default_analogs[i][3], default_stick_mod[i], 0.5f);
WriteSetting(std::string("debug_pad_").append(Settings::NativeAnalog::mapping[i]), WriteStringSetting(std::string("debug_pad_").append(Settings::NativeAnalog::mapping[i]),
Settings::values.debug_pad_analogs[i], std::make_optional(default_param)); Settings::values.debug_pad_analogs[i],
std::make_optional(default_param));
} }
} }
void SdlConfig::SaveHidbusValues() { void SdlConfig::SaveHidbusValues() {
const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
0, 0, default_ringcon_analogs[0], default_ringcon_analogs[1], 0, 0.05f); 0, 0, default_ringcon_analogs[0], default_ringcon_analogs[1], 0, 0.05f);
WriteSetting(std::string("ring_controller"), Settings::values.ringcon_analogs, WriteStringSetting(std::string("ring_controller"), Settings::values.ringcon_analogs,
std::make_optional(default_param)); std::make_optional(default_param));
} }
std::vector<Settings::BasicSetting*>& SdlConfig::FindRelevantList(Settings::Category category) { std::vector<Settings::BasicSetting*>& SdlConfig::FindRelevantList(Settings::Category category) {