Compare commits

...

40 Commits

Author SHA1 Message Date
21e859ff2b Android #144 2023-11-28 00:57:20 +00:00
b1b0ce1a59 Merge PR 12204 2023-11-28 00:57:20 +00:00
2ef3268944 Merge PR 12074 2023-11-28 00:57:20 +00:00
c7649a0cdb Merge pull request #12183 from german77/justmii
service: nfc: Validate mii data
2023-11-27 11:56:37 -05:00
5a96c525e3 Merge pull request #12160 from german77/mouse_constrain
yuzu: Constrain mouse in render window when emulated
2023-11-27 11:56:24 -05:00
f21340f7aa Merge pull request #11535 from GPUCode/upload_cmdbuf
renderer_vulkan: Introduce separate cmd buffer for uploads
2023-11-26 18:33:05 +01:00
e0c894408a Merge pull request #12180 from german77/cabinetmii
service: am: Create random mii for cabinet applet
2023-11-26 09:50:08 -05:00
257a1c884d Merge pull request #12156 from german77/firmwarevs
yuzu: Display firmware version
2023-11-26 09:49:53 -05:00
c100d7e802 Merge pull request #12175 from abouvier/unbundle-simpleini
cmake: prefer system simpleini library
2023-11-26 09:49:43 -05:00
281eb020ea service: nfc: Validate mii data 2023-11-25 23:40:01 -06:00
4ce6762945 service: am: Create random mii for cabinet applet 2023-11-25 21:31:05 -06:00
fe3702223f cmake: prefer system simpleini library 2023-11-26 03:45:10 +01:00
83aa66b17d Merge pull request #12166 from german77/redmagic
android: input: Flip abxy on redmagic controllers
2023-11-25 20:56:40 -05:00
8d7a55be5b Merge pull request #12169 from liamwhite/its-time-to-stop
time: undef GetCurrentTime for Windows
2023-11-25 20:56:31 -05:00
de58618421 android: input: Flip abxy on backbone labs controllers 2023-11-25 18:22:03 -06:00
e6847c65a8 time: undef GetCurrentTime for Windows 2023-11-25 16:18:02 -05:00
b5bde8451c android: input: Flip abxy on redmagic controllers 2023-11-25 10:53:59 -06:00
090ea0281c Merge pull request #12153 from liamwhite/deck2
renderer_vulkan: exclude steam deck oled from force max clock setting
2023-11-25 10:17:18 -05:00
bc4818b058 Merge pull request #12151 from german77/no-crash
core: hid: Ensure output devices are initialized
2023-11-25 10:17:11 -05:00
2993d3bb49 Merge pull request #12110 from liamwhite/mali-nullview
vk_texture_cache: add workaround for nullDescriptor on Mali
2023-11-25 10:17:00 -05:00
5a182f4e7c Merge pull request #11889 from t895/ini-lib
configuration: Unify config handling across frontends
2023-11-24 22:59:55 -05:00
f61cf14646 yuzu: Constrain mouse in render window when emulated 2023-11-24 19:32:35 -06:00
2d4e7c8264 yuzu: Display firmware version 2023-11-24 12:58:07 -06:00
4a278b69b1 renderer_vulkan: exclude steam deck oled from force max clock setting 2023-11-24 11:38:39 -05:00
02a0b41a15 core: hid: Ensure output devices are initialized 2023-11-24 10:18:16 -06:00
dda187d300 frontend_common: Don't specify default value for screenshot_path 2023-11-23 00:36:45 -05:00
0b8218d8eb frontend_common: Don't load config files that we fail to open 2023-11-23 00:30:49 -05:00
d8f380961e frontend_common: Add option to read unsigned integers 2023-11-21 21:01:46 -05:00
1654b8f9e0 frontend_common: Set config array size to 0 if the array is ended without changing its index 2023-11-21 15:14:41 -05:00
14398a1cbb frontend_common: Manually handle opening config file
SimpleIni only has the ability to use ANSI strings for config paths so this breaks opening configs on paths with special characters. This ensures that we open the right path on each platform.
2023-11-21 14:53:32 -05:00
f3fe362c93 frontend_common: Add special config case for unmapped windows network drives
Normally we save paths with '/' as the delimiter for each segment but when you manually select a network drive instead of using a mapped location, this would break. This ensures that if the read filesystem location starts with '//', we keep that pattern.
2023-11-21 01:58:13 -05:00
eedecaef96 frontend_common: Disable UTF-8 BOM in config 2023-11-21 01:58:13 -05:00
e637ec0c38 android: Rework config lifecycle
Exposes options for initializing, unloading, reloading, and saving settings that let us update the config much more granularly based on what's happening in the UI.
2023-11-21 01:58:13 -05:00
e744c06f61 android: Remove ini4j 2023-11-21 01:58:13 -05:00
ac522db857 common: settings: Add ifdefs to define android's default settings 2023-11-21 01:58:13 -05:00
aa20311969 general: Remove inih 2023-11-21 01:58:13 -05:00
da14c7b8e4 config: Unify config handling under frontend_common
Replaces every way of handling config for each frontend with SimpleIni. frontend_common's Config class is at the center where it saves and loads all of the cross-platform settings and provides a set of pure virtual functions for platform specific settings.

As a result of making config handling platform specific, several parts had to be moved to each platform's own config class or to other parts. Default keys were put in platform specific config classes and translatable strings for Qt were moved to shared_translation. Default hotkeys, default_theme, window geometry, and qt metatypes were moved to uisettings. Additionally, to reduce dependence on Qt, QStrings were converted to std::strings where applicable.
2023-11-21 01:58:13 -05:00
e7878e3cf8 vk_texture_cache: add workaround for nullDescriptor on Mali 2023-11-20 20:09:12 -05:00
50bcfa5fb9 Vulkan: Add a final barrier to the upload command buffer 2023-11-12 20:58:30 +01:00
efc50485b8 renderer_vulkan: Introduce separate cmd buffer for uploads 2023-11-12 20:27:39 +01:00
178 changed files with 6623 additions and 4098 deletions

View File

@ -3,4 +3,4 @@
[codespell] [codespell]
skip = ./.git,./build,./dist,./Doxyfile,./externals,./LICENSES,./src/android/app/src/main/res skip = ./.git,./build,./dist,./Doxyfile,./externals,./LICENSES,./src/android/app/src/main/res
ignore-words-list = aci,allright,ba,canonicalizations,deques,froms,hda,inout,lod,masia,nam,nax,nd,optin,pullrequests,pullrequest,te,transfered,unstall,uscaled,vas,zink ignore-words-list = aci,allright,ba,canonicalizations,deques,froms,hda,inout,lod,masia,nam,nax,nce,nd,optin,pullrequests,pullrequest,te,transfered,unstall,uscaled,vas,zink

9
.gitmodules vendored
View File

@ -4,9 +4,6 @@
[submodule "enet"] [submodule "enet"]
path = externals/enet path = externals/enet
url = https://github.com/lsalzman/enet.git url = https://github.com/lsalzman/enet.git
[submodule "inih"]
path = externals/inih/inih
url = https://github.com/benhoyt/inih.git
[submodule "cubeb"] [submodule "cubeb"]
path = externals/cubeb path = externals/cubeb
url = https://github.com/mozilla/cubeb.git url = https://github.com/mozilla/cubeb.git
@ -61,3 +58,9 @@
[submodule "breakpad"] [submodule "breakpad"]
path = externals/breakpad path = externals/breakpad
url = https://github.com/yuzu-emu/breakpad.git url = https://github.com/yuzu-emu/breakpad.git
[submodule "simpleini"]
path = externals/simpleini
url = https://github.com/brofield/simpleini.git
[submodule "oaknut"]
path = externals/oaknut
url = https://github.com/merryhime/oaknut

View File

@ -260,6 +260,11 @@ if (UNIX)
add_definitions(-DYUZU_UNIX=1) add_definitions(-DYUZU_UNIX=1)
endif() endif()
if (ARCHITECTURE_arm64 AND (ANDROID OR ${CMAKE_SYSTEM_NAME} STREQUAL "Linux"))
set(HAS_NCE 1)
add_definitions(-DHAS_NCE=1)
endif()
# Configure C++ standard # Configure C++ standard
# =========================== # ===========================
@ -285,12 +290,12 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin)
find_package(Boost 1.79.0 REQUIRED context) find_package(Boost 1.79.0 REQUIRED context)
find_package(enet 1.3 MODULE) find_package(enet 1.3 MODULE)
find_package(fmt 9 REQUIRED) find_package(fmt 9 REQUIRED)
find_package(inih 52 MODULE COMPONENTS INIReader)
find_package(LLVM 17.0.2 MODULE COMPONENTS Demangle) find_package(LLVM 17.0.2 MODULE COMPONENTS Demangle)
find_package(lz4 REQUIRED) find_package(lz4 REQUIRED)
find_package(nlohmann_json 3.8 REQUIRED) find_package(nlohmann_json 3.8 REQUIRED)
find_package(Opus 1.3 MODULE) find_package(Opus 1.3 MODULE)
find_package(RenderDoc MODULE) find_package(RenderDoc MODULE)
find_package(SimpleIni MODULE)
find_package(stb MODULE) find_package(stb MODULE)
find_package(VulkanMemoryAllocator CONFIG) find_package(VulkanMemoryAllocator CONFIG)
find_package(ZLIB 1.2 REQUIRED) find_package(ZLIB 1.2 REQUIRED)

View File

@ -0,0 +1,19 @@
# SPDX-FileCopyrightText: 2023 Alexandre Bouvier <contact@amb.tf>
#
# SPDX-License-Identifier: GPL-3.0-or-later
find_path(SimpleIni_INCLUDE_DIR SimpleIni.h)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(SimpleIni
REQUIRED_VARS SimpleIni_INCLUDE_DIR
)
if (SimpleIni_FOUND AND NOT TARGET SimpleIni::SimpleIni)
add_library(SimpleIni::SimpleIni INTERFACE IMPORTED)
set_target_properties(SimpleIni::SimpleIni PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${SimpleIni_INCLUDE_DIR}"
)
endif()
mark_as_advanced(SimpleIni_INCLUDE_DIR)

View File

@ -1,27 +0,0 @@
# SPDX-FileCopyrightText: 2022 Alexandre Bouvier <contact@amb.tf>
#
# SPDX-License-Identifier: GPL-3.0-or-later
find_package(PkgConfig QUIET)
pkg_search_module(INIH QUIET IMPORTED_TARGET inih)
if (INIReader IN_LIST inih_FIND_COMPONENTS)
pkg_search_module(INIREADER QUIET IMPORTED_TARGET INIReader)
if (INIREADER_FOUND)
set(inih_INIReader_FOUND TRUE)
endif()
endif()
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(inih
REQUIRED_VARS INIH_LINK_LIBRARIES
VERSION_VAR INIH_VERSION
HANDLE_COMPONENTS
)
if (inih_FOUND AND NOT TARGET inih::inih)
add_library(inih::inih ALIAS PkgConfig::INIH)
endif()
if (inih_FOUND AND inih_INIReader_FOUND AND NOT TARGET inih::INIReader)
add_library(inih::INIReader ALIAS PkgConfig::INIREADER)
endif()

View File

@ -1,3 +1,13 @@
| Pull Request | Commit | Title | Author | Merged? |
|----|----|----|----|----|
| [12074](https://github.com/yuzu-emu/yuzu//pull/12074) | [`a76a8fb5f`](https://github.com/yuzu-emu/yuzu//pull/12074/files) | Implement Native Code Execution (NCE) | [GPUCode](https://github.com/GPUCode/) | Yes |
| [12204](https://github.com/yuzu-emu/yuzu//pull/12204) | [`caa83609f`](https://github.com/yuzu-emu/yuzu//pull/12204/files) | android: Multi directory UI | [t895](https://github.com/t895/) | 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

@ -20,6 +20,10 @@ if ((ARCHITECTURE_x86 OR ARCHITECTURE_x86_64) AND NOT TARGET xbyak::xbyak)
endif() endif()
# Dynarmic # Dynarmic
if (ARCHITECTURE_arm64 AND NOT TARGET merry::oaknut)
add_subdirectory(oaknut)
endif()
if ((ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64) AND NOT TARGET dynarmic::dynarmic) if ((ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64) AND NOT TARGET dynarmic::dynarmic)
set(DYNARMIC_IGNORE_ASSERTS ON) set(DYNARMIC_IGNORE_ASSERTS ON)
add_subdirectory(dynarmic) add_subdirectory(dynarmic)
@ -34,11 +38,6 @@ endif()
# Glad # Glad
add_subdirectory(glad) add_subdirectory(glad)
# inih
if (NOT TARGET inih::INIReader)
add_subdirectory(inih)
endif()
# mbedtls # mbedtls
add_subdirectory(mbedtls) add_subdirectory(mbedtls)
target_include_directories(mbedtls PUBLIC ./mbedtls/include) target_include_directories(mbedtls PUBLIC ./mbedtls/include)
@ -295,3 +294,8 @@ if (YUZU_CRASH_DUMPS AND NOT TARGET libbreakpad_client)
target_link_libraries(dump_syms PRIVATE libbreakpad_client ZLIB::ZLIB) target_link_libraries(dump_syms PRIVATE libbreakpad_client ZLIB::ZLIB)
endif() endif()
endif() endif()
# SimpleIni
if (NOT TARGET SimpleIni::SimpleIni)
add_subdirectory(simpleini)
endif()

View File

@ -1,13 +0,0 @@
# SPDX-FileCopyrightText: 2014 Gui Andrade <admin@archshift.com>
# SPDX-License-Identifier: GPL-2.0-or-later
add_library(inih
inih/ini.c
inih/ini.h
inih/cpp/INIReader.cpp
inih/cpp/INIReader.h
)
create_target_directory_groups(inih)
target_include_directories(inih INTERFACE inih/cpp)
add_library(inih::INIReader ALIAS inih)

1
externals/inih/inih vendored

Submodule externals/inih/inih deleted from 9cecf0643d

1
externals/oaknut vendored Submodule

Submodule externals/oaknut added at 918bd94f02

1
externals/simpleini vendored Submodule

Submodule externals/simpleini added at 382ddbb4b9

View File

@ -187,6 +187,7 @@ add_subdirectory(audio_core)
add_subdirectory(video_core) add_subdirectory(video_core)
add_subdirectory(network) add_subdirectory(network)
add_subdirectory(input_common) add_subdirectory(input_common)
add_subdirectory(frontend_common)
add_subdirectory(shader_recompiler) add_subdirectory(shader_recompiler)
if (YUZU_ROOM) if (YUZU_ROOM)

View File

@ -219,7 +219,6 @@ dependencies {
implementation("io.coil-kt:coil:2.2.2") implementation("io.coil-kt:coil:2.2.2")
implementation("androidx.core:core-splashscreen:1.0.1") implementation("androidx.core:core-splashscreen:1.0.1")
implementation("androidx.window:window:1.2.0-beta03") implementation("androidx.window:window:1.2.0-beta03")
implementation("org.ini4j:ini4j:0.5.4")
implementation("androidx.constraintlayout:constraintlayout:2.1.4") implementation("androidx.constraintlayout:constraintlayout:2.1.4")
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0") implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
implementation("androidx.navigation:navigation-fragment-ktx:2.7.4") implementation("androidx.navigation:navigation-fragment-ktx:2.7.4")

View File

@ -230,8 +230,6 @@ object NativeLibrary {
*/ */
external fun onTouchReleased(finger_id: Int) external fun onTouchReleased(finger_id: Int)
external fun reloadSettings()
external fun initGameIni(gameID: String?) external fun initGameIni(gameID: String?)
external fun setAppDirectory(directory: String) external fun setAppDirectory(directory: String)
@ -301,6 +299,11 @@ object NativeLibrary {
*/ */
external fun getPerfStats(): DoubleArray external fun getPerfStats(): DoubleArray
/**
* Returns the current CPU backend.
*/
external fun getCpuBackend(): String
/** /**
* Notifies the core emulation that the orientation has changed. * Notifies the core emulation that the orientation has changed.
*/ */

View File

@ -0,0 +1,76 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.adapters
import android.net.Uri
import android.text.TextUtils
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.fragment.app.FragmentActivity
import androidx.recyclerview.widget.AsyncDifferConfig
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import org.yuzu.yuzu_emu.databinding.CardFolderBinding
import org.yuzu.yuzu_emu.fragments.GameFolderPropertiesDialogFragment
import org.yuzu.yuzu_emu.model.GameDir
import org.yuzu.yuzu_emu.model.GamesViewModel
class FolderAdapter(val activity: FragmentActivity, val gamesViewModel: GamesViewModel) :
ListAdapter<GameDir, FolderAdapter.FolderViewHolder>(
AsyncDifferConfig.Builder(DiffCallback()).build()
) {
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): FolderAdapter.FolderViewHolder {
CardFolderBinding.inflate(LayoutInflater.from(parent.context), parent, false)
.also { return FolderViewHolder(it) }
}
override fun onBindViewHolder(holder: FolderAdapter.FolderViewHolder, position: Int) =
holder.bind(currentList[position])
inner class FolderViewHolder(val binding: CardFolderBinding) :
RecyclerView.ViewHolder(binding.root) {
private lateinit var gameDir: GameDir
fun bind(gameDir: GameDir) {
this.gameDir = gameDir
binding.apply {
path.text = Uri.parse(gameDir.uriString).path
path.postDelayed(
{
path.isSelected = true
path.ellipsize = TextUtils.TruncateAt.MARQUEE
},
3000
)
buttonEdit.setOnClickListener {
GameFolderPropertiesDialogFragment.newInstance(this@FolderViewHolder.gameDir)
.show(
activity.supportFragmentManager,
GameFolderPropertiesDialogFragment.TAG
)
}
buttonDelete.setOnClickListener {
gamesViewModel.removeFolder(this@FolderViewHolder.gameDir)
}
}
}
}
private class DiffCallback : DiffUtil.ItemCallback<GameDir>() {
override fun areItemsTheSame(oldItem: GameDir, newItem: GameDir): Boolean {
return oldItem == newItem
}
override fun areContentsTheSame(oldItem: GameDir, newItem: GameDir): Boolean {
return oldItem == newItem
}
}
}

View File

@ -10,6 +10,7 @@ enum class IntSetting(
override val category: Settings.Category, override val category: Settings.Category,
override val androidDefault: Int? = null override val androidDefault: Int? = null
) : AbstractIntSetting { ) : AbstractIntSetting {
CPU_BACKEND("cpu_backend", Settings.Category.Cpu),
CPU_ACCURACY("cpu_accuracy", Settings.Category.Cpu), CPU_ACCURACY("cpu_accuracy", Settings.Category.Cpu),
REGION_INDEX("region_index", Settings.Category.System), REGION_INDEX("region_index", Settings.Category.System),
LANGUAGE_INDEX("language_index", Settings.Category.System), LANGUAGE_INDEX("language_index", Settings.Category.System),

View File

@ -3,33 +3,9 @@
package org.yuzu.yuzu_emu.features.settings.model package org.yuzu.yuzu_emu.features.settings.model
import android.text.TextUtils
import android.widget.Toast
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.utils.SettingsFile
object Settings { object Settings {
private val context get() = YuzuApplication.appContext
fun saveSettings(gameId: String = "") {
if (TextUtils.isEmpty(gameId)) {
Toast.makeText(
context,
context.getString(R.string.ini_saved),
Toast.LENGTH_SHORT
).show()
SettingsFile.saveFile(SettingsFile.FILE_NAME_CONFIG)
} else {
// TODO: Save custom game settings
Toast.makeText(
context,
context.getString(R.string.gameid_saved, gameId),
Toast.LENGTH_SHORT
).show()
}
}
enum class Category { enum class Category {
Android, Android,
Audio, Audio,

View File

@ -77,6 +77,15 @@ abstract class SettingsItem(
"%" "%"
) )
) )
put(
SingleChoiceSetting(
IntSetting.CPU_BACKEND,
R.string.cpu_backend,
0,
R.array.cpuBackendArm64Names,
R.array.cpuBackendArm64Values
)
)
put( put(
SingleChoiceSetting( SingleChoiceSetting(
IntSetting.CPU_ACCURACY, IntSetting.CPU_ACCURACY,

View File

@ -19,13 +19,13 @@ import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.fragment.NavHostFragment import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.navArgs import androidx.navigation.navArgs
import com.google.android.material.color.MaterialColors import com.google.android.material.color.MaterialColors
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.NativeLibrary
import java.io.IOException import java.io.IOException
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding
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.fragments.ResetSettingsDialogFragment import org.yuzu.yuzu_emu.fragments.ResetSettingsDialogFragment
import org.yuzu.yuzu_emu.model.SettingsViewModel import org.yuzu.yuzu_emu.model.SettingsViewModel
@ -54,10 +54,6 @@ class SettingsActivity : AppCompatActivity() {
WindowCompat.setDecorFitsSystemWindows(window, false) WindowCompat.setDecorFitsSystemWindows(window, false)
if (savedInstanceState != null) {
settingsViewModel.shouldSave = savedInstanceState.getBoolean(KEY_SHOULD_SAVE)
}
if (InsetsHelper.getSystemGestureType(applicationContext) != if (InsetsHelper.getSystemGestureType(applicationContext) !=
InsetsHelper.GESTURE_NAVIGATION InsetsHelper.GESTURE_NAVIGATION
) { ) {
@ -128,12 +124,6 @@ class SettingsActivity : AppCompatActivity() {
} }
} }
override fun onSaveInstanceState(outState: Bundle) {
// Critical: If super method is not called, rotations will be busted.
super.onSaveInstanceState(outState)
outState.putBoolean(KEY_SHOULD_SAVE, settingsViewModel.shouldSave)
}
override fun onStart() { override fun onStart() {
super.onStart() super.onStart()
// TODO: Load custom settings contextually // TODO: Load custom settings contextually
@ -142,16 +132,10 @@ class SettingsActivity : AppCompatActivity() {
} }
} }
/**
* If this is called, the user has left the settings screen (potentially through the
* home button) and will expect their changes to be persisted. So we kick off an
* IntentService which will do so on a background thread.
*/
override fun onStop() { override fun onStop() {
super.onStop() super.onStop()
if (isFinishing && settingsViewModel.shouldSave) { CoroutineScope(Dispatchers.IO).launch {
Log.debug("[SettingsActivity] Settings activity stopping. Saving settings to INI...") NativeConfig.saveSettings()
Settings.saveSettings()
} }
} }
@ -161,15 +145,13 @@ class SettingsActivity : AppCompatActivity() {
} }
fun onSettingsReset() { fun onSettingsReset() {
// Prevents saving to a non-existent settings file
settingsViewModel.shouldSave = false
// Delete settings file because the user may have changed values that do not exist in the UI // Delete settings file because the user may have changed values that do not exist in the UI
NativeConfig.unloadConfig()
val settingsFile = SettingsFile.getSettingsFile(SettingsFile.FILE_NAME_CONFIG) val settingsFile = SettingsFile.getSettingsFile(SettingsFile.FILE_NAME_CONFIG)
if (!settingsFile.delete()) { if (!settingsFile.delete()) {
throw IOException("Failed to delete $settingsFile") throw IOException("Failed to delete $settingsFile")
} }
NativeLibrary.reloadSettings() NativeConfig.initializeConfig()
Toast.makeText( Toast.makeText(
applicationContext, applicationContext,
@ -194,8 +176,4 @@ class SettingsActivity : AppCompatActivity() {
windowInsets windowInsets
} }
} }
companion object {
private const val KEY_SHOULD_SAVE = "should_save"
}
} }

View File

@ -105,7 +105,6 @@ class SettingsAdapter(
fun onBooleanClick(item: SwitchSetting, checked: Boolean) { fun onBooleanClick(item: SwitchSetting, checked: Boolean) {
item.checked = checked item.checked = checked
settingsViewModel.setShouldReloadSettingsList(true) settingsViewModel.setShouldReloadSettingsList(true)
settingsViewModel.shouldSave = true
} }
fun onSingleChoiceClick(item: SingleChoiceSetting, position: Int) { fun onSingleChoiceClick(item: SingleChoiceSetting, position: Int) {
@ -161,7 +160,6 @@ class SettingsAdapter(
epochTime += timePicker.hour.toLong() * 60 * 60 epochTime += timePicker.hour.toLong() * 60 * 60
epochTime += timePicker.minute.toLong() * 60 epochTime += timePicker.minute.toLong() * 60
if (item.value != epochTime) { if (item.value != epochTime) {
settingsViewModel.shouldSave = true
notifyItemChanged(position) notifyItemChanged(position)
item.value = epochTime item.value = epochTime
} }

View File

@ -269,6 +269,7 @@ class SettingsFragmentPresenter(
add(BooleanSetting.RENDERER_DEBUG.key) add(BooleanSetting.RENDERER_DEBUG.key)
add(HeaderSetting(R.string.cpu)) add(HeaderSetting(R.string.cpu))
add(IntSetting.CPU_BACKEND.key)
add(IntSetting.CPU_ACCURACY.key) add(IntSetting.CPU_ACCURACY.key)
add(BooleanSetting.CPU_DEBUG_MODE.key) add(BooleanSetting.CPU_DEBUG_MODE.key)
add(SettingsItem.FASTMEM_COMBINED) add(SettingsItem.FASTMEM_COMBINED)

View File

@ -3,15 +3,8 @@
package org.yuzu.yuzu_emu.features.settings.utils package org.yuzu.yuzu_emu.features.settings.utils
import android.widget.Toast
import java.io.* import java.io.*
import org.ini4j.Wini
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.features.settings.model.*
import org.yuzu.yuzu_emu.utils.DirectoryInitialization import org.yuzu.yuzu_emu.utils.DirectoryInitialization
import org.yuzu.yuzu_emu.utils.Log
import org.yuzu.yuzu_emu.utils.NativeConfig
/** /**
* Contains static methods for interacting with .ini files in which settings are stored. * Contains static methods for interacting with .ini files in which settings are stored.
@ -19,41 +12,6 @@ import org.yuzu.yuzu_emu.utils.NativeConfig
object SettingsFile { object SettingsFile {
const val FILE_NAME_CONFIG = "config" const val FILE_NAME_CONFIG = "config"
/**
* Saves a Settings HashMap to a given .ini file on disk. If unsuccessful, outputs an error
* telling why it failed.
*
* @param fileName The target filename without a path or extension.
*/
fun saveFile(fileName: String) {
val ini = getSettingsFile(fileName)
try {
val wini = Wini(ini)
for (specificCategory in Settings.Category.values()) {
val categoryHeader = NativeConfig.getConfigHeader(specificCategory.ordinal)
for (setting in Settings.settingsList) {
if (setting.key!!.isEmpty()) continue
val settingCategoryHeader =
NativeConfig.getConfigHeader(setting.category.ordinal)
val iniSetting: String? = wini.get(categoryHeader, setting.key)
if (iniSetting != null || settingCategoryHeader == categoryHeader) {
wini.put(settingCategoryHeader, setting.key, setting.valueAsString)
}
}
}
wini.store()
} catch (e: IOException) {
Log.error("[SettingsFile] File not found: " + fileName + ".ini: " + e.message)
val context = YuzuApplication.appContext
Toast.makeText(
context,
context.getString(R.string.error_saving, fileName, e.message),
Toast.LENGTH_SHORT
).show()
}
}
fun getSettingsFile(fileName: String): File = fun getSettingsFile(fileName: String): File =
File(DirectoryInitialization.userDirectory + "/config/" + fileName + ".ini") File(DirectoryInitialization.userDirectory + "/config/" + fileName + ".ini")
} }

View File

@ -0,0 +1,53 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.fragments
import android.app.Dialog
import android.content.DialogInterface
import android.net.Uri
import android.os.Bundle
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.activityViewModels
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.DialogAddFolderBinding
import org.yuzu.yuzu_emu.model.GameDir
import org.yuzu.yuzu_emu.model.GamesViewModel
class AddGameFolderDialogFragment : DialogFragment() {
private val gamesViewModel: GamesViewModel by activityViewModels()
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val binding = DialogAddFolderBinding.inflate(layoutInflater)
val folderUriString = requireArguments().getString(FOLDER_URI_STRING)
if (folderUriString == null) {
dismiss()
}
binding.path.text = Uri.parse(folderUriString).path
return MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.add_game_folder)
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
val newGameDir = GameDir(folderUriString!!, binding.deepScanSwitch.isChecked)
gamesViewModel.addFolder(newGameDir)
}
.setNegativeButton(android.R.string.cancel, null)
.setView(binding.root)
.show()
}
companion object {
const val TAG = "AddGameFolderDialogFragment"
private const val FOLDER_URI_STRING = "FolderUriString"
fun newInstance(folderUriString: String): AddGameFolderDialogFragment {
val args = Bundle()
args.putString(FOLDER_URI_STRING, folderUriString)
val fragment = AddGameFolderDialogFragment()
fragment.arguments = args
return fragment
}
}
}

View File

@ -414,8 +414,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
perfStatsUpdater = { perfStatsUpdater = {
if (emulationViewModel.emulationStarted.value) { if (emulationViewModel.emulationStarted.value) {
val perfStats = NativeLibrary.getPerfStats() val perfStats = NativeLibrary.getPerfStats()
val cpuBackend = NativeLibrary.getCpuBackend()
if (_binding != null) { if (_binding != null) {
binding.showFpsText.text = String.format("FPS: %.1f", perfStats[FPS]) binding.showFpsText.text =
String.format("FPS: %.1f\n%s", perfStats[FPS], cpuBackend)
} }
perfStatsUpdateHandler.postDelayed(perfStatsUpdater!!, 800) perfStatsUpdateHandler.postDelayed(perfStatsUpdater!!, 800)
} }

View File

@ -0,0 +1,72 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.fragments
import android.app.Dialog
import android.content.DialogInterface
import android.os.Bundle
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.activityViewModels
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.DialogFolderPropertiesBinding
import org.yuzu.yuzu_emu.model.GameDir
import org.yuzu.yuzu_emu.model.GamesViewModel
import org.yuzu.yuzu_emu.utils.SerializableHelper.parcelable
class GameFolderPropertiesDialogFragment : DialogFragment() {
private val gamesViewModel: GamesViewModel by activityViewModels()
private var deepScan = false
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val binding = DialogFolderPropertiesBinding.inflate(layoutInflater)
val gameDir = requireArguments().parcelable<GameDir>(GAME_DIR)!!
// Restore checkbox state
binding.deepScanSwitch.isChecked =
savedInstanceState?.getBoolean(DEEP_SCAN) ?: gameDir.deepScan
// Ensure that we can get the checkbox state even if the view is destroyed
deepScan = binding.deepScanSwitch.isChecked
binding.deepScanSwitch.setOnClickListener {
deepScan = binding.deepScanSwitch.isChecked
}
return MaterialAlertDialogBuilder(requireContext())
.setView(binding.root)
.setTitle(R.string.game_folder_properties)
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
val folderIndex = gamesViewModel.folders.value.indexOf(gameDir)
if (folderIndex != -1) {
gamesViewModel.folders.value[folderIndex].deepScan =
binding.deepScanSwitch.isChecked
gamesViewModel.updateGameDirs()
}
}
.setNegativeButton(android.R.string.cancel, null)
.show()
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putBoolean(DEEP_SCAN, deepScan)
}
companion object {
const val TAG = "GameFolderPropertiesDialogFragment"
private const val GAME_DIR = "GameDir"
private const val DEEP_SCAN = "DeepScan"
fun newInstance(gameDir: GameDir): GameFolderPropertiesDialogFragment {
val args = Bundle()
args.putParcelable(GAME_DIR, gameDir)
val fragment = GameFolderPropertiesDialogFragment()
fragment.arguments = args
return fragment
}
}
}

View File

@ -0,0 +1,128 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.fragments
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.findNavController
import androidx.recyclerview.widget.GridLayoutManager
import com.google.android.material.transition.MaterialSharedAxis
import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.adapters.FolderAdapter
import org.yuzu.yuzu_emu.databinding.FragmentFoldersBinding
import org.yuzu.yuzu_emu.model.GamesViewModel
import org.yuzu.yuzu_emu.model.HomeViewModel
import org.yuzu.yuzu_emu.ui.main.MainActivity
class GameFoldersFragment : Fragment() {
private var _binding: FragmentFoldersBinding? = null
private val binding get() = _binding!!
private val homeViewModel: HomeViewModel by activityViewModels()
private val gamesViewModel: GamesViewModel by activityViewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
gamesViewModel.onOpenGameFoldersFragment()
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentFoldersBinding.inflate(inflater)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
homeViewModel.setNavigationVisibility(visible = false, animated = true)
homeViewModel.setStatusBarShadeVisibility(visible = false)
binding.toolbarFolders.setNavigationOnClickListener {
binding.root.findNavController().popBackStack()
}
binding.listFolders.apply {
layoutManager = GridLayoutManager(
requireContext(),
resources.getInteger(R.integer.grid_columns)
)
adapter = FolderAdapter(requireActivity(), gamesViewModel)
}
viewLifecycleOwner.lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.CREATED) {
gamesViewModel.folders.collect {
(binding.listFolders.adapter as FolderAdapter).submitList(it)
}
}
}
val mainActivity = requireActivity() as MainActivity
binding.buttonAdd.setOnClickListener {
mainActivity.getGamesDirectory.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data)
}
setInsets()
}
override fun onStop() {
super.onStop()
gamesViewModel.onCloseGameFoldersFragment()
}
private fun setInsets() =
ViewCompat.setOnApplyWindowInsetsListener(
binding.root
) { _: View, windowInsets: WindowInsetsCompat ->
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
val leftInsets = barInsets.left + cutoutInsets.left
val rightInsets = barInsets.right + cutoutInsets.right
val mlpToolbar = binding.toolbarFolders.layoutParams as ViewGroup.MarginLayoutParams
mlpToolbar.leftMargin = leftInsets
mlpToolbar.rightMargin = rightInsets
binding.toolbarFolders.layoutParams = mlpToolbar
val fabSpacing = resources.getDimensionPixelSize(R.dimen.spacing_fab)
val mlpFab =
binding.buttonAdd.layoutParams as ViewGroup.MarginLayoutParams
mlpFab.leftMargin = leftInsets + fabSpacing
mlpFab.rightMargin = rightInsets + fabSpacing
mlpFab.bottomMargin = barInsets.bottom + fabSpacing
binding.buttonAdd.layoutParams = mlpFab
val mlpListFolders = binding.listFolders.layoutParams as ViewGroup.MarginLayoutParams
mlpListFolders.leftMargin = leftInsets
mlpListFolders.rightMargin = rightInsets
binding.listFolders.layoutParams = mlpListFolders
binding.listFolders.updatePadding(
bottom = barInsets.bottom +
resources.getDimensionPixelSize(R.dimen.spacing_bottom_list_fab)
)
windowInsets
}
}

View File

@ -127,18 +127,13 @@ class HomeSettingsFragment : Fragment() {
) )
add( add(
HomeSetting( HomeSetting(
R.string.select_games_folder, R.string.manage_game_folders,
R.string.select_games_folder_description, R.string.select_games_folder_description,
R.drawable.ic_add, R.drawable.ic_add,
{ {
mainActivity.getGamesDirectory.launch( binding.root.findNavController()
Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data .navigate(R.id.action_homeSettingsFragment_to_gameFoldersFragment)
) }
},
{ true },
0,
0,
homeViewModel.gamesDir
) )
) )
add( add(

View File

@ -52,7 +52,6 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int -> .setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
settingsViewModel.clickedItem!!.setting.reset() settingsViewModel.clickedItem!!.setting.reset()
settingsViewModel.setAdapterItemChanged(position) settingsViewModel.setAdapterItemChanged(position)
settingsViewModel.shouldSave = true
} }
.setNegativeButton(android.R.string.cancel, null) .setNegativeButton(android.R.string.cancel, null)
.create() .create()
@ -137,24 +136,17 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
is SingleChoiceSetting -> { is SingleChoiceSetting -> {
val scSetting = settingsViewModel.clickedItem as SingleChoiceSetting val scSetting = settingsViewModel.clickedItem as SingleChoiceSetting
val value = getValueForSingleChoiceSelection(scSetting, which) val value = getValueForSingleChoiceSelection(scSetting, which)
if (scSetting.selectedValue != value) {
settingsViewModel.shouldSave = true
}
scSetting.selectedValue = value scSetting.selectedValue = value
} }
is StringSingleChoiceSetting -> { is StringSingleChoiceSetting -> {
val scSetting = settingsViewModel.clickedItem as StringSingleChoiceSetting val scSetting = settingsViewModel.clickedItem as StringSingleChoiceSetting
val value = scSetting.getValueAt(which) val value = scSetting.getValueAt(which)
if (scSetting.selectedValue != value) settingsViewModel.shouldSave = true
scSetting.selectedValue = value scSetting.selectedValue = value
} }
is SliderSetting -> { is SliderSetting -> {
val sliderSetting = settingsViewModel.clickedItem as SliderSetting val sliderSetting = settingsViewModel.clickedItem as SliderSetting
if (sliderSetting.selectedValue != settingsViewModel.sliderProgress.value) {
settingsViewModel.shouldSave = true
}
sliderSetting.selectedValue = settingsViewModel.sliderProgress.value sliderSetting.selectedValue = settingsViewModel.sliderProgress.value
} }
} }

View File

@ -42,7 +42,7 @@ import org.yuzu.yuzu_emu.model.SetupPage
import org.yuzu.yuzu_emu.model.StepState import org.yuzu.yuzu_emu.model.StepState
import org.yuzu.yuzu_emu.ui.main.MainActivity import org.yuzu.yuzu_emu.ui.main.MainActivity
import org.yuzu.yuzu_emu.utils.DirectoryInitialization import org.yuzu.yuzu_emu.utils.DirectoryInitialization
import org.yuzu.yuzu_emu.utils.GameHelper import org.yuzu.yuzu_emu.utils.NativeConfig
import org.yuzu.yuzu_emu.utils.ViewUtils import org.yuzu.yuzu_emu.utils.ViewUtils
class SetupFragment : Fragment() { class SetupFragment : Fragment() {
@ -184,11 +184,7 @@ class SetupFragment : Fragment() {
R.string.add_games_warning_description, R.string.add_games_warning_description,
R.string.add_games_warning_help, R.string.add_games_warning_help,
{ {
val preferences = if (NativeConfig.getGameDirs().isNotEmpty()) {
PreferenceManager.getDefaultSharedPreferences(
YuzuApplication.appContext
)
if (preferences.getString(GameHelper.KEY_GAME_PATH, "")!!.isNotEmpty()) {
StepState.COMPLETE StepState.COMPLETE
} else { } else {
StepState.INCOMPLETE StepState.INCOMPLETE

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.model
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
@Parcelize
data class GameDir(
val uriString: String,
var deepScan: Boolean
) : Parcelable

View File

@ -12,6 +12,7 @@ import java.util.Locale
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.serialization.decodeFromString import kotlinx.serialization.decodeFromString
@ -20,6 +21,7 @@ import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.YuzuApplication import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.utils.GameHelper import org.yuzu.yuzu_emu.utils.GameHelper
import org.yuzu.yuzu_emu.utils.GameMetadata import org.yuzu.yuzu_emu.utils.GameMetadata
import org.yuzu.yuzu_emu.utils.NativeConfig
class GamesViewModel : ViewModel() { class GamesViewModel : ViewModel() {
val games: StateFlow<List<Game>> get() = _games val games: StateFlow<List<Game>> get() = _games
@ -40,6 +42,9 @@ class GamesViewModel : ViewModel() {
val searchFocused: StateFlow<Boolean> get() = _searchFocused val searchFocused: StateFlow<Boolean> get() = _searchFocused
private val _searchFocused = MutableStateFlow(false) private val _searchFocused = MutableStateFlow(false)
private val _folders = MutableStateFlow(mutableListOf<GameDir>())
val folders = _folders.asStateFlow()
init { init {
// Ensure keys are loaded so that ROM metadata can be decrypted. // Ensure keys are loaded so that ROM metadata can be decrypted.
NativeLibrary.reloadKeys() NativeLibrary.reloadKeys()
@ -50,6 +55,7 @@ class GamesViewModel : ViewModel() {
viewModelScope.launch { viewModelScope.launch {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
getGameDirs()
if (storedGames!!.isNotEmpty()) { if (storedGames!!.isNotEmpty()) {
val deserializedGames = mutableSetOf<Game>() val deserializedGames = mutableSetOf<Game>()
storedGames.forEach { storedGames.forEach {
@ -104,7 +110,7 @@ class GamesViewModel : ViewModel() {
_searchFocused.value = searchFocused _searchFocused.value = searchFocused
} }
fun reloadGames(directoryChanged: Boolean) { fun reloadGames(directoriesChanged: Boolean) {
if (isReloading.value) { if (isReloading.value) {
return return
} }
@ -116,10 +122,61 @@ class GamesViewModel : ViewModel() {
setGames(GameHelper.getGames()) setGames(GameHelper.getGames())
_isReloading.value = false _isReloading.value = false
if (directoryChanged) { if (directoriesChanged) {
setShouldSwapData(true) setShouldSwapData(true)
} }
} }
} }
} }
fun addFolder(gameDir: GameDir) =
viewModelScope.launch {
withContext(Dispatchers.IO) {
NativeConfig.addGameDir(gameDir)
getGameDirs()
}
}
fun removeFolder(gameDir: GameDir) =
viewModelScope.launch {
withContext(Dispatchers.IO) {
val gameDirs = _folders.value.toMutableList()
val removedDirIndex = gameDirs.indexOf(gameDir)
if (removedDirIndex != -1) {
gameDirs.removeAt(removedDirIndex)
NativeConfig.setGameDirs(gameDirs.toTypedArray())
getGameDirs()
}
}
}
fun updateGameDirs() =
viewModelScope.launch {
withContext(Dispatchers.IO) {
NativeConfig.setGameDirs(_folders.value.toTypedArray())
getGameDirs()
}
}
fun onOpenGameFoldersFragment() =
viewModelScope.launch {
withContext(Dispatchers.IO) {
getGameDirs()
}
}
fun onCloseGameFoldersFragment() =
viewModelScope.launch {
withContext(Dispatchers.IO) {
getGameDirs(true)
}
}
private fun getGameDirs(reloadList: Boolean = false) {
val gameDirs = NativeConfig.getGameDirs()
_folders.value = gameDirs.toMutableList()
if (reloadList) {
reloadGames(true)
}
}
} }

View File

@ -3,15 +3,9 @@
package org.yuzu.yuzu_emu.model package org.yuzu.yuzu_emu.model
import android.net.Uri
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.preference.PreferenceManager
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.utils.GameHelper
class HomeViewModel : ViewModel() { class HomeViewModel : ViewModel() {
val navigationVisible: StateFlow<Pair<Boolean, Boolean>> get() = _navigationVisible val navigationVisible: StateFlow<Pair<Boolean, Boolean>> get() = _navigationVisible
@ -23,14 +17,6 @@ class HomeViewModel : ViewModel() {
val shouldPageForward: StateFlow<Boolean> get() = _shouldPageForward val shouldPageForward: StateFlow<Boolean> get() = _shouldPageForward
private val _shouldPageForward = MutableStateFlow(false) private val _shouldPageForward = MutableStateFlow(false)
val gamesDir: StateFlow<String> get() = _gamesDir
private val _gamesDir = MutableStateFlow(
Uri.parse(
PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
.getString(GameHelper.KEY_GAME_PATH, "")
).path ?: ""
)
var navigatedToSetup = false var navigatedToSetup = false
fun setNavigationVisibility(visible: Boolean, animated: Boolean) { fun setNavigationVisibility(visible: Boolean, animated: Boolean) {
@ -50,9 +36,4 @@ class HomeViewModel : ViewModel() {
fun setShouldPageForward(pageForward: Boolean) { fun setShouldPageForward(pageForward: Boolean) {
_shouldPageForward.value = pageForward _shouldPageForward.value = pageForward
} }
fun setGamesDir(activity: FragmentActivity, dir: String) {
ViewModelProvider(activity)[GamesViewModel::class.java].reloadGames(true)
_gamesDir.value = dir
}
} }

View File

@ -13,8 +13,6 @@ import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
class SettingsViewModel : ViewModel() { class SettingsViewModel : ViewModel() {
var game: Game? = null var game: Game? = null
var shouldSave = false
var clickedItem: SettingsItem? = null var clickedItem: SettingsItem? = null
val shouldRecreate: StateFlow<Boolean> get() = _shouldRecreate val shouldRecreate: StateFlow<Boolean> get() = _shouldRecreate
@ -73,6 +71,5 @@ class SettingsViewModel : ViewModel() {
fun clear() { fun clear() {
game = null game = null
shouldSave = false
} }
} }

View File

@ -40,6 +40,7 @@ import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.activities.EmulationActivity import org.yuzu.yuzu_emu.activities.EmulationActivity
import org.yuzu.yuzu_emu.databinding.ActivityMainBinding import org.yuzu.yuzu_emu.databinding.ActivityMainBinding
import org.yuzu.yuzu_emu.features.settings.model.Settings import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.fragments.AddGameFolderDialogFragment
import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment
import org.yuzu.yuzu_emu.fragments.MessageDialogFragment import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
import org.yuzu.yuzu_emu.getPublicFilesDir import org.yuzu.yuzu_emu.getPublicFilesDir
@ -252,6 +253,13 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
super.onResume() super.onResume()
} }
override fun onStop() {
super.onStop()
CoroutineScope(Dispatchers.IO).launch {
NativeConfig.saveSettings()
}
}
override fun onDestroy() { override fun onDestroy() {
EmulationActivity.stopForegroundService(this) EmulationActivity.stopForegroundService(this)
super.onDestroy() super.onDestroy()
@ -293,20 +301,19 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
Intent.FLAG_GRANT_READ_URI_PERMISSION Intent.FLAG_GRANT_READ_URI_PERMISSION
) )
// When a new directory is picked, we currently will reset the existing games val uriString = result.toString()
// database. This effectively means that only one game directory is supported. val folder = gamesViewModel.folders.value.firstOrNull { it.uriString == uriString }
PreferenceManager.getDefaultSharedPreferences(applicationContext).edit() if (folder != null) {
.putString(GameHelper.KEY_GAME_PATH, result.toString()) Toast.makeText(
.apply() applicationContext,
R.string.folder_already_added,
Toast.LENGTH_SHORT
).show()
return
}
Toast.makeText( AddGameFolderDialogFragment.newInstance(uriString)
applicationContext, .show(supportFragmentManager, AddGameFolderDialogFragment.TAG)
R.string.games_dir_selected,
Toast.LENGTH_LONG
).show()
gamesViewModel.reloadGames(true)
homeViewModel.setGamesDir(this, result.path!!)
} }
val getProdKey = val getProdKey =
@ -625,6 +632,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
} }
// Clear existing user data // Clear existing user data
NativeConfig.unloadConfig()
File(DirectoryInitialization.userDirectory!!).deleteRecursively() File(DirectoryInitialization.userDirectory!!).deleteRecursively()
// Copy archive to internal storage // Copy archive to internal storage
@ -643,6 +651,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
// Reinitialize relevant data // Reinitialize relevant data
NativeLibrary.initializeSystem(true) NativeLibrary.initializeSystem(true)
NativeConfig.initializeConfig()
gamesViewModel.reloadGames(false) gamesViewModel.reloadGames(false)
return@newInstance getString(R.string.user_data_import_success) return@newInstance getString(R.string.user_data_import_success)

View File

@ -16,6 +16,7 @@ object DirectoryInitialization {
if (!areDirectoriesReady) { if (!areDirectoriesReady) {
initializeInternalStorage() initializeInternalStorage()
NativeLibrary.initializeSystem(false) NativeLibrary.initializeSystem(false)
NativeConfig.initializeConfig()
areDirectoriesReady = true areDirectoriesReady = true
} }
} }

View File

@ -364,6 +364,27 @@ object FileUtil {
.lowercase() .lowercase()
} }
fun isTreeUriValid(uri: Uri): Boolean {
val resolver = context.contentResolver
val columns = arrayOf(
DocumentsContract.Document.COLUMN_DOCUMENT_ID,
DocumentsContract.Document.COLUMN_DISPLAY_NAME,
DocumentsContract.Document.COLUMN_MIME_TYPE
)
return try {
val docId: String = if (isRootTreeUri(uri)) {
DocumentsContract.getTreeDocumentId(uri)
} else {
DocumentsContract.getDocumentId(uri)
}
val childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(uri, docId)
resolver.query(childrenUri, columns, null, null, null)
true
} catch (_: Exception) {
false
}
}
@Throws(IOException::class) @Throws(IOException::class)
fun getStringFromFile(file: File): String = fun getStringFromFile(file: File): String =
String(file.readBytes(), StandardCharsets.UTF_8) String(file.readBytes(), StandardCharsets.UTF_8)

View File

@ -11,10 +11,11 @@ import kotlinx.serialization.json.Json
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.model.Game import org.yuzu.yuzu_emu.model.Game
import org.yuzu.yuzu_emu.model.GameDir
import org.yuzu.yuzu_emu.model.MinimalDocumentFile import org.yuzu.yuzu_emu.model.MinimalDocumentFile
object GameHelper { object GameHelper {
const val KEY_GAME_PATH = "game_path" private const val KEY_OLD_GAME_PATH = "game_path"
const val KEY_GAMES = "Games" const val KEY_GAMES = "Games"
private lateinit var preferences: SharedPreferences private lateinit var preferences: SharedPreferences
@ -22,15 +23,43 @@ object GameHelper {
fun getGames(): List<Game> { fun getGames(): List<Game> {
val games = mutableListOf<Game>() val games = mutableListOf<Game>()
val context = YuzuApplication.appContext val context = YuzuApplication.appContext
val gamesDir =
PreferenceManager.getDefaultSharedPreferences(context).getString(KEY_GAME_PATH, "")
val gamesUri = Uri.parse(gamesDir)
preferences = PreferenceManager.getDefaultSharedPreferences(context) preferences = PreferenceManager.getDefaultSharedPreferences(context)
val gameDirs = mutableListOf<GameDir>()
val oldGamesDir = preferences.getString(KEY_OLD_GAME_PATH, "") ?: ""
if (oldGamesDir.isNotEmpty()) {
gameDirs.add(GameDir(oldGamesDir, true))
preferences.edit().remove(KEY_OLD_GAME_PATH).apply()
}
gameDirs.addAll(NativeConfig.getGameDirs())
// Ensure keys are loaded so that ROM metadata can be decrypted. // Ensure keys are loaded so that ROM metadata can be decrypted.
NativeLibrary.reloadKeys() NativeLibrary.reloadKeys()
addGamesRecursive(games, FileUtil.listFiles(gamesUri), 3) val badDirs = mutableListOf<Int>()
gameDirs.forEachIndexed { index: Int, gameDir: GameDir ->
val gameDirUri = Uri.parse(gameDir.uriString)
val isValid = FileUtil.isTreeUriValid(gameDirUri)
if (isValid) {
addGamesRecursive(
games,
FileUtil.listFiles(gameDirUri),
if (gameDir.deepScan) 3 else 1
)
} else {
badDirs.add(index)
}
}
// Remove all game dirs with insufficient permissions from config
if (badDirs.isNotEmpty()) {
var offset = 0
badDirs.forEach {
gameDirs.removeAt(it - offset)
offset++
}
NativeConfig.setGameDirs(gameDirs.toTypedArray())
}
// Cache list of games found on disk // Cache list of games found on disk
val serializedGames = mutableSetOf<String>() val serializedGames = mutableSetOf<String>()

View File

@ -27,6 +27,8 @@ object InputHandler {
0x054C -> getInputDS5ButtonKey(event.keyCode) 0x054C -> getInputDS5ButtonKey(event.keyCode)
0x057E -> getInputJoyconButtonKey(event.keyCode) 0x057E -> getInputJoyconButtonKey(event.keyCode)
0x1532 -> getInputRazerButtonKey(event.keyCode) 0x1532 -> getInputRazerButtonKey(event.keyCode)
0x3537 -> getInputRedmagicButtonKey(event.keyCode)
0x358A -> getInputBackboneLabsButtonKey(event.keyCode)
else -> getInputGenericButtonKey(event.keyCode) else -> getInputGenericButtonKey(event.keyCode)
} }
@ -227,6 +229,42 @@ object InputHandler {
} }
} }
private fun getInputRedmagicButtonKey(key: Int): Int {
return when (key) {
KeyEvent.KEYCODE_BUTTON_A -> NativeLibrary.ButtonType.BUTTON_B
KeyEvent.KEYCODE_BUTTON_B -> NativeLibrary.ButtonType.BUTTON_A
KeyEvent.KEYCODE_BUTTON_X -> NativeLibrary.ButtonType.BUTTON_Y
KeyEvent.KEYCODE_BUTTON_Y -> NativeLibrary.ButtonType.BUTTON_X
KeyEvent.KEYCODE_BUTTON_L1 -> NativeLibrary.ButtonType.TRIGGER_L
KeyEvent.KEYCODE_BUTTON_R1 -> NativeLibrary.ButtonType.TRIGGER_R
KeyEvent.KEYCODE_BUTTON_L2 -> NativeLibrary.ButtonType.TRIGGER_ZL
KeyEvent.KEYCODE_BUTTON_R2 -> NativeLibrary.ButtonType.TRIGGER_ZR
KeyEvent.KEYCODE_BUTTON_THUMBL -> NativeLibrary.ButtonType.STICK_L
KeyEvent.KEYCODE_BUTTON_THUMBR -> NativeLibrary.ButtonType.STICK_R
KeyEvent.KEYCODE_BUTTON_START -> NativeLibrary.ButtonType.BUTTON_PLUS
KeyEvent.KEYCODE_BUTTON_SELECT -> NativeLibrary.ButtonType.BUTTON_MINUS
else -> -1
}
}
private fun getInputBackboneLabsButtonKey(key: Int): Int {
return when (key) {
KeyEvent.KEYCODE_BUTTON_A -> NativeLibrary.ButtonType.BUTTON_B
KeyEvent.KEYCODE_BUTTON_B -> NativeLibrary.ButtonType.BUTTON_A
KeyEvent.KEYCODE_BUTTON_X -> NativeLibrary.ButtonType.BUTTON_Y
KeyEvent.KEYCODE_BUTTON_Y -> NativeLibrary.ButtonType.BUTTON_X
KeyEvent.KEYCODE_BUTTON_L1 -> NativeLibrary.ButtonType.TRIGGER_L
KeyEvent.KEYCODE_BUTTON_R1 -> NativeLibrary.ButtonType.TRIGGER_R
KeyEvent.KEYCODE_BUTTON_L2 -> NativeLibrary.ButtonType.TRIGGER_ZL
KeyEvent.KEYCODE_BUTTON_R2 -> NativeLibrary.ButtonType.TRIGGER_ZR
KeyEvent.KEYCODE_BUTTON_THUMBL -> NativeLibrary.ButtonType.STICK_L
KeyEvent.KEYCODE_BUTTON_THUMBR -> NativeLibrary.ButtonType.STICK_R
KeyEvent.KEYCODE_BUTTON_START -> NativeLibrary.ButtonType.BUTTON_PLUS
KeyEvent.KEYCODE_BUTTON_SELECT -> NativeLibrary.ButtonType.BUTTON_MINUS
else -> -1
}
}
private fun getInputGenericButtonKey(key: Int): Int { private fun getInputGenericButtonKey(key: Int): Int {
return when (key) { return when (key) {
KeyEvent.KEYCODE_BUTTON_A -> NativeLibrary.ButtonType.BUTTON_A KeyEvent.KEYCODE_BUTTON_A -> NativeLibrary.ButtonType.BUTTON_A

View File

@ -3,7 +3,33 @@
package org.yuzu.yuzu_emu.utils package org.yuzu.yuzu_emu.utils
import org.yuzu.yuzu_emu.model.GameDir
object NativeConfig { object NativeConfig {
/**
* Creates a Config object and opens the emulation config.
*/
@Synchronized
external fun initializeConfig()
/**
* Destroys the stored config object. This automatically saves the existing config.
*/
@Synchronized
external fun unloadConfig()
/**
* Reads values saved to the config file and saves them.
*/
@Synchronized
external fun reloadSettings()
/**
* Saves settings values in memory to disk.
*/
@Synchronized
external fun saveSettings()
external fun getBoolean(key: String, getDefault: Boolean): Boolean external fun getBoolean(key: String, getDefault: Boolean): Boolean
external fun setBoolean(key: String, value: Boolean) external fun setBoolean(key: String, value: Boolean)
@ -30,4 +56,22 @@ object NativeConfig {
external fun getConfigHeader(category: Int): String external fun getConfigHeader(category: Int): String
external fun getPairedSettingKey(key: String): String external fun getPairedSettingKey(key: String): String
/**
* Gets every [GameDir] in AndroidSettings::values.game_dirs
*/
@Synchronized
external fun getGameDirs(): Array<GameDir>
/**
* Clears the AndroidSettings::values.game_dirs array and replaces them with the provided array
*/
@Synchronized
external fun setGameDirs(dirs: Array<GameDir>)
/**
* Adds a single [GameDir] to the AndroidSettings::values.game_dirs array
*/
@Synchronized
external fun addGameDir(dir: GameDir)
} }

View File

@ -6,9 +6,6 @@ add_library(yuzu-android SHARED
android_common/android_common.h android_common/android_common.h
applets/software_keyboard.cpp applets/software_keyboard.cpp
applets/software_keyboard.h applets/software_keyboard.h
config.cpp
config.h
default_ini.h
emu_window/emu_window.cpp emu_window/emu_window.cpp
emu_window/emu_window.h emu_window/emu_window.h
id_cache.cpp id_cache.cpp
@ -16,15 +13,17 @@ add_library(yuzu-android SHARED
native.cpp native.cpp
native.h native.h
native_config.cpp native_config.cpp
uisettings.cpp android_settings.cpp
game_metadata.cpp game_metadata.cpp
native_log.cpp native_log.cpp
android_config.cpp
android_config.h
) )
set_property(TARGET yuzu-android PROPERTY IMPORTED_LOCATION ${FFmpeg_LIBRARY_DIR}) set_property(TARGET yuzu-android PROPERTY IMPORTED_LOCATION ${FFmpeg_LIBRARY_DIR})
target_link_libraries(yuzu-android PRIVATE audio_core common core input_common) target_link_libraries(yuzu-android PRIVATE audio_core common core input_common frontend_common)
target_link_libraries(yuzu-android PRIVATE android camera2ndk EGL glad inih jnigraphics log) target_link_libraries(yuzu-android PRIVATE android camera2ndk EGL glad jnigraphics log)
if (ARCHITECTURE_arm64) if (ARCHITECTURE_arm64)
target_link_libraries(yuzu-android PRIVATE adrenotools) target_link_libraries(yuzu-android PRIVATE adrenotools)
endif() endif()

View File

@ -0,0 +1,120 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "android_config.h"
#include "android_settings.h"
#include "common/settings_setting.h"
AndroidConfig::AndroidConfig(const std::string& config_name, ConfigType config_type)
: Config(config_type) {
Initialize(config_name);
if (config_type != ConfigType::InputProfile) {
ReadAndroidValues();
SaveAndroidValues();
}
}
AndroidConfig::~AndroidConfig() {
if (global) {
AndroidConfig::SaveAllValues();
}
}
void AndroidConfig::ReloadAllValues() {
Reload();
ReadAndroidValues();
SaveAndroidValues();
}
void AndroidConfig::SaveAllValues() {
Save();
SaveAndroidValues();
}
void AndroidConfig::ReadAndroidValues() {
if (global) {
ReadAndroidUIValues();
ReadUIValues();
}
}
void AndroidConfig::ReadAndroidUIValues() {
BeginGroup(Settings::TranslateCategory(Settings::Category::Android));
ReadCategory(Settings::Category::Android);
EndGroup();
}
void AndroidConfig::ReadUIValues() {
BeginGroup(Settings::TranslateCategory(Settings::Category::Ui));
ReadPathValues();
EndGroup();
}
void AndroidConfig::ReadPathValues() {
BeginGroup(Settings::TranslateCategory(Settings::Category::Paths));
const int gamedirs_size = BeginArray(std::string("gamedirs"));
for (int i = 0; i < gamedirs_size; ++i) {
SetArrayIndex(i);
AndroidSettings::GameDir game_dir;
game_dir.path = ReadStringSetting(std::string("path"));
game_dir.deep_scan =
ReadBooleanSetting(std::string("deep_scan"), std::make_optional(false));
AndroidSettings::values.game_dirs.push_back(game_dir);
}
EndArray();
EndGroup();
}
void AndroidConfig::SaveAndroidValues() {
if (global) {
SaveAndroidUIValues();
SaveUIValues();
}
WriteToIni();
}
void AndroidConfig::SaveAndroidUIValues() {
BeginGroup(Settings::TranslateCategory(Settings::Category::Android));
WriteCategory(Settings::Category::Android);
EndGroup();
}
void AndroidConfig::SaveUIValues() {
BeginGroup(Settings::TranslateCategory(Settings::Category::Ui));
SavePathValues();
EndGroup();
}
void AndroidConfig::SavePathValues() {
BeginGroup(Settings::TranslateCategory(Settings::Category::Paths));
BeginArray(std::string("gamedirs"));
for (size_t i = 0; i < AndroidSettings::values.game_dirs.size(); ++i) {
SetArrayIndex(i);
const auto& game_dir = AndroidSettings::values.game_dirs[i];
WriteSetting(std::string("path"), game_dir.path);
WriteSetting(std::string("deep_scan"), game_dir.deep_scan, std::make_optional(false));
}
EndArray();
EndGroup();
}
std::vector<Settings::BasicSetting*>& AndroidConfig::FindRelevantList(Settings::Category category) {
auto& map = Settings::values.linkage.by_category;
if (map.contains(category)) {
return Settings::values.linkage.by_category[category];
}
return AndroidSettings::values.linkage.by_category[category];
}

View File

@ -0,0 +1,41 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "frontend_common/config.h"
class AndroidConfig final : public Config {
public:
explicit AndroidConfig(const std::string& config_name = "config",
ConfigType config_type = ConfigType::GlobalConfig);
~AndroidConfig() override;
void ReloadAllValues() override;
void SaveAllValues() override;
protected:
void ReadAndroidValues();
void ReadAndroidUIValues();
void ReadHidbusValues() override {}
void ReadDebugControlValues() override {}
void ReadPathValues() override;
void ReadShortcutValues() override {}
void ReadUIValues() override;
void ReadUIGamelistValues() override {}
void ReadUILayoutValues() override {}
void ReadMultiplayerValues() override {}
void SaveAndroidValues();
void SaveAndroidUIValues();
void SaveHidbusValues() override {}
void SaveDebugControlValues() override {}
void SavePathValues() override;
void SaveShortcutValues() override {}
void SaveUIValues() override;
void SaveUIGamelistValues() override {}
void SaveUILayoutValues() override {}
void SaveMultiplayerValues() override {}
std::vector<Settings::BasicSetting*>& FindRelevantList(Settings::Category category) override;
};

View File

@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project // SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include "uisettings.h" #include "android_settings.h"
namespace AndroidSettings { namespace AndroidSettings {

View File

@ -9,9 +9,17 @@
namespace AndroidSettings { namespace AndroidSettings {
struct GameDir {
std::string path;
bool deep_scan = false;
};
struct Values { struct Values {
Settings::Linkage linkage; Settings::Linkage linkage;
// Path settings
std::vector<GameDir> game_dirs;
// Android // Android
Settings::Setting<bool> picture_in_picture{linkage, false, "picture_in_picture", Settings::Setting<bool> picture_in_picture{linkage, false, "picture_in_picture",
Settings::Category::Android}; Settings::Category::Android};

View File

@ -1,330 +0,0 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <memory>
#include <optional>
#include <sstream>
#include <INIReader.h>
#include "common/fs/file.h"
#include "common/fs/fs.h"
#include "common/fs/path_util.h"
#include "common/logging/log.h"
#include "common/settings.h"
#include "common/settings_enums.h"
#include "core/hle/service/acc/profile_manager.h"
#include "input_common/main.h"
#include "jni/config.h"
#include "jni/default_ini.h"
#include "uisettings.h"
namespace FS = Common::FS;
Config::Config(const std::string& config_name, ConfigType config_type)
: type(config_type), global{config_type == ConfigType::GlobalConfig} {
Initialize(config_name);
}
Config::~Config() = default;
bool Config::LoadINI(const std::string& default_contents, bool retry) {
void(FS::CreateParentDir(config_loc));
config = std::make_unique<INIReader>(FS::PathToUTF8String(config_loc));
const auto config_loc_str = FS::PathToUTF8String(config_loc);
if (config->ParseError() < 0) {
if (retry) {
LOG_WARNING(Config, "Failed to load {}. Creating file from defaults...",
config_loc_str);
void(FS::CreateParentDir(config_loc));
void(FS::WriteStringToFile(config_loc, FS::FileType::TextFile, default_contents));
config = std::make_unique<INIReader>(config_loc_str);
return LoadINI(default_contents, false);
}
LOG_ERROR(Config, "Failed.");
return false;
}
LOG_INFO(Config, "Successfully loaded {}", config_loc_str);
return true;
}
template <>
void Config::ReadSetting(const std::string& group, Settings::Setting<std::string>& setting) {
std::string setting_value = config->Get(group, setting.GetLabel(), setting.GetDefault());
if (setting_value.empty()) {
setting_value = setting.GetDefault();
}
setting = std::move(setting_value);
}
template <>
void Config::ReadSetting(const std::string& group, Settings::Setting<bool>& setting) {
setting = config->GetBoolean(group, setting.GetLabel(), setting.GetDefault());
}
template <typename Type, bool ranged>
void Config::ReadSetting(const std::string& group, Settings::Setting<Type, ranged>& setting) {
setting = static_cast<Type>(
config->GetInteger(group, setting.GetLabel(), static_cast<long>(setting.GetDefault())));
}
void Config::ReadValues() {
ReadSetting("ControlsGeneral", Settings::values.mouse_enabled);
ReadSetting("ControlsGeneral", Settings::values.touch_device);
ReadSetting("ControlsGeneral", Settings::values.keyboard_enabled);
ReadSetting("ControlsGeneral", Settings::values.debug_pad_enabled);
ReadSetting("ControlsGeneral", Settings::values.vibration_enabled);
ReadSetting("ControlsGeneral", Settings::values.enable_accurate_vibrations);
ReadSetting("ControlsGeneral", Settings::values.motion_enabled);
Settings::values.touchscreen.enabled =
config->GetBoolean("ControlsGeneral", "touch_enabled", true);
Settings::values.touchscreen.rotation_angle =
config->GetInteger("ControlsGeneral", "touch_angle", 0);
Settings::values.touchscreen.diameter_x =
config->GetInteger("ControlsGeneral", "touch_diameter_x", 15);
Settings::values.touchscreen.diameter_y =
config->GetInteger("ControlsGeneral", "touch_diameter_y", 15);
int num_touch_from_button_maps =
config->GetInteger("ControlsGeneral", "touch_from_button_map", 0);
if (num_touch_from_button_maps > 0) {
for (int i = 0; i < num_touch_from_button_maps; ++i) {
Settings::TouchFromButtonMap map;
map.name = config->Get("ControlsGeneral",
std::string("touch_from_button_maps_") + std::to_string(i) +
std::string("_name"),
"default");
const int num_touch_maps = config->GetInteger(
"ControlsGeneral",
std::string("touch_from_button_maps_") + std::to_string(i) + std::string("_count"),
0);
map.buttons.reserve(num_touch_maps);
for (int j = 0; j < num_touch_maps; ++j) {
std::string touch_mapping =
config->Get("ControlsGeneral",
std::string("touch_from_button_maps_") + std::to_string(i) +
std::string("_bind_") + std::to_string(j),
"");
map.buttons.emplace_back(std::move(touch_mapping));
}
Settings::values.touch_from_button_maps.emplace_back(std::move(map));
}
} else {
Settings::values.touch_from_button_maps.emplace_back(
Settings::TouchFromButtonMap{"default", {}});
num_touch_from_button_maps = 1;
}
Settings::values.touch_from_button_map_index = std::clamp(
Settings::values.touch_from_button_map_index.GetValue(), 0, num_touch_from_button_maps - 1);
ReadSetting("ControlsGeneral", Settings::values.udp_input_servers);
// Data Storage
ReadSetting("Data Storage", Settings::values.use_virtual_sd);
FS::SetYuzuPath(FS::YuzuPath::NANDDir,
config->Get("Data Storage", "nand_directory",
FS::GetYuzuPathString(FS::YuzuPath::NANDDir)));
FS::SetYuzuPath(FS::YuzuPath::SDMCDir,
config->Get("Data Storage", "sdmc_directory",
FS::GetYuzuPathString(FS::YuzuPath::SDMCDir)));
FS::SetYuzuPath(FS::YuzuPath::LoadDir,
config->Get("Data Storage", "load_directory",
FS::GetYuzuPathString(FS::YuzuPath::LoadDir)));
FS::SetYuzuPath(FS::YuzuPath::DumpDir,
config->Get("Data Storage", "dump_directory",
FS::GetYuzuPathString(FS::YuzuPath::DumpDir)));
ReadSetting("Data Storage", Settings::values.gamecard_inserted);
ReadSetting("Data Storage", Settings::values.gamecard_current_game);
ReadSetting("Data Storage", Settings::values.gamecard_path);
// System
ReadSetting("System", Settings::values.current_user);
Settings::values.current_user = std::clamp<int>(Settings::values.current_user.GetValue(), 0,
Service::Account::MAX_USERS - 1);
// Disable docked mode by default on Android
Settings::values.use_docked_mode.SetValue(config->GetBoolean("System", "use_docked_mode", false)
? Settings::ConsoleMode::Docked
: Settings::ConsoleMode::Handheld);
const auto rng_seed_enabled = config->GetBoolean("System", "rng_seed_enabled", false);
if (rng_seed_enabled) {
Settings::values.rng_seed.SetValue(config->GetInteger("System", "rng_seed", 0));
} else {
Settings::values.rng_seed.SetValue(0);
}
Settings::values.rng_seed_enabled.SetValue(rng_seed_enabled);
const auto custom_rtc_enabled = config->GetBoolean("System", "custom_rtc_enabled", false);
if (custom_rtc_enabled) {
Settings::values.custom_rtc = config->GetInteger("System", "custom_rtc", 0);
} else {
Settings::values.custom_rtc = 0;
}
Settings::values.custom_rtc_enabled = custom_rtc_enabled;
ReadSetting("System", Settings::values.language_index);
ReadSetting("System", Settings::values.region_index);
ReadSetting("System", Settings::values.time_zone_index);
ReadSetting("System", Settings::values.sound_index);
// Core
ReadSetting("Core", Settings::values.use_multi_core);
ReadSetting("Core", Settings::values.memory_layout_mode);
// Cpu
ReadSetting("Cpu", Settings::values.cpu_accuracy);
ReadSetting("Cpu", Settings::values.cpu_debug_mode);
ReadSetting("Cpu", Settings::values.cpuopt_page_tables);
ReadSetting("Cpu", Settings::values.cpuopt_block_linking);
ReadSetting("Cpu", Settings::values.cpuopt_return_stack_buffer);
ReadSetting("Cpu", Settings::values.cpuopt_fast_dispatcher);
ReadSetting("Cpu", Settings::values.cpuopt_context_elimination);
ReadSetting("Cpu", Settings::values.cpuopt_const_prop);
ReadSetting("Cpu", Settings::values.cpuopt_misc_ir);
ReadSetting("Cpu", Settings::values.cpuopt_reduce_misalign_checks);
ReadSetting("Cpu", Settings::values.cpuopt_fastmem);
ReadSetting("Cpu", Settings::values.cpuopt_fastmem_exclusives);
ReadSetting("Cpu", Settings::values.cpuopt_recompile_exclusives);
ReadSetting("Cpu", Settings::values.cpuopt_ignore_memory_aborts);
ReadSetting("Cpu", Settings::values.cpuopt_unsafe_unfuse_fma);
ReadSetting("Cpu", Settings::values.cpuopt_unsafe_reduce_fp_error);
ReadSetting("Cpu", Settings::values.cpuopt_unsafe_ignore_standard_fpcr);
ReadSetting("Cpu", Settings::values.cpuopt_unsafe_inaccurate_nan);
ReadSetting("Cpu", Settings::values.cpuopt_unsafe_fastmem_check);
ReadSetting("Cpu", Settings::values.cpuopt_unsafe_ignore_global_monitor);
// Renderer
ReadSetting("Renderer", Settings::values.renderer_backend);
ReadSetting("Renderer", Settings::values.renderer_debug);
ReadSetting("Renderer", Settings::values.renderer_shader_feedback);
ReadSetting("Renderer", Settings::values.enable_nsight_aftermath);
ReadSetting("Renderer", Settings::values.disable_shader_loop_safety_checks);
ReadSetting("Renderer", Settings::values.vulkan_device);
ReadSetting("Renderer", Settings::values.resolution_setup);
ReadSetting("Renderer", Settings::values.scaling_filter);
ReadSetting("Renderer", Settings::values.fsr_sharpening_slider);
ReadSetting("Renderer", Settings::values.anti_aliasing);
ReadSetting("Renderer", Settings::values.fullscreen_mode);
ReadSetting("Renderer", Settings::values.aspect_ratio);
ReadSetting("Renderer", Settings::values.max_anisotropy);
ReadSetting("Renderer", Settings::values.use_speed_limit);
ReadSetting("Renderer", Settings::values.speed_limit);
ReadSetting("Renderer", Settings::values.use_disk_shader_cache);
ReadSetting("Renderer", Settings::values.use_asynchronous_gpu_emulation);
ReadSetting("Renderer", Settings::values.vsync_mode);
ReadSetting("Renderer", Settings::values.shader_backend);
ReadSetting("Renderer", Settings::values.use_asynchronous_shaders);
ReadSetting("Renderer", Settings::values.nvdec_emulation);
ReadSetting("Renderer", Settings::values.use_fast_gpu_time);
ReadSetting("Renderer", Settings::values.use_vulkan_driver_pipeline_cache);
ReadSetting("Renderer", Settings::values.bg_red);
ReadSetting("Renderer", Settings::values.bg_green);
ReadSetting("Renderer", Settings::values.bg_blue);
// Use GPU accuracy normal by default on Android
Settings::values.gpu_accuracy = static_cast<Settings::GpuAccuracy>(config->GetInteger(
"Renderer", "gpu_accuracy", static_cast<u32>(Settings::GpuAccuracy::Normal)));
// Use GPU default anisotropic filtering on Android
Settings::values.max_anisotropy =
static_cast<Settings::AnisotropyMode>(config->GetInteger("Renderer", "max_anisotropy", 1));
// Disable ASTC compute by default on Android
Settings::values.accelerate_astc.SetValue(
config->GetBoolean("Renderer", "accelerate_astc", false) ? Settings::AstcDecodeMode::Gpu
: Settings::AstcDecodeMode::Cpu);
// Enable asynchronous presentation by default on Android
Settings::values.async_presentation =
config->GetBoolean("Renderer", "async_presentation", true);
// Disable force_max_clock by default on Android
Settings::values.renderer_force_max_clock =
config->GetBoolean("Renderer", "force_max_clock", false);
// Disable use_reactive_flushing by default on Android
Settings::values.use_reactive_flushing =
config->GetBoolean("Renderer", "use_reactive_flushing", false);
// Audio
ReadSetting("Audio", Settings::values.sink_id);
ReadSetting("Audio", Settings::values.audio_output_device_id);
ReadSetting("Audio", Settings::values.volume);
// Miscellaneous
// log_filter has a different default here than from common
Settings::values.log_filter = "*:Info";
ReadSetting("Miscellaneous", Settings::values.use_dev_keys);
// Debugging
Settings::values.record_frame_times =
config->GetBoolean("Debugging", "record_frame_times", false);
ReadSetting("Debugging", Settings::values.dump_exefs);
ReadSetting("Debugging", Settings::values.dump_nso);
ReadSetting("Debugging", Settings::values.enable_fs_access_log);
ReadSetting("Debugging", Settings::values.reporting_services);
ReadSetting("Debugging", Settings::values.quest_flag);
ReadSetting("Debugging", Settings::values.use_debug_asserts);
ReadSetting("Debugging", Settings::values.use_auto_stub);
ReadSetting("Debugging", Settings::values.disable_macro_jit);
ReadSetting("Debugging", Settings::values.disable_macro_hle);
ReadSetting("Debugging", Settings::values.use_gdbstub);
ReadSetting("Debugging", Settings::values.gdbstub_port);
const auto title_list = config->Get("AddOns", "title_ids", "");
std::stringstream ss(title_list);
std::string line;
while (std::getline(ss, line, '|')) {
const auto title_id = std::strtoul(line.c_str(), nullptr, 16);
const auto disabled_list = config->Get("AddOns", "disabled_" + line, "");
std::stringstream inner_ss(disabled_list);
std::string inner_line;
std::vector<std::string> out;
while (std::getline(inner_ss, inner_line, '|')) {
out.push_back(inner_line);
}
Settings::values.disabled_addons.insert_or_assign(title_id, out);
}
// Web Service
ReadSetting("WebService", Settings::values.enable_telemetry);
ReadSetting("WebService", Settings::values.web_api_url);
ReadSetting("WebService", Settings::values.yuzu_username);
ReadSetting("WebService", Settings::values.yuzu_token);
// Network
ReadSetting("Network", Settings::values.network_interface);
// Android
ReadSetting("Android", AndroidSettings::values.picture_in_picture);
ReadSetting("Android", AndroidSettings::values.screen_layout);
}
void Config::Initialize(const std::string& config_name) {
const auto fs_config_loc = FS::GetYuzuPath(FS::YuzuPath::ConfigDir);
const auto config_file = fmt::format("{}.ini", config_name);
switch (type) {
case ConfigType::GlobalConfig:
config_loc = FS::PathToUTF8String(fs_config_loc / config_file);
break;
case ConfigType::PerGameConfig:
config_loc = FS::PathToUTF8String(fs_config_loc / "custom" / FS::ToU8String(config_file));
break;
case ConfigType::InputProfile:
config_loc = FS::PathToUTF8String(fs_config_loc / "input" / config_file);
LoadINI(DefaultINI::android_config_file);
return;
}
LoadINI(DefaultINI::android_config_file);
ReadValues();
}

View File

@ -1,47 +0,0 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <filesystem>
#include <memory>
#include <optional>
#include <string>
#include "common/settings.h"
class INIReader;
class Config {
bool LoadINI(const std::string& default_contents = "", bool retry = true);
public:
enum class ConfigType {
GlobalConfig,
PerGameConfig,
InputProfile,
};
explicit Config(const std::string& config_name = "config",
ConfigType config_type = ConfigType::GlobalConfig);
~Config();
void Initialize(const std::string& config_name);
private:
/**
* Applies a value read from the config to a Setting.
*
* @param group The name of the INI group
* @param setting The yuzu setting to modify
*/
template <typename Type, bool ranged>
void ReadSetting(const std::string& group, Settings::Setting<Type, ranged>& setting);
void ReadValues();
const ConfigType type;
std::unique_ptr<INIReader> config;
std::string config_loc;
const bool global;
};

View File

@ -1,511 +0,0 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
namespace DefaultINI {
const char* android_config_file = R"(
[ControlsP0]
# The input devices and parameters for each Switch native input
# The config section determines the player number where the config will be applied on. For example "ControlsP0", "ControlsP1", ...
# It should be in the format of "engine:[engine_name],[param1]:[value1],[param2]:[value2]..."
# Escape characters $0 (for ':'), $1 (for ',') and $2 (for '$') can be used in values
# Indicates if this player should be connected at boot
connected=
# for button input, the following devices are available:
# - "keyboard" (default) for keyboard input. Required parameters:
# - "code": the code of the key to bind
# - "sdl" for joystick input using SDL. Required parameters:
# - "guid": SDL identification GUID of the joystick
# - "port": the index of the joystick to bind
# - "button"(optional): the index of the button to bind
# - "hat"(optional): the index of the hat to bind as direction buttons
# - "axis"(optional): the index of the axis to bind
# - "direction"(only used for hat): the direction name of the hat to bind. Can be "up", "down", "left" or "right"
# - "threshold"(only used for axis): a float value in (-1.0, 1.0) which the button is
# triggered if the axis value crosses
# - "direction"(only used for axis): "+" means the button is triggered when the axis value
# is greater than the threshold; "-" means the button is triggered when the axis value
# is smaller than the threshold
button_a=
button_b=
button_x=
button_y=
button_lstick=
button_rstick=
button_l=
button_r=
button_zl=
button_zr=
button_plus=
button_minus=
button_dleft=
button_dup=
button_dright=
button_ddown=
button_lstick_left=
button_lstick_up=
button_lstick_right=
button_lstick_down=
button_sl=
button_sr=
button_home=
button_screenshot=
# for analog input, the following devices are available:
# - "analog_from_button" (default) for emulating analog input from direction buttons. Required parameters:
# - "up", "down", "left", "right": sub-devices for each direction.
# Should be in the format as a button input devices using escape characters, for example, "engine$0keyboard$1code$00"
# - "modifier": sub-devices as a modifier.
# - "modifier_scale": a float number representing the applied modifier scale to the analog input.
# Must be in range of 0.0-1.0. Defaults to 0.5
# - "sdl" for joystick input using SDL. Required parameters:
# - "guid": SDL identification GUID of the joystick
# - "port": the index of the joystick to bind
# - "axis_x": the index of the axis to bind as x-axis (default to 0)
# - "axis_y": the index of the axis to bind as y-axis (default to 1)
lstick=
rstick=
# for motion input, the following devices are available:
# - "keyboard" (default) for emulating random motion input from buttons. Required parameters:
# - "code": the code of the key to bind
# - "sdl" for motion input using SDL. Required parameters:
# - "guid": SDL identification GUID of the joystick
# - "port": the index of the joystick to bind
# - "motion": the index of the motion sensor to bind
# - "cemuhookudp" for motion input using Cemu Hook protocol. Required parameters:
# - "guid": the IP address of the cemu hook server encoded to a hex string. for example 192.168.0.1 = "c0a80001"
# - "port": the port of the cemu hook server
# - "pad": the index of the joystick
# - "motion": the index of the motion sensor of the joystick to bind
motionleft=
motionright=
[ControlsGeneral]
# To use the debug_pad, prepend `debug_pad_` before each button setting above.
# i.e. debug_pad_button_a=
# Enable debug pad inputs to the guest
# 0 (default): Disabled, 1: Enabled
debug_pad_enabled =
# Whether to enable or disable vibration
# 0: Disabled, 1 (default): Enabled
vibration_enabled=
# Whether to enable or disable accurate vibrations
# 0 (default): Disabled, 1: Enabled
enable_accurate_vibrations=
# Enables controller motion inputs
# 0: Disabled, 1 (default): Enabled
motion_enabled =
# Defines the udp device's touch screen coordinate system for cemuhookudp devices
# - "min_x", "min_y", "max_x", "max_y"
touch_device=
# for mapping buttons to touch inputs.
#touch_from_button_map=1
#touch_from_button_maps_0_name=default
#touch_from_button_maps_0_count=2
#touch_from_button_maps_0_bind_0=foo
#touch_from_button_maps_0_bind_1=bar
# etc.
# List of Cemuhook UDP servers, delimited by ','.
# Default: 127.0.0.1:26760
# Example: 127.0.0.1:26760,123.4.5.67:26761
udp_input_servers =
# Enable controlling an axis via a mouse input.
# 0 (default): Off, 1: On
mouse_panning =
# Set mouse sensitivity.
# Default: 1.0
mouse_panning_sensitivity =
# Emulate an analog control stick from keyboard inputs.
# 0 (default): Disabled, 1: Enabled
emulate_analog_keyboard =
# Enable mouse inputs to the guest
# 0 (default): Disabled, 1: Enabled
mouse_enabled =
# Enable keyboard inputs to the guest
# 0 (default): Disabled, 1: Enabled
keyboard_enabled =
[Core]
# Whether to use multi-core for CPU emulation
# 0: Disabled, 1 (default): Enabled
use_multi_core =
# Enable unsafe extended guest system memory layout (8GB DRAM)
# 0 (default): Disabled, 1: Enabled
use_unsafe_extended_memory_layout =
[Cpu]
# Adjusts various optimizations.
# Auto-select mode enables choice unsafe optimizations.
# Accurate enables only safe optimizations.
# Unsafe allows any unsafe optimizations.
# 0 (default): Auto-select, 1: Accurate, 2: Enable unsafe optimizations
cpu_accuracy =
# Allow disabling safe optimizations.
# 0 (default): Disabled, 1: Enabled
cpu_debug_mode =
# Enable inline page tables optimization (faster guest memory access)
# 0: Disabled, 1 (default): Enabled
cpuopt_page_tables =
# Enable block linking CPU optimization (reduce block dispatcher use during predictable jumps)
# 0: Disabled, 1 (default): Enabled
cpuopt_block_linking =
# Enable return stack buffer CPU optimization (reduce block dispatcher use during predictable returns)
# 0: Disabled, 1 (default): Enabled
cpuopt_return_stack_buffer =
# Enable fast dispatcher CPU optimization (use a two-tiered dispatcher architecture)
# 0: Disabled, 1 (default): Enabled
cpuopt_fast_dispatcher =
# Enable context elimination CPU Optimization (reduce host memory use for guest context)
# 0: Disabled, 1 (default): Enabled
cpuopt_context_elimination =
# Enable constant propagation CPU optimization (basic IR optimization)
# 0: Disabled, 1 (default): Enabled
cpuopt_const_prop =
# Enable miscellaneous CPU optimizations (basic IR optimization)
# 0: Disabled, 1 (default): Enabled
cpuopt_misc_ir =
# Enable reduction of memory misalignment checks (reduce memory fallbacks for misaligned access)
# 0: Disabled, 1 (default): Enabled
cpuopt_reduce_misalign_checks =
# Enable Host MMU Emulation (faster guest memory access)
# 0: Disabled, 1 (default): Enabled
cpuopt_fastmem =
# Enable Host MMU Emulation for exclusive memory instructions (faster guest memory access)
# 0: Disabled, 1 (default): Enabled
cpuopt_fastmem_exclusives =
# Enable fallback on failure of fastmem of exclusive memory instructions (faster guest memory access)
# 0: Disabled, 1 (default): Enabled
cpuopt_recompile_exclusives =
# Enable optimization to ignore invalid memory accesses (faster guest memory access)
# 0: Disabled, 1 (default): Enabled
cpuopt_ignore_memory_aborts =
# Enable unfuse FMA (improve performance on CPUs without FMA)
# Only enabled if cpu_accuracy is set to Unsafe. Automatically chosen with cpu_accuracy = Auto-select.
# 0: Disabled, 1 (default): Enabled
cpuopt_unsafe_unfuse_fma =
# Enable faster FRSQRTE and FRECPE
# Only enabled if cpu_accuracy is set to Unsafe.
# 0: Disabled, 1 (default): Enabled
cpuopt_unsafe_reduce_fp_error =
# Enable faster ASIMD instructions (32 bits only)
# Only enabled if cpu_accuracy is set to Unsafe. Automatically chosen with cpu_accuracy = Auto-select.
# 0: Disabled, 1 (default): Enabled
cpuopt_unsafe_ignore_standard_fpcr =
# Enable inaccurate NaN handling
# Only enabled if cpu_accuracy is set to Unsafe. Automatically chosen with cpu_accuracy = Auto-select.
# 0: Disabled, 1 (default): Enabled
cpuopt_unsafe_inaccurate_nan =
# Disable address space checks (64 bits only)
# Only enabled if cpu_accuracy is set to Unsafe. Automatically chosen with cpu_accuracy = Auto-select.
# 0: Disabled, 1 (default): Enabled
cpuopt_unsafe_fastmem_check =
# Enable faster exclusive instructions
# Only enabled if cpu_accuracy is set to Unsafe. Automatically chosen with cpu_accuracy = Auto-select.
# 0: Disabled, 1 (default): Enabled
cpuopt_unsafe_ignore_global_monitor =
[Renderer]
# Which backend API to use.
# 0: OpenGL (unsupported), 1 (default): Vulkan, 2: Null
backend =
# Whether to enable asynchronous presentation (Vulkan only)
# 0: Off, 1 (default): On
async_presentation =
# Forces the GPU to run at the maximum possible clocks (thermal constraints will still be applied).
# 0 (default): Disabled, 1: Enabled
force_max_clock =
# Enable graphics API debugging mode.
# 0 (default): Disabled, 1: Enabled
debug =
# Enable shader feedback.
# 0 (default): Disabled, 1: Enabled
renderer_shader_feedback =
# Enable Nsight Aftermath crash dumps
# 0 (default): Disabled, 1: Enabled
nsight_aftermath =
# Disable shader loop safety checks, executing the shader without loop logic changes
# 0 (default): Disabled, 1: Enabled
disable_shader_loop_safety_checks =
# Which Vulkan physical device to use (defaults to 0)
vulkan_device =
# 0: 0.5x (360p/540p) [EXPERIMENTAL]
# 1: 0.75x (540p/810p) [EXPERIMENTAL]
# 2 (default): 1x (720p/1080p)
# 3: 2x (1440p/2160p)
# 4: 3x (2160p/3240p)
# 5: 4x (2880p/4320p)
# 6: 5x (3600p/5400p)
# 7: 6x (4320p/6480p)
resolution_setup =
# Pixel filter to use when up- or down-sampling rendered frames.
# 0: Nearest Neighbor
# 1 (default): Bilinear
# 2: Bicubic
# 3: Gaussian
# 4: ScaleForce
# 5: AMD FidelityFX™ Super Resolution [Vulkan Only]
scaling_filter =
# Anti-Aliasing (AA)
# 0 (default): None, 1: FXAA
anti_aliasing =
# Whether to use fullscreen or borderless window mode
# 0 (Windows default): Borderless window, 1 (All other default): Exclusive fullscreen
fullscreen_mode =
# Aspect ratio
# 0: Default (16:9), 1: Force 4:3, 2: Force 21:9, 3: Force 16:10, 4: Stretch to Window
aspect_ratio =
# Anisotropic filtering
# 0: Default, 1: 2x, 2: 4x, 3: 8x, 4: 16x
max_anisotropy =
# Whether to enable VSync or not.
# OpenGL: Values other than 0 enable VSync
# Vulkan: FIFO is selected if the requested mode is not supported by the driver.
# FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen refresh rate.
# FIFO Relaxed is similar to FIFO but allows tearing as it recovers from a slow down.
# Mailbox can have lower latency than FIFO and does not tear but may drop frames.
# Immediate (no synchronization) just presents whatever is available and can exhibit tearing.
# 0: Immediate (Off), 1 (Default): Mailbox (On), 2: FIFO, 3: FIFO Relaxed
use_vsync =
# Selects the OpenGL shader backend. NV_gpu_program5 is required for GLASM. If NV_gpu_program5 is
# not available and GLASM is selected, GLSL will be used.
# 0: GLSL, 1 (default): GLASM, 2: SPIR-V
shader_backend =
# Whether to allow asynchronous shader building.
# 0 (default): Off, 1: On
use_asynchronous_shaders =
# Uses reactive flushing instead of predictive flushing. Allowing a more accurate syncing of memory.
# 0 (default): Off, 1: On
use_reactive_flushing =
# NVDEC emulation.
# 0: Disabled, 1: CPU Decoding, 2 (default): GPU Decoding
nvdec_emulation =
# Accelerate ASTC texture decoding.
# 0 (default): Off, 1: On
accelerate_astc =
# Turns on the speed limiter, which will limit the emulation speed to the desired speed limit value
# 0: Off, 1: On (default)
use_speed_limit =
# Limits the speed of the game to run no faster than this value as a percentage of target speed
# 1 - 9999: Speed limit as a percentage of target game speed. 100 (default)
speed_limit =
# Whether to use disk based shader cache
# 0: Off, 1 (default): On
use_disk_shader_cache =
# Which gpu accuracy level to use
# 0 (default): Normal, 1: High, 2: Extreme (Very slow)
gpu_accuracy =
# Whether to use asynchronous GPU emulation
# 0 : Off (slow), 1 (default): On (fast)
use_asynchronous_gpu_emulation =
# Inform the guest that GPU operations completed more quickly than they did.
# 0: Off, 1 (default): On
use_fast_gpu_time =
# Force unmodified buffers to be flushed, which can cost performance.
# 0: Off (default), 1: On
use_pessimistic_flushes =
# Whether to use garbage collection or not for GPU caches.
# 0 (default): Off, 1: On
use_caches_gc =
# The clear color for the renderer. What shows up on the sides of the bottom screen.
# Must be in range of 0-255. Defaults to 0 for all.
bg_red =
bg_blue =
bg_green =
[Audio]
# Which audio output engine to use.
# auto (default): Auto-select
# cubeb: Cubeb audio engine (if available)
# sdl2: SDL2 audio engine (if available)
# null: No audio output
output_engine =
# Which audio device to use.
# auto (default): Auto-select
output_device =
# Output volume.
# 100 (default): 100%, 0; mute
volume =
[Data Storage]
# Whether to create a virtual SD card.
# 1: Yes, 0 (default): No
use_virtual_sd =
# Whether or not to enable gamecard emulation
# 1: Yes, 0 (default): No
gamecard_inserted =
# Whether or not the gamecard should be emulated as the current game
# If 'gamecard_inserted' is 0 this setting is irrelevant
# 1: Yes, 0 (default): No
gamecard_current_game =
# Path to an XCI file to use as the gamecard
# If 'gamecard_inserted' is 0 this setting is irrelevant
# If 'gamecard_current_game' is 1 this setting is irrelevant
gamecard_path =
[System]
# Whether the system is docked
# 1 (default): Yes, 0: No
use_docked_mode =
# Sets the seed for the RNG generator built into the switch
# rng_seed will be ignored and randomly generated if rng_seed_enabled is false
rng_seed_enabled =
rng_seed =
# Sets the current time (in seconds since 12:00 AM Jan 1, 1970) that will be used by the time service
# This will auto-increment, with the time set being the time the game is started
# This override will only occur if custom_rtc_enabled is true, otherwise the current time is used
custom_rtc_enabled =
custom_rtc =
# Sets the systems language index
# 0: Japanese, 1: English (default), 2: French, 3: German, 4: Italian, 5: Spanish, 6: Chinese,
# 7: Korean, 8: Dutch, 9: Portuguese, 10: Russian, 11: Taiwanese, 12: British English, 13: Canadian French,
# 14: Latin American Spanish, 15: Simplified Chinese, 16: Traditional Chinese, 17: Brazilian Portuguese
language_index =
# The system region that yuzu will use during emulation
# -1: Auto-select (default), 0: Japan, 1: USA, 2: Europe, 3: Australia, 4: China, 5: Korea, 6: Taiwan
region_index =
# The system time zone that yuzu will use during emulation
# 0: Auto-select (default), 1: Default (system archive value), Others: Index for specified time zone
time_zone_index =
# Sets the sound output mode.
# 0: Mono, 1 (default): Stereo, 2: Surround
sound_index =
[Miscellaneous]
# A filter which removes logs below a certain logging level.
# Examples: *:Debug Kernel.SVC:Trace Service.*:Critical
log_filter = *:Trace
# Use developer keys
# 0 (default): Disabled, 1: Enabled
use_dev_keys =
[Debugging]
# Record frame time data, can be found in the log directory. Boolean value
record_frame_times =
# Determines whether or not yuzu will dump the ExeFS of all games it attempts to load while loading them
dump_exefs=false
# Determines whether or not yuzu will dump all NSOs it attempts to load while loading them
dump_nso=false
# Determines whether or not yuzu will save the filesystem access log.
enable_fs_access_log=false
# Enables verbose reporting services
reporting_services =
# Determines whether or not yuzu will report to the game that the emulated console is in Kiosk Mode
# false: Retail/Normal Mode (default), true: Kiosk Mode
quest_flag =
# Determines whether debug asserts should be enabled, which will throw an exception on asserts.
# false: Disabled (default), true: Enabled
use_debug_asserts =
# Determines whether unimplemented HLE service calls should be automatically stubbed.
# false: Disabled (default), true: Enabled
use_auto_stub =
# Enables/Disables the macro JIT compiler
disable_macro_jit=false
# Determines whether to enable the GDB stub and wait for the debugger to attach before running.
# false: Disabled (default), true: Enabled
use_gdbstub=false
# The port to use for the GDB server, if it is enabled.
gdbstub_port=6543
[WebService]
# Whether or not to enable telemetry
# 0: No, 1 (default): Yes
enable_telemetry =
# URL for Web API
web_api_url = https://api.yuzu-emu.org
# Username and token for yuzu Web Service
# See https://profile.yuzu-emu.org/ for more info
yuzu_username =
yuzu_token =
[Network]
# Name of the network interface device to use with yuzu LAN play.
# e.g. On *nix: 'enp7s0', 'wlp6s0u1u3u3', 'lo'
# e.g. On Windows: 'Ethernet', 'Wi-Fi'
network_interface =
[AddOns]
# Used to disable add-ons
# List of title IDs of games that will have add-ons disabled (separated by '|'):
title_ids =
# For each title ID, have a key/value pair called `disabled_<title_id>` equal to the names of the add-ons to disable (sep. by '|')
# e.x. disabled_0100000000010000 = Update|DLC <- disables Updates and DLC on Super Mario Odyssey
)";
} // namespace DefaultINI

View File

@ -13,6 +13,8 @@ static JavaVM* s_java_vm;
static jclass s_native_library_class; static jclass s_native_library_class;
static jclass s_disk_cache_progress_class; static jclass s_disk_cache_progress_class;
static jclass s_load_callback_stage_class; static jclass s_load_callback_stage_class;
static jclass s_game_dir_class;
static jmethodID s_game_dir_constructor;
static jmethodID s_exit_emulation_activity; static jmethodID s_exit_emulation_activity;
static jmethodID s_disk_cache_load_progress; static jmethodID s_disk_cache_load_progress;
static jmethodID s_on_emulation_started; static jmethodID s_on_emulation_started;
@ -53,6 +55,14 @@ jclass GetDiskCacheLoadCallbackStageClass() {
return s_load_callback_stage_class; return s_load_callback_stage_class;
} }
jclass GetGameDirClass() {
return s_game_dir_class;
}
jmethodID GetGameDirConstructor() {
return s_game_dir_constructor;
}
jmethodID GetExitEmulationActivity() { jmethodID GetExitEmulationActivity() {
return s_exit_emulation_activity; return s_exit_emulation_activity;
} }
@ -90,6 +100,11 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) {
s_load_callback_stage_class = reinterpret_cast<jclass>(env->NewGlobalRef(env->FindClass( s_load_callback_stage_class = reinterpret_cast<jclass>(env->NewGlobalRef(env->FindClass(
"org/yuzu/yuzu_emu/disk_shader_cache/DiskShaderCacheProgress$LoadCallbackStage"))); "org/yuzu/yuzu_emu/disk_shader_cache/DiskShaderCacheProgress$LoadCallbackStage")));
const jclass game_dir_class = env->FindClass("org/yuzu/yuzu_emu/model/GameDir");
s_game_dir_class = reinterpret_cast<jclass>(env->NewGlobalRef(game_dir_class));
s_game_dir_constructor = env->GetMethodID(game_dir_class, "<init>", "(Ljava/lang/String;Z)V");
env->DeleteLocalRef(game_dir_class);
// Initialize methods // Initialize methods
s_exit_emulation_activity = s_exit_emulation_activity =
env->GetStaticMethodID(s_native_library_class, "exitEmulationActivity", "(I)V"); env->GetStaticMethodID(s_native_library_class, "exitEmulationActivity", "(I)V");
@ -120,6 +135,7 @@ void JNI_OnUnload(JavaVM* vm, void* reserved) {
env->DeleteGlobalRef(s_native_library_class); env->DeleteGlobalRef(s_native_library_class);
env->DeleteGlobalRef(s_disk_cache_progress_class); env->DeleteGlobalRef(s_disk_cache_progress_class);
env->DeleteGlobalRef(s_load_callback_stage_class); env->DeleteGlobalRef(s_load_callback_stage_class);
env->DeleteGlobalRef(s_game_dir_class);
// UnInitialize applets // UnInitialize applets
SoftwareKeyboard::CleanupJNI(env); SoftwareKeyboard::CleanupJNI(env);

View File

@ -13,6 +13,8 @@ JNIEnv* GetEnvForThread();
jclass GetNativeLibraryClass(); jclass GetNativeLibraryClass();
jclass GetDiskCacheProgressClass(); jclass GetDiskCacheProgressClass();
jclass GetDiskCacheLoadCallbackStageClass(); jclass GetDiskCacheLoadCallbackStageClass();
jclass GetGameDirClass();
jmethodID GetGameDirConstructor();
jmethodID GetExitEmulationActivity(); jmethodID GetExitEmulationActivity();
jmethodID GetDiskCacheLoadProgress(); jmethodID GetDiskCacheLoadProgress();
jmethodID GetOnEmulationStarted(); jmethodID GetOnEmulationStarted();

View File

@ -52,8 +52,8 @@
#include "core/hle/service/am/applets/applets.h" #include "core/hle/service/am/applets/applets.h"
#include "core/hle/service/filesystem/filesystem.h" #include "core/hle/service/filesystem/filesystem.h"
#include "core/loader/loader.h" #include "core/loader/loader.h"
#include "frontend_common/config.h"
#include "jni/android_common/android_common.h" #include "jni/android_common/android_common.h"
#include "jni/config.h"
#include "jni/id_cache.h" #include "jni/id_cache.h"
#include "jni/native.h" #include "jni/native.h"
#include "video_core/renderer_base.h" #include "video_core/renderer_base.h"
@ -664,8 +664,6 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchReleased(JNIEnv* env, jclass c
void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeSystem(JNIEnv* env, jclass clazz, void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeSystem(JNIEnv* env, jclass clazz,
jboolean reload) { jboolean reload) {
// Create the default config.ini.
Config{};
// Initialize the emulated system. // Initialize the emulated system.
if (!reload) { if (!reload) {
EmulationSession::GetInstance().System().Initialize(); EmulationSession::GetInstance().System().Initialize();
@ -680,17 +678,6 @@ jint Java_org_yuzu_yuzu_1emu_NativeLibrary_defaultCPUCore(JNIEnv* env, jclass cl
void Java_org_yuzu_yuzu_1emu_NativeLibrary_run__Ljava_lang_String_2Ljava_lang_String_2Z( void Java_org_yuzu_yuzu_1emu_NativeLibrary_run__Ljava_lang_String_2Ljava_lang_String_2Z(
JNIEnv* env, jclass clazz, jstring j_file, jstring j_savestate, jboolean j_delete_savestate) {} JNIEnv* env, jclass clazz, jstring j_file, jstring j_savestate, jboolean j_delete_savestate) {}
void Java_org_yuzu_yuzu_1emu_NativeLibrary_reloadSettings(JNIEnv* env, jclass clazz) {
Config{};
}
void Java_org_yuzu_yuzu_1emu_NativeLibrary_initGameIni(JNIEnv* env, jclass clazz,
jstring j_game_id) {
std::string_view game_id = env->GetStringUTFChars(j_game_id, 0);
env->ReleaseStringUTFChars(j_game_id, game_id.data());
}
jdoubleArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getPerfStats(JNIEnv* env, jclass clazz) { jdoubleArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getPerfStats(JNIEnv* env, jclass clazz) {
jdoubleArray j_stats = env->NewDoubleArray(4); jdoubleArray j_stats = env->NewDoubleArray(4);
@ -707,6 +694,14 @@ jdoubleArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getPerfStats(JNIEnv* env, jcl
return j_stats; return j_stats;
} }
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getCpuBackend(JNIEnv* env, jclass clazz) {
if (Settings::IsNceEnabled()) {
return ToJString(env, "NCE");
}
return ToJString(env, "JIT");
}
void Java_org_yuzu_yuzu_1emu_utils_DirectoryInitialization_setSysDirectory(JNIEnv* env, void Java_org_yuzu_yuzu_1emu_utils_DirectoryInitialization_setSysDirectory(JNIEnv* env,
jclass clazz, jclass clazz,
jstring j_path) {} jstring j_path) {}

View File

@ -5,11 +5,15 @@
#include <jni.h> #include <jni.h>
#include "android_config.h"
#include "android_settings.h"
#include "common/logging/log.h" #include "common/logging/log.h"
#include "common/settings.h" #include "common/settings.h"
#include "frontend_common/config.h"
#include "jni/android_common/android_common.h" #include "jni/android_common/android_common.h"
#include "jni/config.h" #include "jni/id_cache.h"
#include "uisettings.h"
std::unique_ptr<AndroidConfig> config;
template <typename T> template <typename T>
Settings::Setting<T>* getSetting(JNIEnv* env, jstring jkey) { Settings::Setting<T>* getSetting(JNIEnv* env, jstring jkey) {
@ -28,6 +32,22 @@ Settings::Setting<T>* getSetting(JNIEnv* env, jstring jkey) {
extern "C" { extern "C" {
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_initializeConfig(JNIEnv* env, jobject obj) {
config = std::make_unique<AndroidConfig>();
}
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_unloadConfig(JNIEnv* env, jobject obj) {
config.reset();
}
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_reloadSettings(JNIEnv* env, jobject obj) {
config->AndroidConfig::ReloadAllValues();
}
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_saveSettings(JNIEnv* env, jobject obj) {
config->AndroidConfig::SaveAllValues();
}
jboolean Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getBoolean(JNIEnv* env, jobject obj, jboolean Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getBoolean(JNIEnv* env, jobject obj,
jstring jkey, jboolean getDefault) { jstring jkey, jboolean getDefault) {
auto setting = getSetting<bool>(env, jkey); auto setting = getSetting<bool>(env, jkey);
@ -234,4 +254,55 @@ jstring Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getPairedSettingKey(JNIEnv* e
return ToJString(env, setting->PairedSetting()->GetLabel()); return ToJString(env, setting->PairedSetting()->GetLabel());
} }
jobjectArray Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getGameDirs(JNIEnv* env, jobject obj) {
jclass gameDirClass = IDCache::GetGameDirClass();
jmethodID gameDirConstructor = IDCache::GetGameDirConstructor();
jobjectArray jgameDirArray =
env->NewObjectArray(AndroidSettings::values.game_dirs.size(), gameDirClass, nullptr);
for (size_t i = 0; i < AndroidSettings::values.game_dirs.size(); ++i) {
jobject jgameDir =
env->NewObject(gameDirClass, gameDirConstructor,
ToJString(env, AndroidSettings::values.game_dirs[i].path),
static_cast<jboolean>(AndroidSettings::values.game_dirs[i].deep_scan));
env->SetObjectArrayElement(jgameDirArray, i, jgameDir);
}
return jgameDirArray;
}
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setGameDirs(JNIEnv* env, jobject obj,
jobjectArray gameDirs) {
AndroidSettings::values.game_dirs.clear();
int size = env->GetArrayLength(gameDirs);
if (size == 0) {
return;
}
jobject dir = env->GetObjectArrayElement(gameDirs, 0);
jclass gameDirClass = IDCache::GetGameDirClass();
jfieldID uriStringField = env->GetFieldID(gameDirClass, "uriString", "Ljava/lang/String;");
jfieldID deepScanBooleanField = env->GetFieldID(gameDirClass, "deepScan", "Z");
for (int i = 0; i < size; ++i) {
dir = env->GetObjectArrayElement(gameDirs, i);
jstring juriString = static_cast<jstring>(env->GetObjectField(dir, uriStringField));
jboolean jdeepScanBoolean = env->GetBooleanField(dir, deepScanBooleanField);
std::string uriString = GetJString(env, juriString);
AndroidSettings::values.game_dirs.push_back(
AndroidSettings::GameDir{uriString, static_cast<bool>(jdeepScanBoolean)});
}
}
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_addGameDir(JNIEnv* env, jobject obj,
jobject gameDir) {
jclass gameDirClass = IDCache::GetGameDirClass();
jfieldID uriStringField = env->GetFieldID(gameDirClass, "uriString", "Ljava/lang/String;");
jfieldID deepScanBooleanField = env->GetFieldID(gameDirClass, "deepScan", "Z");
jstring juriString = static_cast<jstring>(env->GetObjectField(gameDir, uriStringField));
jboolean jdeepScanBoolean = env->GetBooleanField(gameDir, deepScanBooleanField);
std::string uriString = GetJString(env, juriString);
AndroidSettings::values.game_dirs.push_back(
AndroidSettings::GameDir{uriString, static_cast<bool>(jdeepScanBoolean)});
}
} // extern "C" } // extern "C"

View File

@ -0,0 +1,70 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
style="?attr/materialCardViewOutlinedStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginVertical="12dp"
android:focusable="true">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="16dp"
android:layout_gravity="center_vertical"
android:animateLayoutChanges="true">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/path"
style="@style/TextAppearance.Material3.BodyLarge"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|start"
android:ellipsize="none"
android:marqueeRepeatLimit="marquee_forever"
android:requiresFadingEdge="horizontal"
android:singleLine="true"
android:textAlignment="viewStart"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/button_layout"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="@string/select_gpu_driver_default" />
<LinearLayout
android:id="@+id/button_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent">
<Button
android:id="@+id/button_edit"
style="@style/Widget.Material3.Button.IconButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/delete"
android:tooltipText="@string/edit"
app:icon="@drawable/ic_edit"
app:iconTint="?attr/colorControlNormal" />
<Button
android:id="@+id/button_delete"
style="@style/Widget.Material3.Button.IconButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/delete"
android:tooltipText="@string/delete"
app:icon="@drawable/ic_delete"
app:iconTint="?attr/colorControlNormal" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>

View File

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="24dp"
android:orientation="vertical">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/path"
style="@style/TextAppearance.Material3.BodyLarge"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_gravity="center_vertical|start"
android:layout_weight="1"
android:ellipsize="marquee"
android:marqueeRepeatLimit="marquee_forever"
android:requiresFadingEdge="horizontal"
android:singleLine="true"
android:textAlignment="viewStart"
tools:text="folder/folder/folder/folder" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingTop="8dp">
<com.google.android.material.textview.MaterialTextView
style="@style/TextAppearance.Material3.BodyMedium"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|start"
android:layout_weight="1"
android:text="@string/deep_scan"
android:textAlignment="viewStart" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/deep_scan_switch"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout>

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="24dp"
android:orientation="vertical">
<LinearLayout
android:id="@+id/deep_scan_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<com.google.android.material.textview.MaterialTextView
style="@style/TextAppearance.Material3.BodyMedium"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|start"
android:layout_weight="1"
android:text="@string/deep_scan"
android:textAlignment="viewStart" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/deep_scan_switch"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout>

View File

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/coordinator_folders"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/colorSurface">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar_folders"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
app:liftOnScrollTargetViewId="@id/list_folders">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar_folders"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:navigationIcon="@drawable/ic_back"
app:title="@string/game_folders" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/list_folders"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/button_add"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:contentDescription="@string/add_games"
app:srcCompat="@drawable/ic_add"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -28,6 +28,9 @@
<action <action
android:id="@+id/action_homeSettingsFragment_to_appletLauncherFragment" android:id="@+id/action_homeSettingsFragment_to_appletLauncherFragment"
app:destination="@id/appletLauncherFragment" /> app:destination="@id/appletLauncherFragment" />
<action
android:id="@+id/action_homeSettingsFragment_to_gameFoldersFragment"
app:destination="@id/gameFoldersFragment" />
</fragment> </fragment>
<fragment <fragment
@ -117,5 +120,9 @@
android:id="@+id/cabinetLauncherDialogFragment" android:id="@+id/cabinetLauncherDialogFragment"
android:name="org.yuzu.yuzu_emu.fragments.CabinetLauncherDialogFragment" android:name="org.yuzu.yuzu_emu.fragments.CabinetLauncherDialogFragment"
android:label="CabinetLauncherDialogFragment" /> android:label="CabinetLauncherDialogFragment" />
<fragment
android:id="@+id/gameFoldersFragment"
android:name="org.yuzu.yuzu_emu.fragments.GameFoldersFragment"
android:label="GameFoldersFragment" />
</navigation> </navigation>

View File

@ -175,6 +175,24 @@
<item>2</item> <item>2</item>
</integer-array> </integer-array>
<string-array name="cpuBackendArm64Names">
<item>@string/cpu_backend_dynarmic</item>
<item>@string/cpu_backend_nce</item>
</string-array>
<integer-array name="cpuBackendArm64Values">
<item>0</item>
<item>1</item>
</integer-array>
<string-array name="cpuBackendX86Names">
<item>@string/cpu_backend_dynarmic</item>
</string-array>
<integer-array name="cpuBackendX86Values">
<item>0</item>
</integer-array>
<string-array name="cpuAccuracyNames"> <string-array name="cpuAccuracyNames">
<item>@string/auto</item> <item>@string/auto</item>
<item>@string/cpu_accuracy_accurate</item> <item>@string/cpu_accuracy_accurate</item>

View File

@ -13,7 +13,7 @@
<dimen name="menu_width">256dp</dimen> <dimen name="menu_width">256dp</dimen>
<dimen name="card_width">165dp</dimen> <dimen name="card_width">165dp</dimen>
<dimen name="icon_inset">24dp</dimen> <dimen name="icon_inset">24dp</dimen>
<dimen name="spacing_bottom_list_fab">72dp</dimen> <dimen name="spacing_bottom_list_fab">76dp</dimen>
<dimen name="spacing_fab">24dp</dimen> <dimen name="spacing_fab">24dp</dimen>
<dimen name="dialog_margin">20dp</dimen> <dimen name="dialog_margin">20dp</dimen>

View File

@ -38,6 +38,7 @@
<string name="empty_gamelist">No files were found or no game directory has been selected yet.</string> <string name="empty_gamelist">No files were found or no game directory has been selected yet.</string>
<string name="search_and_filter_games">Search and filter games</string> <string name="search_and_filter_games">Search and filter games</string>
<string name="select_games_folder">Select games folder</string> <string name="select_games_folder">Select games folder</string>
<string name="manage_game_folders">Manage game folders</string>
<string name="select_games_folder_description">Allows yuzu to populate the games list</string> <string name="select_games_folder_description">Allows yuzu to populate the games list</string>
<string name="add_games_warning">Skip selecting games folder?</string> <string name="add_games_warning">Skip selecting games folder?</string>
<string name="add_games_warning_description">Games won\'t be displayed in the Games list if a folder isn\'t selected.</string> <string name="add_games_warning_description">Games won\'t be displayed in the Games list if a folder isn\'t selected.</string>
@ -124,6 +125,11 @@
<string name="manage_yuzu_data_description">Import/export firmware, keys, user data, and more!</string> <string name="manage_yuzu_data_description">Import/export firmware, keys, user data, and more!</string>
<string name="share_save_file">Share save file</string> <string name="share_save_file">Share save file</string>
<string name="export_save_failed">Failed to export save</string> <string name="export_save_failed">Failed to export save</string>
<string name="game_folders">Game folders</string>
<string name="deep_scan">Deep scan</string>
<string name="add_game_folder">Add game folder</string>
<string name="folder_already_added">This folder was already added!</string>
<string name="game_folder_properties">Game folder properties</string>
<!-- Applet launcher strings --> <!-- Applet launcher strings -->
<string name="applets">Applet launcher</string> <string name="applets">Applet launcher</string>
@ -185,6 +191,7 @@
<string name="frame_limit_enable_description">Limits emulation speed to a specified percentage of normal speed.</string> <string name="frame_limit_enable_description">Limits emulation speed to a specified percentage of normal speed.</string>
<string name="frame_limit_slider">Limit speed percent</string> <string name="frame_limit_slider">Limit speed percent</string>
<string name="frame_limit_slider_description">Specifies the percentage to limit emulation speed. 100% is the normal speed. Values higher or lower will increase or decrease the speed limit.</string> <string name="frame_limit_slider_description">Specifies the percentage to limit emulation speed. 100% is the normal speed. Values higher or lower will increase or decrease the speed limit.</string>
<string name="cpu_backend">CPU backend</string>
<string name="cpu_accuracy">CPU accuracy</string> <string name="cpu_accuracy">CPU accuracy</string>
<string name="value_with_units">%1$s%2$s</string> <string name="value_with_units">%1$s%2$s</string>
@ -257,6 +264,7 @@
<string name="cancelling">Cancelling</string> <string name="cancelling">Cancelling</string>
<string name="install">Install</string> <string name="install">Install</string>
<string name="delete">Delete</string> <string name="delete">Delete</string>
<string name="edit">Edit</string>
<string name="export_success">Exported successfully</string> <string name="export_success">Exported successfully</string>
<!-- GPU driver installation --> <!-- GPU driver installation -->
@ -416,6 +424,10 @@
<string name="ratio_force_sixteen_ten">Force 16:10</string> <string name="ratio_force_sixteen_ten">Force 16:10</string>
<string name="ratio_stretch">Stretch to window</string> <string name="ratio_stretch">Stretch to window</string>
<!-- CPU Backend -->
<string name="cpu_backend_dynarmic">Dynarmic (Slow)</string>
<string name="cpu_backend_nce">Native code execution (NCE)</string>
<!-- CPU Accuracy --> <!-- CPU Accuracy -->
<string name="cpu_accuracy_accurate">Accurate</string> <string name="cpu_accuracy_accurate">Accurate</string>
<string name="cpu_accuracy_unsafe">Unsafe</string> <string name="cpu_accuracy_unsafe">Unsafe</string>

View File

@ -52,6 +52,7 @@ add_library(common STATIC
fiber.cpp fiber.cpp
fiber.h fiber.h
fixed_point.h fixed_point.h
free_region_manager.h
fs/file.cpp fs/file.cpp
fs/file.h fs/file.h
fs/fs.cpp fs/fs.cpp
@ -166,6 +167,13 @@ if (WIN32)
target_link_libraries(common PRIVATE ntdll) target_link_libraries(common PRIVATE ntdll)
endif() endif()
if (NOT WIN32)
target_sources(common PRIVATE
signal_chain.cpp
signal_chain.h
)
endif()
if(ANDROID) if(ANDROID)
target_sources(common target_sources(common
PRIVATE PRIVATE
@ -191,7 +199,7 @@ if(ARCHITECTURE_x86_64)
target_link_libraries(common PRIVATE xbyak::xbyak) target_link_libraries(common PRIVATE xbyak::xbyak)
endif() endif()
if (ARCHITECTURE_arm64 AND (ANDROID OR LINUX)) if (HAS_NCE)
target_sources(common target_sources(common
PRIVATE PRIVATE
arm64/native_clock.cpp arm64/native_clock.cpp

View File

@ -0,0 +1,55 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <mutex>
#include <boost/icl/interval_set.hpp>
namespace Common {
class FreeRegionManager {
public:
explicit FreeRegionManager() = default;
~FreeRegionManager() = default;
void SetAddressSpace(void* start, size_t size) {
this->FreeBlock(start, size);
}
std::pair<void*, size_t> FreeBlock(void* block_ptr, size_t size) {
std::scoped_lock lk(m_mutex);
// Check to see if we are adjacent to any regions.
auto start_address = reinterpret_cast<uintptr_t>(block_ptr);
auto end_address = start_address + size;
auto it = m_free_regions.find({start_address - 1, end_address + 1});
// If we are, join with them, ensuring we stay in bounds.
if (it != m_free_regions.end()) {
start_address = std::min(start_address, it->lower());
end_address = std::max(end_address, it->upper());
}
// Free the relevant region.
m_free_regions.insert({start_address, end_address});
// Return the adjusted pointers.
block_ptr = reinterpret_cast<void*>(start_address);
size = end_address - start_address;
return {block_ptr, size};
}
void AllocateBlock(void* block_ptr, size_t size) {
std::scoped_lock lk(m_mutex);
auto address = reinterpret_cast<uintptr_t>(block_ptr);
m_free_regions.subtract({address, address + size});
}
private:
std::mutex m_mutex;
boost::icl::interval_set<uintptr_t> m_free_regions;
};
} // namespace Common

View File

@ -21,15 +21,18 @@
#include <boost/icl/interval_set.hpp> #include <boost/icl/interval_set.hpp>
#include <fcntl.h> #include <fcntl.h>
#include <sys/mman.h> #include <sys/mman.h>
#include <sys/random.h>
#include <unistd.h> #include <unistd.h>
#include "common/scope_exit.h" #include "common/scope_exit.h"
#endif // ^^^ Linux ^^^ #endif // ^^^ Linux ^^^
#include <mutex> #include <mutex>
#include <random>
#include "common/alignment.h" #include "common/alignment.h"
#include "common/assert.h" #include "common/assert.h"
#include "common/free_region_manager.h"
#include "common/host_memory.h" #include "common/host_memory.h"
#include "common/logging/log.h" #include "common/logging/log.h"
@ -141,7 +144,7 @@ public:
Release(); Release();
} }
void Map(size_t virtual_offset, size_t host_offset, size_t length) { void Map(size_t virtual_offset, size_t host_offset, size_t length, MemoryPermission perms) {
std::unique_lock lock{placeholder_mutex}; std::unique_lock lock{placeholder_mutex};
if (!IsNiechePlaceholder(virtual_offset, length)) { if (!IsNiechePlaceholder(virtual_offset, length)) {
Split(virtual_offset, length); Split(virtual_offset, length);
@ -160,7 +163,7 @@ public:
} }
} }
void Protect(size_t virtual_offset, size_t length, bool read, bool write) { void Protect(size_t virtual_offset, size_t length, bool read, bool write, bool execute) {
DWORD new_flags{}; DWORD new_flags{};
if (read && write) { if (read && write) {
new_flags = PAGE_READWRITE; new_flags = PAGE_READWRITE;
@ -186,6 +189,11 @@ public:
} }
} }
void EnableDirectMappedAddress() {
// TODO
UNREACHABLE();
}
const size_t backing_size; ///< Size of the backing memory in bytes const size_t backing_size; ///< Size of the backing memory in bytes
const size_t virtual_size; ///< Size of the virtual address placeholder in bytes const size_t virtual_size; ///< Size of the virtual address placeholder in bytes
@ -353,6 +361,55 @@ private:
#elif defined(__linux__) || defined(__FreeBSD__) // ^^^ Windows ^^^ vvv Linux vvv #elif defined(__linux__) || defined(__FreeBSD__) // ^^^ Windows ^^^ vvv Linux vvv
#ifdef ARCHITECTURE_arm64
static void* ChooseVirtualBase(size_t virtual_size) {
constexpr uintptr_t Map39BitSize = (1ULL << 39);
constexpr uintptr_t Map36BitSize = (1ULL << 36);
// This is not a cryptographic application, we just want something random.
std::mt19937_64 rng;
// We want to ensure we are allocating at an address aligned to the L2 block size.
// For Qualcomm devices, we must also allocate memory above 36 bits.
const size_t lower = Map36BitSize / HugePageSize;
const size_t upper = (Map39BitSize - virtual_size) / HugePageSize;
const size_t range = upper - lower;
// Try up to 64 times to allocate memory at random addresses in the range.
for (int i = 0; i < 64; i++) {
// Calculate a possible location.
uintptr_t hint_address = ((rng() % range) + lower) * HugePageSize;
// Try to map.
// Note: we may be able to take advantage of MAP_FIXED_NOREPLACE here.
void* map_pointer =
mmap(reinterpret_cast<void*>(hint_address), virtual_size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0);
// If we successfully mapped, we're done.
if (reinterpret_cast<uintptr_t>(map_pointer) == hint_address) {
return map_pointer;
}
// Unmap if necessary, and try again.
if (map_pointer != MAP_FAILED) {
munmap(map_pointer, virtual_size);
}
}
return MAP_FAILED;
}
#else
static void* ChooseVirtualBase(size_t virtual_size) {
return mmap(nullptr, virtual_size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0);
}
#endif
class HostMemory::Impl { class HostMemory::Impl {
public: public:
explicit Impl(size_t backing_size_, size_t virtual_size_) explicit Impl(size_t backing_size_, size_t virtual_size_)
@ -415,8 +472,7 @@ public:
} }
} }
#else #else
virtual_base = static_cast<u8*>(mmap(nullptr, virtual_size, PROT_NONE, virtual_base = virtual_map_base = static_cast<u8*>(ChooseVirtualBase(virtual_size));
MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0));
if (virtual_base == MAP_FAILED) { if (virtual_base == MAP_FAILED) {
LOG_CRITICAL(HW_Memory, "mmap failed: {}", strerror(errno)); LOG_CRITICAL(HW_Memory, "mmap failed: {}", strerror(errno));
throw std::bad_alloc{}; throw std::bad_alloc{};
@ -424,7 +480,7 @@ public:
madvise(virtual_base, virtual_size, MADV_HUGEPAGE); madvise(virtual_base, virtual_size, MADV_HUGEPAGE);
#endif #endif
placeholders.add({0, virtual_size}); free_manager.SetAddressSpace(virtual_base, virtual_size);
good = true; good = true;
} }
@ -432,14 +488,29 @@ public:
Release(); Release();
} }
void Map(size_t virtual_offset, size_t host_offset, size_t length) { void Map(size_t virtual_offset, size_t host_offset, size_t length, MemoryPermission perms) {
{ // Intersect the range with our address space.
std::scoped_lock lock{placeholder_mutex}; AdjustMap(&virtual_offset, &length);
placeholders.subtract({virtual_offset, virtual_offset + length});
}
void* ret = mmap(virtual_base + virtual_offset, length, PROT_READ | PROT_WRITE, // We are removing a placeholder.
MAP_SHARED | MAP_FIXED, fd, host_offset); free_manager.AllocateBlock(virtual_base + virtual_offset, length);
// Deduce mapping protection flags.
int flags = PROT_NONE;
if (True(perms & MemoryPermission::Read)) {
flags |= PROT_READ;
}
if (True(perms & MemoryPermission::Write)) {
flags |= PROT_WRITE;
}
#ifdef ARCHITECTURE_arm64
if (True(perms & MemoryPermission::Execute)) {
flags |= PROT_EXEC;
}
#endif
void* ret = mmap(virtual_base + virtual_offset, length, flags, MAP_SHARED | MAP_FIXED, fd,
host_offset);
ASSERT_MSG(ret != MAP_FAILED, "mmap failed: {}", strerror(errno)); ASSERT_MSG(ret != MAP_FAILED, "mmap failed: {}", strerror(errno));
} }
@ -447,47 +518,54 @@ public:
// The method name is wrong. We're still talking about the virtual range. // The method name is wrong. We're still talking about the virtual range.
// We don't want to unmap, we want to reserve this memory. // We don't want to unmap, we want to reserve this memory.
{ // Intersect the range with our address space.
std::scoped_lock lock{placeholder_mutex}; AdjustMap(&virtual_offset, &length);
auto it = placeholders.find({virtual_offset - 1, virtual_offset + length + 1});
if (it != placeholders.end()) { // Merge with any adjacent placeholder mappings.
size_t prev_upper = virtual_offset + length; auto [merged_pointer, merged_size] =
virtual_offset = std::min(virtual_offset, it->lower()); free_manager.FreeBlock(virtual_base + virtual_offset, length);
length = std::max(it->upper(), prev_upper) - virtual_offset;
}
placeholders.add({virtual_offset, virtual_offset + length}); void* ret = mmap(merged_pointer, merged_size, PROT_NONE,
}
void* ret = mmap(virtual_base + virtual_offset, length, PROT_NONE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0); MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0);
ASSERT_MSG(ret != MAP_FAILED, "mmap failed: {}", strerror(errno)); ASSERT_MSG(ret != MAP_FAILED, "mmap failed: {}", strerror(errno));
} }
void Protect(size_t virtual_offset, size_t length, bool read, bool write) { void Protect(size_t virtual_offset, size_t length, bool read, bool write, bool execute) {
int flags = 0; // Intersect the range with our address space.
AdjustMap(&virtual_offset, &length);
int flags = PROT_NONE;
if (read) { if (read) {
flags |= PROT_READ; flags |= PROT_READ;
} }
if (write) { if (write) {
flags |= PROT_WRITE; flags |= PROT_WRITE;
} }
#ifdef ARCHITECTURE_arm64
if (execute) {
flags |= PROT_EXEC;
}
#endif
int ret = mprotect(virtual_base + virtual_offset, length, flags); int ret = mprotect(virtual_base + virtual_offset, length, flags);
ASSERT_MSG(ret == 0, "mprotect failed: {}", strerror(errno)); ASSERT_MSG(ret == 0, "mprotect failed: {}", strerror(errno));
} }
void EnableDirectMappedAddress() {
virtual_base = nullptr;
}
const size_t backing_size; ///< Size of the backing memory in bytes const size_t backing_size; ///< Size of the backing memory in bytes
const size_t virtual_size; ///< Size of the virtual address placeholder in bytes const size_t virtual_size; ///< Size of the virtual address placeholder in bytes
u8* backing_base{reinterpret_cast<u8*>(MAP_FAILED)}; u8* backing_base{reinterpret_cast<u8*>(MAP_FAILED)};
u8* virtual_base{reinterpret_cast<u8*>(MAP_FAILED)}; u8* virtual_base{reinterpret_cast<u8*>(MAP_FAILED)};
u8* virtual_map_base{reinterpret_cast<u8*>(MAP_FAILED)};
private: private:
/// Release all resources in the object /// Release all resources in the object
void Release() { void Release() {
if (virtual_base != MAP_FAILED) { if (virtual_map_base != MAP_FAILED) {
int ret = munmap(virtual_base, virtual_size); int ret = munmap(virtual_map_base, virtual_size);
ASSERT_MSG(ret == 0, "munmap failed: {}", strerror(errno)); ASSERT_MSG(ret == 0, "munmap failed: {}", strerror(errno));
} }
@ -502,10 +580,29 @@ private:
} }
} }
int fd{-1}; // memfd file descriptor, -1 is the error value of memfd_create void AdjustMap(size_t* virtual_offset, size_t* length) {
if (virtual_base != nullptr) {
return;
}
boost::icl::interval_set<size_t> placeholders; ///< Mapped placeholders // If we are direct mapped, we want to make sure we are operating on a region
std::mutex placeholder_mutex; ///< Mutex for placeholders // that is in range of our virtual mapping.
size_t intended_start = *virtual_offset;
size_t intended_end = intended_start + *length;
size_t address_space_start = reinterpret_cast<size_t>(virtual_map_base);
size_t address_space_end = address_space_start + virtual_size;
if (address_space_start > intended_end || intended_start > address_space_end) {
*virtual_offset = 0;
*length = 0;
} else {
*virtual_offset = std::max(intended_start, address_space_start);
*length = std::min(intended_end, address_space_end) - *virtual_offset;
}
}
int fd{-1}; // memfd file descriptor, -1 is the error value of memfd_create
FreeRegionManager free_manager{};
}; };
#else // ^^^ Linux ^^^ vvv Generic vvv #else // ^^^ Linux ^^^ vvv Generic vvv
@ -518,11 +615,13 @@ public:
throw std::bad_alloc{}; throw std::bad_alloc{};
} }
void Map(size_t virtual_offset, size_t host_offset, size_t length) {} void Map(size_t virtual_offset, size_t host_offset, size_t length, MemoryPermission perm) {}
void Unmap(size_t virtual_offset, size_t length) {} void Unmap(size_t virtual_offset, size_t length) {}
void Protect(size_t virtual_offset, size_t length, bool read, bool write) {} void Protect(size_t virtual_offset, size_t length, bool read, bool write, bool execute) {}
void EnableDirectMappedAddress() {}
u8* backing_base{nullptr}; u8* backing_base{nullptr};
u8* virtual_base{nullptr}; u8* virtual_base{nullptr};
@ -535,15 +634,16 @@ HostMemory::HostMemory(size_t backing_size_, size_t virtual_size_)
try { try {
// Try to allocate a fastmem arena. // Try to allocate a fastmem arena.
// The implementation will fail with std::bad_alloc on errors. // The implementation will fail with std::bad_alloc on errors.
impl = std::make_unique<HostMemory::Impl>(AlignUp(backing_size, PageAlignment), impl =
AlignUp(virtual_size, PageAlignment) + std::make_unique<HostMemory::Impl>(AlignUp(backing_size, PageAlignment),
3 * HugePageSize); AlignUp(virtual_size, PageAlignment) + HugePageSize);
backing_base = impl->backing_base; backing_base = impl->backing_base;
virtual_base = impl->virtual_base; virtual_base = impl->virtual_base;
if (virtual_base) { if (virtual_base) {
virtual_base += 2 * HugePageSize - 1; // Ensure the virtual base is aligned to the L2 block size.
virtual_base -= reinterpret_cast<size_t>(virtual_base) & (HugePageSize - 1); virtual_base = reinterpret_cast<u8*>(
Common::AlignUp(reinterpret_cast<uintptr_t>(virtual_base), HugePageSize));
virtual_base_offset = virtual_base - impl->virtual_base; virtual_base_offset = virtual_base - impl->virtual_base;
} }
@ -562,7 +662,8 @@ 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) {
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);
@ -571,7 +672,7 @@ void HostMemory::Map(size_t virtual_offset, size_t host_offset, size_t length) {
if (length == 0 || !virtual_base || !impl) { if (length == 0 || !virtual_base || !impl) {
return; return;
} }
impl->Map(virtual_offset + virtual_base_offset, host_offset, length); 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) {
@ -584,14 +685,22 @@ 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, bool read, bool write,
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;
} }
impl->Protect(virtual_offset + virtual_base_offset, length, read, write); impl->Protect(virtual_offset + virtual_base_offset, length, read, write, execute);
}
void HostMemory::EnableDirectMappedAddress() {
if (impl) {
impl->EnableDirectMappedAddress();
virtual_size += reinterpret_cast<uintptr_t>(virtual_base);
}
} }
} // namespace Common } // namespace Common

View File

@ -4,11 +4,20 @@
#pragma once #pragma once
#include <memory> #include <memory>
#include "common/common_funcs.h"
#include "common/common_types.h" #include "common/common_types.h"
#include "common/virtual_buffer.h" #include "common/virtual_buffer.h"
namespace Common { namespace Common {
enum class MemoryPermission : u32 {
Read = 1 << 0,
Write = 1 << 1,
ReadWrite = Read | Write,
Execute = 1 << 2,
};
DECLARE_ENUM_FLAG_OPERATORS(MemoryPermission)
/** /**
* A low level linear memory buffer, which supports multiple mappings * A low level linear memory buffer, which supports multiple mappings
* Its purpose is to rebuild a given sparse memory layout, including mirrors. * Its purpose is to rebuild a given sparse memory layout, including mirrors.
@ -31,11 +40,13 @@ 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); void Map(size_t virtual_offset, size_t host_offset, size_t length, MemoryPermission perms);
void Unmap(size_t virtual_offset, size_t length); void Unmap(size_t virtual_offset, size_t length);
void Protect(size_t virtual_offset, size_t length, bool read, bool write); void Protect(size_t virtual_offset, size_t length, bool read, bool write, bool execute = false);
void EnableDirectMappedAddress();
[[nodiscard]] u8* BackingBasePointer() noexcept { [[nodiscard]] u8* BackingBasePointer() noexcept {
return backing_base; return backing_base;

View File

@ -41,6 +41,7 @@ SWITCHABLE(AspectRatio, true);
SWITCHABLE(AstcDecodeMode, true); SWITCHABLE(AstcDecodeMode, true);
SWITCHABLE(AstcRecompression, true); SWITCHABLE(AstcRecompression, true);
SWITCHABLE(AudioMode, true); SWITCHABLE(AudioMode, true);
SWITCHABLE(CpuBackend, true);
SWITCHABLE(CpuAccuracy, true); SWITCHABLE(CpuAccuracy, true);
SWITCHABLE(FullscreenMode, true); SWITCHABLE(FullscreenMode, true);
SWITCHABLE(GpuAccuracy, true); SWITCHABLE(GpuAccuracy, true);
@ -155,6 +156,22 @@ bool IsFastmemEnabled() {
return true; return true;
} }
static bool is_nce_enabled = false;
void SetNceEnabled(bool is_39bit) {
const bool is_nce_selected = values.cpu_backend.GetValue() == CpuBackend::Nce;
is_nce_enabled = IsFastmemEnabled() && is_nce_selected && is_39bit;
if (is_nce_selected && !is_nce_enabled) {
LOG_WARNING(
Common,
"Program does not utilize 39-bit address space, unable to natively execute code");
}
}
bool IsNceEnabled() {
return is_nce_enabled;
}
bool IsDockedMode() { bool IsDockedMode() {
return values.use_docked_mode.GetValue() == Settings::ConsoleMode::Docked; return values.use_docked_mode.GetValue() == Settings::ConsoleMode::Docked;
} }
@ -206,9 +223,9 @@ const char* TranslateCategory(Category category) {
case Category::UiAudio: case Category::UiAudio:
return "UiAudio"; return "UiAudio";
case Category::UiLayout: case Category::UiLayout:
return "UiLayout"; return "UILayout";
case Category::UiGameList: case Category::UiGameList:
return "UiGameList"; return "UIGameList";
case Category::Screenshots: case Category::Screenshots:
return "Screenshots"; return "Screenshots";
case Category::Shortcuts: case Category::Shortcuts:

View File

@ -63,6 +63,7 @@ SWITCHABLE(AspectRatio, true);
SWITCHABLE(AstcDecodeMode, true); SWITCHABLE(AstcDecodeMode, true);
SWITCHABLE(AstcRecompression, true); SWITCHABLE(AstcRecompression, true);
SWITCHABLE(AudioMode, true); SWITCHABLE(AudioMode, true);
SWITCHABLE(CpuBackend, true);
SWITCHABLE(CpuAccuracy, true); SWITCHABLE(CpuAccuracy, true);
SWITCHABLE(FullscreenMode, true); SWITCHABLE(FullscreenMode, true);
SWITCHABLE(GpuAccuracy, true); SWITCHABLE(GpuAccuracy, true);
@ -179,6 +180,14 @@ struct Values {
&use_speed_limit}; &use_speed_limit};
// Cpu // Cpu
SwitchableSetting<CpuBackend, true> cpu_backend{
linkage, CpuBackend::Dynarmic, CpuBackend::Dynarmic,
#ifdef HAS_NCE
CpuBackend::Nce,
#else
CpuBackend::Dynarmic,
#endif
"cpu_backend", Category::Cpu};
SwitchableSetting<CpuAccuracy, true> cpu_accuracy{linkage, CpuAccuracy::Auto, SwitchableSetting<CpuAccuracy, true> cpu_accuracy{linkage, CpuAccuracy::Auto,
CpuAccuracy::Auto, CpuAccuracy::Paranoid, CpuAccuracy::Auto, CpuAccuracy::Paranoid,
"cpu_accuracy", Category::Cpu}; "cpu_accuracy", Category::Cpu};
@ -232,7 +241,11 @@ struct Values {
SwitchableSetting<bool> use_asynchronous_gpu_emulation{ SwitchableSetting<bool> use_asynchronous_gpu_emulation{
linkage, true, "use_asynchronous_gpu_emulation", Category::Renderer}; linkage, true, "use_asynchronous_gpu_emulation", Category::Renderer};
SwitchableSetting<AstcDecodeMode, true> accelerate_astc{linkage, SwitchableSetting<AstcDecodeMode, true> accelerate_astc{linkage,
#ifdef ANDROID
AstcDecodeMode::Cpu,
#else
AstcDecodeMode::Gpu, AstcDecodeMode::Gpu,
#endif
AstcDecodeMode::Cpu, AstcDecodeMode::Cpu,
AstcDecodeMode::CpuAsynchronous, AstcDecodeMode::CpuAsynchronous,
"accelerate_astc", "accelerate_astc",
@ -304,7 +317,11 @@ struct Values {
linkage, 0, "bg_blue", Category::Renderer, Specialization::Default, true, true}; linkage, 0, "bg_blue", Category::Renderer, Specialization::Default, true, true};
SwitchableSetting<GpuAccuracy, true> gpu_accuracy{linkage, SwitchableSetting<GpuAccuracy, true> gpu_accuracy{linkage,
#ifdef ANDROID
GpuAccuracy::Normal,
#else
GpuAccuracy::High, GpuAccuracy::High,
#endif
GpuAccuracy::Normal, GpuAccuracy::Normal,
GpuAccuracy::Extreme, GpuAccuracy::Extreme,
"gpu_accuracy", "gpu_accuracy",
@ -313,20 +330,38 @@ struct Values {
true, true,
true}; true};
GpuAccuracy current_gpu_accuracy{GpuAccuracy::High}; GpuAccuracy current_gpu_accuracy{GpuAccuracy::High};
SwitchableSetting<AnisotropyMode, true> max_anisotropy{ SwitchableSetting<AnisotropyMode, true> max_anisotropy{linkage,
linkage, AnisotropyMode::Automatic, AnisotropyMode::Automatic, AnisotropyMode::X16, #ifdef ANDROID
"max_anisotropy", Category::RendererAdvanced}; AnisotropyMode::Default,
#else
AnisotropyMode::Automatic,
#endif
AnisotropyMode::Automatic,
AnisotropyMode::X16,
"max_anisotropy",
Category::RendererAdvanced};
SwitchableSetting<AstcRecompression, true> astc_recompression{linkage, SwitchableSetting<AstcRecompression, true> astc_recompression{linkage,
AstcRecompression::Uncompressed, AstcRecompression::Uncompressed,
AstcRecompression::Uncompressed, AstcRecompression::Uncompressed,
AstcRecompression::Bc3, AstcRecompression::Bc3,
"astc_recompression", "astc_recompression",
Category::RendererAdvanced}; Category::RendererAdvanced};
SwitchableSetting<bool> async_presentation{linkage, false, "async_presentation", SwitchableSetting<bool> async_presentation{linkage,
Category::RendererAdvanced}; #ifdef ANDROID
true,
#else
false,
#endif
"async_presentation", Category::RendererAdvanced};
SwitchableSetting<bool> renderer_force_max_clock{linkage, false, "force_max_clock", SwitchableSetting<bool> renderer_force_max_clock{linkage, false, "force_max_clock",
Category::RendererAdvanced}; Category::RendererAdvanced};
SwitchableSetting<bool> use_reactive_flushing{linkage, true, "use_reactive_flushing", SwitchableSetting<bool> use_reactive_flushing{linkage,
#ifdef ANDROID
false,
#else
true,
#endif
"use_reactive_flushing",
Category::RendererAdvanced}; Category::RendererAdvanced};
SwitchableSetting<bool> use_asynchronous_shaders{linkage, false, "use_asynchronous_shaders", SwitchableSetting<bool> use_asynchronous_shaders{linkage, false, "use_asynchronous_shaders",
Category::RendererAdvanced}; Category::RendererAdvanced};
@ -358,6 +393,8 @@ struct Values {
Category::RendererDebug}; Category::RendererDebug};
// TODO: remove this once AMDVLK supports VK_EXT_depth_bias_control // TODO: remove this once AMDVLK supports VK_EXT_depth_bias_control
bool renderer_amdvlk_depth_bias_workaround{}; bool renderer_amdvlk_depth_bias_workaround{};
Setting<bool> disable_buffer_reorder{linkage, false, "disable_buffer_reorder",
Category::RendererDebug};
// System // System
SwitchableSetting<Language, true> language_index{linkage, SwitchableSetting<Language, true> language_index{linkage,
@ -390,7 +427,11 @@ struct Values {
Setting<s32> current_user{linkage, 0, "current_user", Category::System}; Setting<s32> current_user{linkage, 0, "current_user", Category::System};
SwitchableSetting<ConsoleMode> use_docked_mode{linkage, SwitchableSetting<ConsoleMode> use_docked_mode{linkage,
#ifdef ANDROID
ConsoleMode::Handheld,
#else
ConsoleMode::Docked, ConsoleMode::Docked,
#endif
"use_docked_mode", "use_docked_mode",
Category::System, Category::System,
Specialization::Radio, Specialization::Radio,
@ -534,6 +575,8 @@ bool IsGPULevelExtreme();
bool IsGPULevelHigh(); bool IsGPULevelHigh();
bool IsFastmemEnabled(); bool IsFastmemEnabled();
void SetNceEnabled(bool is_64bit);
bool IsNceEnabled();
bool IsDockedMode(); bool IsDockedMode();

View File

@ -129,6 +129,8 @@ ENUM(ShaderBackend, Glsl, Glasm, SpirV);
ENUM(GpuAccuracy, Normal, High, Extreme); ENUM(GpuAccuracy, Normal, High, Extreme);
ENUM(CpuBackend, Dynarmic, Nce);
ENUM(CpuAccuracy, Auto, Accurate, Unsafe, Paranoid); ENUM(CpuAccuracy, Auto, Accurate, Unsafe, Paranoid);
ENUM(MemoryLayout, Memory_4Gb, Memory_6Gb, Memory_8Gb); ENUM(MemoryLayout, Memory_4Gb, Memory_6Gb, Memory_8Gb);

View File

@ -0,0 +1,42 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <dlfcn.h>
#include "common/assert.h"
#include "common/dynamic_library.h"
#include "common/scope_exit.h"
#include "common/signal_chain.h"
namespace Common {
template <typename T>
T* LookupLibcSymbol(const char* name) {
#if defined(__BIONIC__)
Common::DynamicLibrary provider("libc.so");
if (!provider.IsOpen()) {
UNREACHABLE_MSG("Failed to open libc!");
}
#else
// For other operating environments, we assume the symbol is not overridden.
const char* base = nullptr;
Common::DynamicLibrary provider(base);
#endif
void* sym = provider.GetSymbolAddress(name);
if (sym == nullptr) {
sym = dlsym(RTLD_DEFAULT, name);
}
if (sym == nullptr) {
UNREACHABLE_MSG("Unable to find symbol {}!", name);
}
return reinterpret_cast<T*>(sym);
}
int SigAction(int signum, const struct sigaction* act, struct sigaction* oldact) {
static auto libc_sigaction = LookupLibcSymbol<decltype(sigaction)>("sigaction");
return libc_sigaction(signum, act, oldact);
}
} // namespace Common

19
src/common/signal_chain.h Normal file
View File

@ -0,0 +1,19 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#ifndef _WIN32
#include <signal.h>
namespace Common {
// Android's ART overrides sigaction with its own wrapper. This is problematic for SIGSEGV
// in particular, because ART's handler accesses tpidr_el0, which conflicts with NCE.
// This extracts the libc symbol and calls it directly.
int SigAction(int signum, const struct sigaction* act, struct sigaction* oldact);
} // namespace Common
#endif

View File

@ -10,7 +10,7 @@
#include "common/x64/rdtsc.h" #include "common/x64/rdtsc.h"
#endif #endif
#if defined(ARCHITECTURE_arm64) && defined(__linux__) #ifdef HAS_NCE
#include "common/arm64/native_clock.h" #include "common/arm64/native_clock.h"
#endif #endif
@ -68,7 +68,7 @@ std::unique_ptr<WallClock> CreateOptimalClock() {
// - Is not more precise than 1 GHz (1ns resolution) // - Is not more precise than 1 GHz (1ns resolution)
return std::make_unique<StandardWallClock>(); return std::make_unique<StandardWallClock>();
} }
#elif defined(ARCHITECTURE_arm64) && defined(__linux__) #elif defined(HAS_NCE)
return std::make_unique<Arm64::NativeClock>(); return std::make_unique<Arm64::NativeClock>();
#else #else
return std::make_unique<StandardWallClock>(); return std::make_unique<StandardWallClock>();

View File

@ -926,6 +926,22 @@ if (ENABLE_WEB_SERVICE)
target_link_libraries(core PRIVATE web_service) target_link_libraries(core PRIVATE web_service)
endif() endif()
if (HAS_NCE)
enable_language(C ASM)
set(CMAKE_ASM_FLAGS "${CFLAGS} -x assembler-with-cpp")
target_sources(core PRIVATE
arm/nce/arm_nce.cpp
arm/nce/arm_nce.h
arm/nce/arm_nce.s
arm/nce/guest_context.h
arm/nce/patch.cpp
arm/nce/patch.h
arm/nce/instructions.h
)
target_link_libraries(core PRIVATE merry::oaknut)
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.h arm/dynarmic/arm_dynarmic.h

View File

@ -201,6 +201,8 @@ void ARM_Interface::Run() {
if (True(hr & HaltReason::DataAbort)) { if (True(hr & HaltReason::DataAbort)) {
if (system.DebuggerEnabled()) { if (system.DebuggerEnabled()) {
system.GetDebugger().NotifyThreadWatchpoint(current_thread, *HaltedWatchpoint()); system.GetDebugger().NotifyThreadWatchpoint(current_thread, *HaltedWatchpoint());
} else {
LogBacktrace();
} }
current_thread->RequestSuspend(SuspendType::Debug); current_thread->RequestSuspend(SuspendType::Debug);
break; break;

View File

@ -81,6 +81,9 @@ public:
// thread context to be 800 bytes in size. // thread context to be 800 bytes in size.
static_assert(sizeof(ThreadContext64) == 0x320); static_assert(sizeof(ThreadContext64) == 0x320);
/// Perform any backend-specific initialization.
virtual void Initialize() {}
/// Runs the CPU until an event happens /// Runs the CPU until an event happens
void Run(); void Run();

View File

@ -0,0 +1,400 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <cinttypes>
#include <memory>
#include "common/signal_chain.h"
#include "core/arm/nce/arm_nce.h"
#include "core/arm/nce/patch.h"
#include "core/core.h"
#include "core/memory.h"
#include "core/hle/kernel/k_process.h"
#include <signal.h>
#include <sys/syscall.h>
#include <unistd.h>
namespace Core {
namespace {
struct sigaction g_orig_action;
// Verify assembly offsets.
using NativeExecutionParameters = Kernel::KThread::NativeExecutionParameters;
static_assert(offsetof(NativeExecutionParameters, native_context) == TpidrEl0NativeContext);
static_assert(offsetof(NativeExecutionParameters, lock) == TpidrEl0Lock);
static_assert(offsetof(NativeExecutionParameters, magic) == TpidrEl0TlsMagic);
fpsimd_context* GetFloatingPointState(mcontext_t& host_ctx) {
_aarch64_ctx* header = reinterpret_cast<_aarch64_ctx*>(&host_ctx.__reserved);
while (header->magic != FPSIMD_MAGIC) {
header = reinterpret_cast<_aarch64_ctx*>(reinterpret_cast<char*>(header) + header->size);
}
return reinterpret_cast<fpsimd_context*>(header);
}
} // namespace
void* ARM_NCE::RestoreGuestContext(void* raw_context) {
// Retrieve the host context.
auto& host_ctx = static_cast<ucontext_t*>(raw_context)->uc_mcontext;
// Thread-local parameters will be located in x9.
auto* tpidr = reinterpret_cast<NativeExecutionParameters*>(host_ctx.regs[9]);
auto* guest_ctx = static_cast<GuestContext*>(tpidr->native_context);
// Retrieve the host floating point state.
auto* fpctx = GetFloatingPointState(host_ctx);
// Save host callee-saved registers.
std::memcpy(guest_ctx->host_ctx.host_saved_vregs.data(), &fpctx->vregs[8],
sizeof(guest_ctx->host_ctx.host_saved_vregs));
std::memcpy(guest_ctx->host_ctx.host_saved_regs.data(), &host_ctx.regs[19],
sizeof(guest_ctx->host_ctx.host_saved_regs));
// Save stack pointer.
guest_ctx->host_ctx.host_sp = host_ctx.sp;
// Restore all guest state except tpidr_el0.
host_ctx.sp = guest_ctx->sp;
host_ctx.pc = guest_ctx->pc;
host_ctx.pstate = guest_ctx->pstate;
fpctx->fpcr = guest_ctx->fpcr;
fpctx->fpsr = guest_ctx->fpsr;
std::memcpy(host_ctx.regs, guest_ctx->cpu_registers.data(), sizeof(host_ctx.regs));
std::memcpy(fpctx->vregs, guest_ctx->vector_registers.data(), sizeof(fpctx->vregs));
// Return the new thread-local storage pointer.
return tpidr;
}
void ARM_NCE::SaveGuestContext(GuestContext* guest_ctx, void* raw_context) {
// Retrieve the host context.
auto& host_ctx = static_cast<ucontext_t*>(raw_context)->uc_mcontext;
// Retrieve the host floating point state.
auto* fpctx = GetFloatingPointState(host_ctx);
// Save all guest registers except tpidr_el0.
std::memcpy(guest_ctx->cpu_registers.data(), host_ctx.regs, sizeof(host_ctx.regs));
std::memcpy(guest_ctx->vector_registers.data(), fpctx->vregs, sizeof(fpctx->vregs));
guest_ctx->fpsr = fpctx->fpsr;
guest_ctx->fpcr = fpctx->fpcr;
guest_ctx->pstate = static_cast<u32>(host_ctx.pstate);
guest_ctx->pc = host_ctx.pc;
guest_ctx->sp = host_ctx.sp;
// Restore stack pointer.
host_ctx.sp = guest_ctx->host_ctx.host_sp;
// Restore host callee-saved registers.
std::memcpy(&host_ctx.regs[19], guest_ctx->host_ctx.host_saved_regs.data(),
sizeof(guest_ctx->host_ctx.host_saved_regs));
std::memcpy(&fpctx->vregs[8], guest_ctx->host_ctx.host_saved_vregs.data(),
sizeof(guest_ctx->host_ctx.host_saved_vregs));
// Return from the call on exit by setting pc to x30.
host_ctx.pc = guest_ctx->host_ctx.host_saved_regs[11];
// Clear esr_el1 and return it.
host_ctx.regs[0] = guest_ctx->esr_el1.exchange(0);
}
bool ARM_NCE::HandleGuestFault(GuestContext* guest_ctx, void* raw_info, void* raw_context) {
auto& host_ctx = static_cast<ucontext_t*>(raw_context)->uc_mcontext;
auto* info = static_cast<siginfo_t*>(raw_info);
// Try to handle an invalid access.
// TODO: handle accesses which split a page?
const Common::ProcessAddress addr =
(reinterpret_cast<u64>(info->si_addr) & ~Memory::YUZU_PAGEMASK);
if (guest_ctx->system->ApplicationMemory().InvalidateNCE(addr, Memory::YUZU_PAGESIZE)) {
// We handled the access successfully and are returning to guest code.
return true;
}
// We can't handle the access, so determine why we crashed.
const bool is_prefetch_abort = host_ctx.pc == reinterpret_cast<u64>(info->si_addr);
// For data aborts, skip the instruction and return to guest code.
// This will allow games to continue in many scenarios where they would otherwise crash.
if (!is_prefetch_abort) {
host_ctx.pc += 4;
return true;
}
// This is a prefetch abort.
guest_ctx->esr_el1.fetch_or(static_cast<u64>(HaltReason::PrefetchAbort));
// Forcibly mark the context as locked. We are still running.
// We may race with SignalInterrupt here:
// - If we lose the race, then SignalInterrupt will send us a signal we are masking,
// and it will do nothing when it is unmasked, as we have already left guest code.
// - If we win the race, then SignalInterrupt will wait for us to unlock first.
auto& thread_params = guest_ctx->parent->running_thread->GetNativeExecutionParameters();
thread_params.lock.store(SpinLockLocked);
// Return to host.
SaveGuestContext(guest_ctx, raw_context);
return false;
}
void ARM_NCE::HandleHostFault(int sig, void* raw_info, void* raw_context) {
return g_orig_action.sa_sigaction(sig, static_cast<siginfo_t*>(raw_info), raw_context);
}
HaltReason ARM_NCE::RunJit() {
// Get the thread parameters.
// TODO: pass the current thread down from ::Run
auto* thread = Kernel::GetCurrentThreadPointer(system.Kernel());
auto* thread_params = &thread->GetNativeExecutionParameters();
{
// Lock our core context.
std::scoped_lock lk{lock};
// We should not be running.
ASSERT(running_thread == nullptr);
// Check if we need to run. If we have already been halted, we are done.
u64 halt = guest_ctx.esr_el1.exchange(0);
if (halt != 0) {
return static_cast<HaltReason>(halt);
}
// Mark that we are running.
running_thread = thread;
// Acquire the lock on the thread parameters.
// This allows us to force synchronization with SignalInterrupt.
LockThreadParameters(thread_params);
}
// Assign current members.
guest_ctx.parent = this;
thread_params->native_context = &guest_ctx;
thread_params->tpidr_el0 = guest_ctx.tpidr_el0;
thread_params->tpidrro_el0 = guest_ctx.tpidrro_el0;
thread_params->is_running = true;
HaltReason halt{};
// TODO: finding and creating the post handler needs to be locked
// to deal with dynamic loading of NROs.
const auto& post_handlers = system.ApplicationProcess()->GetPostHandlers();
if (auto it = post_handlers.find(guest_ctx.pc); it != post_handlers.end()) {
halt = ReturnToRunCodeByTrampoline(thread_params, &guest_ctx, it->second);
} else {
halt = ReturnToRunCodeByExceptionLevelChange(thread_id, thread_params);
}
// Unload members.
// The thread does not change, so we can persist the old reference.
guest_ctx.tpidr_el0 = thread_params->tpidr_el0;
thread_params->native_context = nullptr;
thread_params->is_running = false;
// Unlock the thread parameters.
UnlockThreadParameters(thread_params);
{
// Lock the core context.
std::scoped_lock lk{lock};
// On exit, we no longer have an active thread.
running_thread = nullptr;
}
// Return the halt reason.
return halt;
}
HaltReason ARM_NCE::StepJit() {
return HaltReason::StepThread;
}
u32 ARM_NCE::GetSvcNumber() const {
return guest_ctx.svc_swi;
}
ARM_NCE::ARM_NCE(System& system_, bool uses_wall_clock_, std::size_t core_index_)
: ARM_Interface{system_, uses_wall_clock_}, core_index{core_index_} {
guest_ctx.system = &system_;
}
ARM_NCE::~ARM_NCE() = default;
void ARM_NCE::Initialize() {
thread_id = gettid();
// Setup our signals
static std::once_flag flag;
std::call_once(flag, [] {
using HandlerType = decltype(sigaction::sa_sigaction);
sigset_t signal_mask;
sigemptyset(&signal_mask);
sigaddset(&signal_mask, ReturnToRunCodeByExceptionLevelChangeSignal);
sigaddset(&signal_mask, BreakFromRunCodeSignal);
sigaddset(&signal_mask, GuestFaultSignal);
struct sigaction return_to_run_code_action {};
return_to_run_code_action.sa_flags = SA_SIGINFO | SA_ONSTACK;
return_to_run_code_action.sa_sigaction = reinterpret_cast<HandlerType>(
&ARM_NCE::ReturnToRunCodeByExceptionLevelChangeSignalHandler);
return_to_run_code_action.sa_mask = signal_mask;
Common::SigAction(ReturnToRunCodeByExceptionLevelChangeSignal, &return_to_run_code_action,
nullptr);
struct sigaction break_from_run_code_action {};
break_from_run_code_action.sa_flags = SA_SIGINFO | SA_ONSTACK;
break_from_run_code_action.sa_sigaction =
reinterpret_cast<HandlerType>(&ARM_NCE::BreakFromRunCodeSignalHandler);
break_from_run_code_action.sa_mask = signal_mask;
Common::SigAction(BreakFromRunCodeSignal, &break_from_run_code_action, nullptr);
struct sigaction fault_action {};
fault_action.sa_flags = SA_SIGINFO | SA_ONSTACK | SA_RESTART;
fault_action.sa_sigaction =
reinterpret_cast<HandlerType>(&ARM_NCE::GuestFaultSignalHandler);
fault_action.sa_mask = signal_mask;
Common::SigAction(GuestFaultSignal, &fault_action, &g_orig_action);
// Simplify call for g_orig_action.
// These fields occupy the same space in memory, so this should be a no-op in practice.
if (!(g_orig_action.sa_flags & SA_SIGINFO)) {
g_orig_action.sa_sigaction =
reinterpret_cast<decltype(g_orig_action.sa_sigaction)>(g_orig_action.sa_handler);
}
});
}
void ARM_NCE::SetPC(u64 pc) {
guest_ctx.pc = pc;
}
u64 ARM_NCE::GetPC() const {
return guest_ctx.pc;
}
u64 ARM_NCE::GetSP() const {
return guest_ctx.sp;
}
u64 ARM_NCE::GetReg(int index) const {
return guest_ctx.cpu_registers[index];
}
void ARM_NCE::SetReg(int index, u64 value) {
guest_ctx.cpu_registers[index] = value;
}
u128 ARM_NCE::GetVectorReg(int index) const {
return guest_ctx.vector_registers[index];
}
void ARM_NCE::SetVectorReg(int index, u128 value) {
guest_ctx.vector_registers[index] = value;
}
u32 ARM_NCE::GetPSTATE() const {
return guest_ctx.pstate;
}
void ARM_NCE::SetPSTATE(u32 pstate) {
guest_ctx.pstate = pstate;
}
u64 ARM_NCE::GetTlsAddress() const {
return guest_ctx.tpidrro_el0;
}
void ARM_NCE::SetTlsAddress(u64 address) {
guest_ctx.tpidrro_el0 = address;
}
u64 ARM_NCE::GetTPIDR_EL0() const {
return guest_ctx.tpidr_el0;
}
void ARM_NCE::SetTPIDR_EL0(u64 value) {
guest_ctx.tpidr_el0 = value;
}
void ARM_NCE::SaveContext(ThreadContext64& ctx) const {
ctx.cpu_registers = guest_ctx.cpu_registers;
ctx.sp = guest_ctx.sp;
ctx.pc = guest_ctx.pc;
ctx.pstate = guest_ctx.pstate;
ctx.vector_registers = guest_ctx.vector_registers;
ctx.fpcr = guest_ctx.fpcr;
ctx.fpsr = guest_ctx.fpsr;
ctx.tpidr = guest_ctx.tpidr_el0;
}
void ARM_NCE::LoadContext(const ThreadContext64& ctx) {
guest_ctx.cpu_registers = ctx.cpu_registers;
guest_ctx.sp = ctx.sp;
guest_ctx.pc = ctx.pc;
guest_ctx.pstate = ctx.pstate;
guest_ctx.vector_registers = ctx.vector_registers;
guest_ctx.fpcr = ctx.fpcr;
guest_ctx.fpsr = ctx.fpsr;
guest_ctx.tpidr_el0 = ctx.tpidr;
}
void ARM_NCE::SignalInterrupt() {
// Lock core context.
std::scoped_lock lk{lock};
// Add break loop condition.
guest_ctx.esr_el1.fetch_or(static_cast<u64>(HaltReason::BreakLoop));
// If there is no thread running, we are done.
if (running_thread == nullptr) {
return;
}
// Lock the thread context.
auto* params = &running_thread->GetNativeExecutionParameters();
LockThreadParameters(params);
if (params->is_running) {
// We should signal to the running thread.
// The running thread will unlock the thread context.
syscall(SYS_tkill, thread_id, BreakFromRunCodeSignal);
} else {
// If the thread is no longer running, we have nothing to do.
UnlockThreadParameters(params);
}
}
void ARM_NCE::ClearInterrupt() {
guest_ctx.esr_el1 = {};
}
void ARM_NCE::ClearInstructionCache() {
// TODO: This is not possible to implement correctly on Linux because
// we do not have any access to ic iallu.
// Require accesses to complete.
std::atomic_thread_fence(std::memory_order_seq_cst);
}
void ARM_NCE::InvalidateCacheRange(u64 addr, std::size_t size) {
this->ClearInstructionCache();
}
void ARM_NCE::ClearExclusiveState() {
// No-op.
}
void ARM_NCE::PageTableChanged(Common::PageTable& page_table,
std::size_t new_address_space_size_in_bits) {
// No-op. Page table is never used.
}
} // namespace Core

108
src/core/arm/nce/arm_nce.h Normal file
View File

@ -0,0 +1,108 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <atomic>
#include <memory>
#include <span>
#include <unordered_map>
#include <vector>
#include "core/arm/arm_interface.h"
#include "core/arm/nce/guest_context.h"
namespace Core::Memory {
class Memory;
}
namespace Core {
class System;
class ARM_NCE final : public ARM_Interface {
public:
ARM_NCE(System& system_, bool uses_wall_clock_, std::size_t core_index_);
~ARM_NCE() override;
void Initialize() override;
void SetPC(u64 pc) override;
u64 GetPC() const override;
u64 GetSP() const override;
u64 GetReg(int index) const override;
void SetReg(int index, u64 value) override;
u128 GetVectorReg(int index) const override;
void SetVectorReg(int index, u128 value) override;
u32 GetPSTATE() const override;
void SetPSTATE(u32 pstate) override;
u64 GetTlsAddress() const override;
void SetTlsAddress(u64 address) override;
void SetTPIDR_EL0(u64 value) override;
u64 GetTPIDR_EL0() const override;
Architecture GetArchitecture() const override {
return Architecture::Aarch64;
}
void SaveContext(ThreadContext32& ctx) const override {}
void SaveContext(ThreadContext64& ctx) const override;
void LoadContext(const ThreadContext32& ctx) override {}
void LoadContext(const ThreadContext64& ctx) override;
void SignalInterrupt() override;
void ClearInterrupt() override;
void ClearExclusiveState() override;
void ClearInstructionCache() override;
void InvalidateCacheRange(u64 addr, std::size_t size) override;
void PageTableChanged(Common::PageTable& new_page_table,
std::size_t new_address_space_size_in_bits) override;
protected:
HaltReason RunJit() override;
HaltReason StepJit() override;
u32 GetSvcNumber() const override;
const Kernel::DebugWatchpoint* HaltedWatchpoint() const override {
return nullptr;
}
void RewindBreakpointInstruction() override {}
private:
// Assembly definitions.
static HaltReason ReturnToRunCodeByTrampoline(void* tpidr, GuestContext* ctx,
u64 trampoline_addr);
static HaltReason ReturnToRunCodeByExceptionLevelChange(int tid, void* tpidr);
static void ReturnToRunCodeByExceptionLevelChangeSignalHandler(int sig, void* info,
void* raw_context);
static void BreakFromRunCodeSignalHandler(int sig, void* info, void* raw_context);
static void GuestFaultSignalHandler(int sig, void* info, void* raw_context);
static void LockThreadParameters(void* tpidr);
static void UnlockThreadParameters(void* tpidr);
private:
// C++ implementation functions for assembly definitions.
static void* RestoreGuestContext(void* raw_context);
static void SaveGuestContext(GuestContext* ctx, void* raw_context);
static bool HandleGuestFault(GuestContext* ctx, void* info, void* raw_context);
static void HandleHostFault(int sig, void* info, void* raw_context);
public:
// Members set on initialization.
std::size_t core_index{};
pid_t thread_id{-1};
// Core context.
GuestContext guest_ctx;
// Thread and invalidation info.
std::mutex lock;
Kernel::KThread* running_thread{};
};
} // namespace Core

222
src/core/arm/nce/arm_nce.s Normal file
View File

@ -0,0 +1,222 @@
/* SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project */
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "core/arm/nce/arm_nce_asm_definitions.h"
#define LOAD_IMMEDIATE_32(reg, val) \
mov reg, #(((val) >> 0x00) & 0xFFFF); \
movk reg, #(((val) >> 0x10) & 0xFFFF), lsl #16
/* static HaltReason Core::ARM_NCE::ReturnToRunCodeByTrampoline(void* tpidr, Core::GuestContext* ctx, u64 trampoline_addr) */
.section .text._ZN4Core7ARM_NCE27ReturnToRunCodeByTrampolineEPvPNS_12GuestContextEm, "ax", %progbits
.global _ZN4Core7ARM_NCE27ReturnToRunCodeByTrampolineEPvPNS_12GuestContextEm
.type _ZN4Core7ARM_NCE27ReturnToRunCodeByTrampolineEPvPNS_12GuestContextEm, %function
_ZN4Core7ARM_NCE27ReturnToRunCodeByTrampolineEPvPNS_12GuestContextEm:
/* Back up host sp to x3. */
/* Back up host tpidr_el0 to x4. */
mov x3, sp
mrs x4, tpidr_el0
/* Load guest sp. x5 is used as a scratch register. */
ldr x5, [x1, #(GuestContextSp)]
mov sp, x5
/* Offset GuestContext pointer to the host member. */
add x5, x1, #(GuestContextHostContext)
/* Save original host sp and tpidr_el0 (x3, x4) to host context. */
stp x3, x4, [x5, #(HostContextSpTpidrEl0)]
/* Save all callee-saved host GPRs. */
stp x19, x20, [x5, #(HostContextRegs+0x0)]
stp x21, x22, [x5, #(HostContextRegs+0x10)]
stp x23, x24, [x5, #(HostContextRegs+0x20)]
stp x25, x26, [x5, #(HostContextRegs+0x30)]
stp x27, x28, [x5, #(HostContextRegs+0x40)]
stp x29, x30, [x5, #(HostContextRegs+0x50)]
/* Save all callee-saved host FPRs. */
stp q8, q9, [x5, #(HostContextVregs+0x0)]
stp q10, q11, [x5, #(HostContextVregs+0x20)]
stp q12, q13, [x5, #(HostContextVregs+0x40)]
stp q14, q15, [x5, #(HostContextVregs+0x60)]
/* Load guest tpidr_el0 from argument. */
msr tpidr_el0, x0
/* Tail call the trampoline to restore guest state. */
br x2
/* static HaltReason Core::ARM_NCE::ReturnToRunCodeByExceptionLevelChange(int tid, void* tpidr) */
.section .text._ZN4Core7ARM_NCE37ReturnToRunCodeByExceptionLevelChangeEiPv, "ax", %progbits
.global _ZN4Core7ARM_NCE37ReturnToRunCodeByExceptionLevelChangeEiPv
.type _ZN4Core7ARM_NCE37ReturnToRunCodeByExceptionLevelChangeEiPv, %function
_ZN4Core7ARM_NCE37ReturnToRunCodeByExceptionLevelChangeEiPv:
/* This jumps to the signal handler, which will restore the entire context. */
/* On entry, x0 = thread id, which is already in the right place. */
/* Move tpidr to x9 so it is not trampled. */
mov x9, x1
/* Set up arguments. */
mov x8, #(__NR_tkill)
mov x1, #(ReturnToRunCodeByExceptionLevelChangeSignal)
/* Tail call the signal handler. */
svc #0
/* Block execution from flowing here. */
brk #1000
/* static void Core::ARM_NCE::ReturnToRunCodeByExceptionLevelChangeSignalHandler(int sig, void* info, void* raw_context) */
.section .text._ZN4Core7ARM_NCE50ReturnToRunCodeByExceptionLevelChangeSignalHandlerEiPvS1_, "ax", %progbits
.global _ZN4Core7ARM_NCE50ReturnToRunCodeByExceptionLevelChangeSignalHandlerEiPvS1_
.type _ZN4Core7ARM_NCE50ReturnToRunCodeByExceptionLevelChangeSignalHandlerEiPvS1_, %function
_ZN4Core7ARM_NCE50ReturnToRunCodeByExceptionLevelChangeSignalHandlerEiPvS1_:
stp x29, x30, [sp, #-0x10]!
mov x29, sp
/* Call the context restorer with the raw context. */
mov x0, x2
bl _ZN4Core7ARM_NCE19RestoreGuestContextEPv
/* Save the old value of tpidr_el0. */
mrs x8, tpidr_el0
ldr x9, [x0, #(TpidrEl0NativeContext)]
str x8, [x9, #(GuestContextHostContext + HostContextTpidrEl0)]
/* Set our new tpidr_el0. */
msr tpidr_el0, x0
/* Unlock the context. */
bl _ZN4Core7ARM_NCE22UnlockThreadParametersEPv
/* Returning from here will enter the guest. */
ldp x29, x30, [sp], #0x10
ret
/* static void Core::ARM_NCE::BreakFromRunCodeSignalHandler(int sig, void* info, void* raw_context) */
.section .text._ZN4Core7ARM_NCE29BreakFromRunCodeSignalHandlerEiPvS1_, "ax", %progbits
.global _ZN4Core7ARM_NCE29BreakFromRunCodeSignalHandlerEiPvS1_
.type _ZN4Core7ARM_NCE29BreakFromRunCodeSignalHandlerEiPvS1_, %function
_ZN4Core7ARM_NCE29BreakFromRunCodeSignalHandlerEiPvS1_:
/* Check to see if we have the correct TLS magic. */
mrs x8, tpidr_el0
ldr w9, [x8, #(TpidrEl0TlsMagic)]
LOAD_IMMEDIATE_32(w10, TlsMagic)
cmp w9, w10
b.ne 1f
/* Correct TLS magic, so this is a guest interrupt. */
/* Restore host tpidr_el0. */
ldr x0, [x8, #(TpidrEl0NativeContext)]
ldr x3, [x0, #(GuestContextHostContext + HostContextTpidrEl0)]
msr tpidr_el0, x3
/* Tail call the restorer. */
mov x1, x2
b _ZN4Core7ARM_NCE16SaveGuestContextEPNS_12GuestContextEPv
/* Returning from here will enter host code. */
1:
/* Incorrect TLS magic, so this is a spurious signal. */
ret
/* static void Core::ARM_NCE::GuestFaultSignalHandler(int sig, void* info, void* raw_context) */
.section .text._ZN4Core7ARM_NCE23GuestFaultSignalHandlerEiPvS1_, "ax", %progbits
.global _ZN4Core7ARM_NCE23GuestFaultSignalHandlerEiPvS1_
.type _ZN4Core7ARM_NCE23GuestFaultSignalHandlerEiPvS1_, %function
_ZN4Core7ARM_NCE23GuestFaultSignalHandlerEiPvS1_:
/* Check to see if we have the correct TLS magic. */
mrs x8, tpidr_el0
ldr w9, [x8, #(TpidrEl0TlsMagic)]
LOAD_IMMEDIATE_32(w10, TlsMagic)
cmp w9, w10
b.eq 1f
/* Incorrect TLS magic, so this is a host fault. */
/* Tail call the handler. */
b _ZN4Core7ARM_NCE15HandleHostFaultEiPvS1_
1:
/* Correct TLS magic, so this is a guest fault. */
stp x29, x30, [sp, #-0x20]!
str x19, [sp, #0x10]
mov x29, sp
/* Save the old tpidr_el0. */
mov x19, x8
/* Restore host tpidr_el0. */
ldr x0, [x8, #(TpidrEl0NativeContext)]
ldr x3, [x0, #(GuestContextHostContext + HostContextTpidrEl0)]
msr tpidr_el0, x3
/* Call the handler. */
bl _ZN4Core7ARM_NCE16HandleGuestFaultEPNS_12GuestContextEPvS3_
/* If the handler returned false, we want to preserve the host tpidr_el0. */
cbz x0, 2f
/* Otherwise, restore guest tpidr_el0. */
msr tpidr_el0, x19
2:
ldr x19, [sp, #0x10]
ldp x29, x30, [sp], #0x20
ret
/* static void Core::ARM_NCE::LockThreadParameters(void* tpidr) */
.section .text._ZN4Core7ARM_NCE20LockThreadParametersEPv, "ax", %progbits
.global _ZN4Core7ARM_NCE20LockThreadParametersEPv
.type _ZN4Core7ARM_NCE20LockThreadParametersEPv, %function
_ZN4Core7ARM_NCE20LockThreadParametersEPv:
/* Offset to lock member. */
add x0, x0, #(TpidrEl0Lock)
1:
/* Clear the monitor. */
clrex
2:
/* Load-linked with acquire ordering. */
ldaxr w1, [x0]
/* If the value was SpinLockLocked, clear monitor and retry. */
cbz w1, 1b
/* Store-conditional SpinLockLocked with relaxed ordering. */
stxr w1, wzr, [x0]
/* If we failed to store, retry. */
cbnz w1, 2b
ret
/* static void Core::ARM_NCE::UnlockThreadParameters(void* tpidr) */
.section .text._ZN4Core7ARM_NCE22UnlockThreadParametersEPv, "ax", %progbits
.global _ZN4Core7ARM_NCE22UnlockThreadParametersEPv
.type _ZN4Core7ARM_NCE22UnlockThreadParametersEPv, %function
_ZN4Core7ARM_NCE22UnlockThreadParametersEPv:
/* Offset to lock member. */
add x0, x0, #(TpidrEl0Lock)
/* Load SpinLockUnlocked. */
mov w1, #(SpinLockUnlocked)
/* Store value with release ordering. */
stlr w1, [x0]
ret

View File

@ -0,0 +1,29 @@
/* SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project */
/* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#define __ASSEMBLY__
#include <asm-generic/signal.h>
#include <asm-generic/unistd.h>
#define ReturnToRunCodeByExceptionLevelChangeSignal SIGUSR2
#define BreakFromRunCodeSignal SIGURG
#define GuestFaultSignal SIGSEGV
#define GuestContextSp 0xF8
#define GuestContextHostContext 0x320
#define HostContextSpTpidrEl0 0xE0
#define HostContextTpidrEl0 0xE8
#define HostContextRegs 0x0
#define HostContextVregs 0x60
#define TpidrEl0NativeContext 0x10
#define TpidrEl0Lock 0x18
#define TpidrEl0TlsMagic 0x20
#define TlsMagic 0x555a5559
#define SpinLockLocked 0
#define SpinLockUnlocked 1

View File

@ -0,0 +1,50 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "core/arm/arm_interface.h"
#include "core/arm/nce/arm_nce_asm_definitions.h"
namespace Core {
class ARM_NCE;
class System;
struct HostContext {
alignas(16) std::array<u64, 12> host_saved_regs{};
alignas(16) std::array<u128, 8> host_saved_vregs{};
u64 host_sp{};
void* host_tpidr_el0{};
};
struct GuestContext {
std::array<u64, 31> cpu_registers{};
u64 sp{};
u64 pc{};
u32 fpcr{};
u32 fpsr{};
std::array<u128, 32> vector_registers{};
u32 pstate{};
alignas(16) HostContext host_ctx{};
u64 tpidrro_el0{};
u64 tpidr_el0{};
std::atomic<u64> esr_el1{};
u32 nzcv{};
u32 svc_swi{};
System* system{};
ARM_NCE* parent{};
};
// Verify assembly offsets.
static_assert(offsetof(GuestContext, sp) == GuestContextSp);
static_assert(offsetof(GuestContext, host_ctx) == GuestContextHostContext);
static_assert(offsetof(HostContext, host_sp) == HostContextSpTpidrEl0);
static_assert(offsetof(HostContext, host_tpidr_el0) - 8 == HostContextSpTpidrEl0);
static_assert(offsetof(HostContext, host_tpidr_el0) == HostContextTpidrEl0);
static_assert(offsetof(HostContext, host_saved_regs) == HostContextRegs);
static_assert(offsetof(HostContext, host_saved_vregs) == HostContextVregs);
} // namespace Core

View File

@ -0,0 +1,147 @@
// SPDX-FileCopyrightText: Copyright © 2020 Skyline Team and Contributors
// SPDX-License-Identifier: MPL-2.0
#include "common/bit_field.h"
#include "common/common_types.h"
namespace Core::NCE {
enum SystemRegister : u32 {
TpidrEl0 = 0x5E82,
TpidrroEl0 = 0x5E83,
CntfrqEl0 = 0x5F00,
CntpctEl0 = 0x5F01,
};
// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/SVC--Supervisor-Call-
union SVC {
constexpr explicit SVC(u32 raw_) : raw{raw_} {}
constexpr bool Verify() {
return (this->GetSig0() == 0x1 && this->GetSig1() == 0x6A0);
}
constexpr u32 GetSig0() {
return decltype(sig0)::ExtractValue(raw);
}
constexpr u32 GetValue() {
return decltype(value)::ExtractValue(raw);
}
constexpr u32 GetSig1() {
return decltype(sig1)::ExtractValue(raw);
}
u32 raw;
private:
BitField<0, 5, u32> sig0; // 0x1
BitField<5, 16, u32> value; // 16-bit immediate
BitField<21, 11, u32> sig1; // 0x6A0
};
static_assert(sizeof(SVC) == sizeof(u32));
static_assert(SVC(0xD40000C1).Verify());
static_assert(SVC(0xD40000C1).GetValue() == 0x6);
// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/MRS--Move-System-Register-
union MRS {
constexpr explicit MRS(u32 raw_) : raw{raw_} {}
constexpr bool Verify() {
return (this->GetSig() == 0xD53);
}
constexpr u32 GetRt() {
return decltype(rt)::ExtractValue(raw);
}
constexpr u32 GetSystemReg() {
return decltype(system_reg)::ExtractValue(raw);
}
constexpr u32 GetSig() {
return decltype(sig)::ExtractValue(raw);
}
u32 raw;
private:
BitField<0, 5, u32> rt; // destination register
BitField<5, 15, u32> system_reg; // source system register
BitField<20, 12, u32> sig; // 0xD53
};
static_assert(sizeof(MRS) == sizeof(u32));
static_assert(MRS(0xD53BE020).Verify());
static_assert(MRS(0xD53BE020).GetSystemReg() == CntpctEl0);
static_assert(MRS(0xD53BE020).GetRt() == 0x0);
// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/MSR--register---Move-general-purpose-register-to-System-Register-
union MSR {
constexpr explicit MSR(u32 raw_) : raw{raw_} {}
constexpr bool Verify() {
return this->GetSig() == 0xD51;
}
constexpr u32 GetRt() {
return decltype(rt)::ExtractValue(raw);
}
constexpr u32 GetSystemReg() {
return decltype(system_reg)::ExtractValue(raw);
}
constexpr u32 GetSig() {
return decltype(sig)::ExtractValue(raw);
}
u32 raw;
private:
BitField<0, 5, u32> rt; // source register
BitField<5, 15, u32> system_reg; // destination system register
BitField<20, 12, u32> sig; // 0xD51
};
static_assert(sizeof(MSR) == sizeof(u32));
static_assert(MSR(0xD51BD040).Verify());
static_assert(MSR(0xD51BD040).GetSystemReg() == TpidrEl0);
static_assert(MSR(0xD51BD040).GetRt() == 0x0);
// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/LDXR--Load-Exclusive-Register-
// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/LDXP--Load-Exclusive-Pair-of-Registers-
// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/STXR--Store-Exclusive-Register-
// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/STXP--Store-Exclusive-Pair-of-registers-
union Exclusive {
constexpr explicit Exclusive(u32 raw_) : raw{raw_} {}
constexpr bool Verify() {
return this->GetSig() == 0x10;
}
constexpr u32 GetSig() {
return decltype(sig)::ExtractValue(raw);
}
constexpr u32 AsOrdered() {
return raw | decltype(o0)::FormatValue(1);
}
u32 raw;
private:
BitField<0, 5, u32> rt; // memory operand
BitField<5, 5, u32> rn; // register operand 1
BitField<10, 5, u32> rt2; // register operand 2
BitField<15, 1, u32> o0; // ordered
BitField<16, 5, u32> rs; // status register
BitField<21, 2, u32> l; // operation type
BitField<23, 7, u32> sig; // 0x10
BitField<30, 2, u32> size; // size
};
static_assert(Exclusive(0xC85FFC00).Verify());
static_assert(Exclusive(0xC85FFC00).AsOrdered() == 0xC85FFC00);
static_assert(Exclusive(0xC85F7C00).AsOrdered() == 0xC85FFC00);
static_assert(Exclusive(0xC8200440).AsOrdered() == 0xC8208440);
} // namespace Core::NCE

474
src/core/arm/nce/patch.cpp Normal file
View File

@ -0,0 +1,474 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/arm64/native_clock.h"
#include "common/bit_cast.h"
#include "common/literals.h"
#include "core/arm/nce/arm_nce.h"
#include "core/arm/nce/guest_context.h"
#include "core/arm/nce/instructions.h"
#include "core/arm/nce/patch.h"
#include "core/core.h"
#include "core/core_timing.h"
#include "core/hle/kernel/svc.h"
namespace Core::NCE {
using namespace Common::Literals;
using namespace oaknut::util;
using NativeExecutionParameters = Kernel::KThread::NativeExecutionParameters;
constexpr size_t MaxRelativeBranch = 128_MiB;
constexpr u32 ModuleCodeIndex = 0x24 / sizeof(u32);
Patcher::Patcher() : c(m_patch_instructions) {}
Patcher::~Patcher() = default;
void Patcher::PatchText(const Kernel::PhysicalMemory& program_image,
const Kernel::CodeSet::Segment& code) {
// Write save context helper function.
c.l(m_save_context);
WriteSaveContext();
// Write load context helper function.
c.l(m_load_context);
WriteLoadContext();
// Retrieve text segment data.
const auto text = std::span{program_image}.subspan(code.offset, code.size);
const auto text_words =
std::span<const u32>{reinterpret_cast<const u32*>(text.data()), text.size() / sizeof(u32)};
// Loop through instructions, patching as needed.
for (u32 i = ModuleCodeIndex; i < static_cast<u32>(text_words.size()); i++) {
const u32 inst = text_words[i];
const auto AddRelocations = [&] {
const uintptr_t this_offset = i * sizeof(u32);
const uintptr_t next_offset = this_offset + sizeof(u32);
// Relocate from here to patch.
this->BranchToPatch(this_offset);
// Relocate from patch to next instruction.
return next_offset;
};
// SVC
if (auto svc = SVC{inst}; svc.Verify()) {
WriteSvcTrampoline(AddRelocations(), svc.GetValue());
continue;
}
// MRS Xn, TPIDR_EL0
// MRS Xn, TPIDRRO_EL0
if (auto mrs = MRS{inst};
mrs.Verify() && (mrs.GetSystemReg() == TpidrroEl0 || mrs.GetSystemReg() == TpidrEl0)) {
const auto src_reg = mrs.GetSystemReg() == TpidrroEl0 ? oaknut::SystemReg::TPIDRRO_EL0
: oaknut::SystemReg::TPIDR_EL0;
const auto dest_reg = oaknut::XReg{static_cast<int>(mrs.GetRt())};
WriteMrsHandler(AddRelocations(), dest_reg, src_reg);
continue;
}
// MRS Xn, CNTPCT_EL0
if (auto mrs = MRS{inst}; mrs.Verify() && mrs.GetSystemReg() == CntpctEl0) {
WriteCntpctHandler(AddRelocations(), oaknut::XReg{static_cast<int>(mrs.GetRt())});
continue;
}
// MRS Xn, CNTFRQ_EL0
if (auto mrs = MRS{inst}; mrs.Verify() && mrs.GetSystemReg() == CntfrqEl0) {
UNREACHABLE();
}
// MSR TPIDR_EL0, Xn
if (auto msr = MSR{inst}; msr.Verify() && msr.GetSystemReg() == TpidrEl0) {
WriteMsrHandler(AddRelocations(), oaknut::XReg{static_cast<int>(msr.GetRt())});
continue;
}
if (auto exclusive = Exclusive{inst}; exclusive.Verify()) {
m_exclusives.push_back(i);
}
}
// Determine patching mode for the final relocation step
const size_t image_size = program_image.size();
this->mode = image_size > MaxRelativeBranch ? PatchMode::PreText : PatchMode::PostData;
}
void Patcher::RelocateAndCopy(Common::ProcessAddress load_base,
const Kernel::CodeSet::Segment& code,
Kernel::PhysicalMemory& program_image,
EntryTrampolines* out_trampolines) {
const size_t patch_size = GetSectionSize();
const size_t image_size = program_image.size();
// Retrieve text segment data.
const auto text = std::span{program_image}.subspan(code.offset, code.size);
const auto text_words =
std::span<u32>{reinterpret_cast<u32*>(text.data()), text.size() / sizeof(u32)};
const auto ApplyBranchToPatchRelocation = [&](u32* target, const Relocation& rel) {
oaknut::CodeGenerator rc{target};
if (mode == PatchMode::PreText) {
rc.B(rel.patch_offset - patch_size - rel.module_offset);
} else {
rc.B(image_size - rel.module_offset + rel.patch_offset);
}
};
const auto ApplyBranchToModuleRelocation = [&](u32* target, const Relocation& rel) {
oaknut::CodeGenerator rc{target};
if (mode == PatchMode::PreText) {
rc.B(patch_size - rel.patch_offset + rel.module_offset);
} else {
rc.B(rel.module_offset - image_size - rel.patch_offset);
}
};
const auto RebasePatch = [&](ptrdiff_t patch_offset) {
if (mode == PatchMode::PreText) {
return GetInteger(load_base) + patch_offset;
} else {
return GetInteger(load_base) + image_size + patch_offset;
}
};
const auto RebasePc = [&](uintptr_t module_offset) {
if (mode == PatchMode::PreText) {
return GetInteger(load_base) + patch_size + module_offset;
} else {
return GetInteger(load_base) + module_offset;
}
};
// We are now ready to relocate!
for (const Relocation& rel : m_branch_to_patch_relocations) {
ApplyBranchToPatchRelocation(text_words.data() + rel.module_offset / sizeof(u32), rel);
}
for (const Relocation& rel : m_branch_to_module_relocations) {
ApplyBranchToModuleRelocation(m_patch_instructions.data() + rel.patch_offset / sizeof(u32),
rel);
}
// Rewrite PC constants and record post trampolines
for (const Relocation& rel : m_write_module_pc_relocations) {
oaknut::CodeGenerator rc{m_patch_instructions.data() + rel.patch_offset / sizeof(u32)};
rc.dx(RebasePc(rel.module_offset));
}
for (const Trampoline& rel : m_trampolines) {
out_trampolines->insert({RebasePc(rel.module_offset), RebasePatch(rel.patch_offset)});
}
// Cortex-A57 seems to treat all exclusives as ordered, but newer processors do not.
// Convert to ordered to preserve this assumption.
for (const ModuleTextAddress i : m_exclusives) {
auto exclusive = Exclusive{text_words[i]};
text_words[i] = exclusive.AsOrdered();
}
// Copy to program image
if (this->mode == PatchMode::PreText) {
std::memcpy(program_image.data(), m_patch_instructions.data(),
m_patch_instructions.size() * sizeof(u32));
} else {
program_image.resize(image_size + patch_size);
std::memcpy(program_image.data() + image_size, m_patch_instructions.data(),
m_patch_instructions.size() * sizeof(u32));
}
}
size_t Patcher::GetSectionSize() const noexcept {
return Common::AlignUp(m_patch_instructions.size() * sizeof(u32), Core::Memory::YUZU_PAGESIZE);
}
void Patcher::WriteLoadContext() {
// This function was called, which modifies X30, so use that as a scratch register.
// SP contains the guest X30, so save our return X30 to SP + 8, since we have allocated 16 bytes
// of stack.
c.STR(X30, SP, 8);
c.MRS(X30, oaknut::SystemReg::TPIDR_EL0);
c.LDR(X30, X30, offsetof(NativeExecutionParameters, native_context));
// Load system registers.
c.LDR(W0, X30, offsetof(GuestContext, fpsr));
c.MSR(oaknut::SystemReg::FPSR, X0);
c.LDR(W0, X30, offsetof(GuestContext, fpcr));
c.MSR(oaknut::SystemReg::FPCR, X0);
c.LDR(W0, X30, offsetof(GuestContext, nzcv));
c.MSR(oaknut::SystemReg::NZCV, X0);
// Load all vector registers.
static constexpr size_t VEC_OFF = offsetof(GuestContext, vector_registers);
for (int i = 0; i <= 30; i += 2) {
c.LDP(oaknut::QReg{i}, oaknut::QReg{i + 1}, X30, VEC_OFF + 16 * i);
}
// Load all general-purpose registers except X30.
for (int i = 0; i <= 28; i += 2) {
c.LDP(oaknut::XReg{i}, oaknut::XReg{i + 1}, X30, 8 * i);
}
// Reload our return X30 from the stack and return.
// The patch code will reload the guest X30 for us.
c.LDR(X30, SP, 8);
c.RET();
}
void Patcher::WriteSaveContext() {
// This function was called, which modifies X30, so use that as a scratch register.
// SP contains the guest X30, so save our X30 to SP + 8, since we have allocated 16 bytes of
// stack.
c.STR(X30, SP, 8);
c.MRS(X30, oaknut::SystemReg::TPIDR_EL0);
c.LDR(X30, X30, offsetof(NativeExecutionParameters, native_context));
// Store all general-purpose registers except X30.
for (int i = 0; i <= 28; i += 2) {
c.STP(oaknut::XReg{i}, oaknut::XReg{i + 1}, X30, 8 * i);
}
// Store all vector registers.
static constexpr size_t VEC_OFF = offsetof(GuestContext, vector_registers);
for (int i = 0; i <= 30; i += 2) {
c.STP(oaknut::QReg{i}, oaknut::QReg{i + 1}, X30, VEC_OFF + 16 * i);
}
// Store guest system registers, X30 and SP, using X0 as a scratch register.
c.STR(X0, SP, PRE_INDEXED, -16);
c.LDR(X0, SP, 16);
c.STR(X0, X30, 8 * 30);
c.ADD(X0, SP, 32);
c.STR(X0, X30, offsetof(GuestContext, sp));
c.MRS(X0, oaknut::SystemReg::FPSR);
c.STR(W0, X30, offsetof(GuestContext, fpsr));
c.MRS(X0, oaknut::SystemReg::FPCR);
c.STR(W0, X30, offsetof(GuestContext, fpcr));
c.MRS(X0, oaknut::SystemReg::NZCV);
c.STR(W0, X30, offsetof(GuestContext, nzcv));
c.LDR(X0, SP, POST_INDEXED, 16);
// Reload our return X30 from the stack, and return.
c.LDR(X30, SP, 8);
c.RET();
}
void Patcher::WriteSvcTrampoline(ModuleDestLabel module_dest, u32 svc_id) {
// We are about to start saving state, so we need to lock the context.
this->LockContext();
// Store guest X30 to the stack. Then, save the context and restore the stack.
// This will save all registers except PC, but we know PC at patch time.
c.STR(X30, SP, PRE_INDEXED, -16);
c.BL(m_save_context);
c.LDR(X30, SP, POST_INDEXED, 16);
// Now that we've saved all registers, we can use any registers as scratch.
// Store PC + 4 to arm interface, since we know the instruction offset from the entry point.
oaknut::Label pc_after_svc;
c.MRS(X1, oaknut::SystemReg::TPIDR_EL0);
c.LDR(X1, X1, offsetof(NativeExecutionParameters, native_context));
c.LDR(X2, pc_after_svc);
c.STR(X2, X1, offsetof(GuestContext, pc));
// Store SVC number to execute when we return
c.MOV(X2, svc_id);
c.STR(W2, X1, offsetof(GuestContext, svc_swi));
// We are calling a SVC. Clear esr_el1 and return it.
static_assert(std::is_same_v<std::underlying_type_t<HaltReason>, u64>);
oaknut::Label retry;
c.ADD(X2, X1, offsetof(GuestContext, esr_el1));
c.l(retry);
c.LDAXR(X0, X2);
c.STLXR(W3, XZR, X2);
c.CBNZ(W3, retry);
// Add "calling SVC" flag. Since this is X0, this is now our return value.
c.ORR(X0, X0, static_cast<u64>(HaltReason::SupervisorCall));
// Offset the GuestContext pointer to the HostContext member.
// STP has limited range of [-512, 504] which we can't reach otherwise
// NB: Due to this all offsets below are from the start of HostContext.
c.ADD(X1, X1, offsetof(GuestContext, host_ctx));
// Reload host TPIDR_EL0 and SP.
static_assert(offsetof(HostContext, host_sp) + 8 == offsetof(HostContext, host_tpidr_el0));
c.LDP(X2, X3, X1, offsetof(HostContext, host_sp));
c.MOV(SP, X2);
c.MSR(oaknut::SystemReg::TPIDR_EL0, X3);
// Load callee-saved host registers and return to host.
static constexpr size_t HOST_REGS_OFF = offsetof(HostContext, host_saved_regs);
static constexpr size_t HOST_VREGS_OFF = offsetof(HostContext, host_saved_vregs);
c.LDP(X19, X20, X1, HOST_REGS_OFF);
c.LDP(X21, X22, X1, HOST_REGS_OFF + 2 * sizeof(u64));
c.LDP(X23, X24, X1, HOST_REGS_OFF + 4 * sizeof(u64));
c.LDP(X25, X26, X1, HOST_REGS_OFF + 6 * sizeof(u64));
c.LDP(X27, X28, X1, HOST_REGS_OFF + 8 * sizeof(u64));
c.LDP(X29, X30, X1, HOST_REGS_OFF + 10 * sizeof(u64));
c.LDP(Q8, Q9, X1, HOST_VREGS_OFF);
c.LDP(Q10, Q11, X1, HOST_VREGS_OFF + 2 * sizeof(u128));
c.LDP(Q12, Q13, X1, HOST_VREGS_OFF + 4 * sizeof(u128));
c.LDP(Q14, Q15, X1, HOST_VREGS_OFF + 6 * sizeof(u128));
c.RET();
// Write the post-SVC trampoline address, which will jump back to the guest after restoring its
// state.
m_trampolines.push_back({c.offset(), module_dest});
// Host called this location. Save the return address so we can
// unwind the stack properly when jumping back.
c.MRS(X2, oaknut::SystemReg::TPIDR_EL0);
c.LDR(X2, X2, offsetof(NativeExecutionParameters, native_context));
c.ADD(X0, X2, offsetof(GuestContext, host_ctx));
c.STR(X30, X0, offsetof(HostContext, host_saved_regs) + 11 * sizeof(u64));
// Reload all guest registers except X30 and PC.
// The function also expects 16 bytes of stack already allocated.
c.STR(X30, SP, PRE_INDEXED, -16);
c.BL(m_load_context);
c.LDR(X30, SP, POST_INDEXED, 16);
// Use X1 as a scratch register to restore X30.
c.STR(X1, SP, PRE_INDEXED, -16);
c.MRS(X1, oaknut::SystemReg::TPIDR_EL0);
c.LDR(X1, X1, offsetof(NativeExecutionParameters, native_context));
c.LDR(X30, X1, offsetof(GuestContext, cpu_registers) + sizeof(u64) * 30);
c.LDR(X1, SP, POST_INDEXED, 16);
// Unlock the context.
this->UnlockContext();
// Jump back to the instruction after the emulated SVC.
this->BranchToModule(module_dest);
// Store PC after call.
c.l(pc_after_svc);
this->WriteModulePc(module_dest);
}
void Patcher::WriteMrsHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg,
oaknut::SystemReg src_reg) {
// Retrieve emulated TLS register from GuestContext.
c.MRS(dest_reg, oaknut::SystemReg::TPIDR_EL0);
if (src_reg == oaknut::SystemReg::TPIDRRO_EL0) {
c.LDR(dest_reg, dest_reg, offsetof(NativeExecutionParameters, tpidrro_el0));
} else {
c.LDR(dest_reg, dest_reg, offsetof(NativeExecutionParameters, tpidr_el0));
}
// Jump back to the instruction after the emulated MRS.
this->BranchToModule(module_dest);
}
void Patcher::WriteMsrHandler(ModuleDestLabel module_dest, oaknut::XReg src_reg) {
const auto scratch_reg = src_reg.index() == 0 ? X1 : X0;
c.STR(scratch_reg, SP, PRE_INDEXED, -16);
// Save guest value to NativeExecutionParameters::tpidr_el0.
c.MRS(scratch_reg, oaknut::SystemReg::TPIDR_EL0);
c.STR(src_reg, scratch_reg, offsetof(NativeExecutionParameters, tpidr_el0));
// Restore scratch register.
c.LDR(scratch_reg, SP, POST_INDEXED, 16);
// Jump back to the instruction after the emulated MSR.
this->BranchToModule(module_dest);
}
void Patcher::WriteCntpctHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg) {
static Common::Arm64::NativeClock clock{};
const auto factor = clock.GetGuestCNTFRQFactor();
const auto raw_factor = Common::BitCast<std::array<u64, 2>>(factor);
const auto use_x2_x3 = dest_reg.index() == 0 || dest_reg.index() == 1;
oaknut::XReg scratch0 = use_x2_x3 ? X2 : X0;
oaknut::XReg scratch1 = use_x2_x3 ? X3 : X1;
oaknut::Label factorlo;
oaknut::Label factorhi;
// Save scratches.
c.STP(scratch0, scratch1, SP, PRE_INDEXED, -16);
// Load counter value.
c.MRS(dest_reg, oaknut::SystemReg::CNTVCT_EL0);
// Load scaling factor.
c.LDR(scratch0, factorlo);
c.LDR(scratch1, factorhi);
// Multiply low bits and get result.
c.UMULH(scratch0, dest_reg, scratch0);
// Multiply high bits and add low bit result.
c.MADD(dest_reg, dest_reg, scratch1, scratch0);
// Reload scratches.
c.LDP(scratch0, scratch1, SP, POST_INDEXED, 16);
// Jump back to the instruction after the emulated MRS.
this->BranchToModule(module_dest);
// Scaling factor constant values.
c.l(factorlo);
c.dx(raw_factor[0]);
c.l(factorhi);
c.dx(raw_factor[1]);
}
void Patcher::LockContext() {
oaknut::Label retry;
// Save scratches.
c.STP(X0, X1, SP, PRE_INDEXED, -16);
// Reload lock pointer.
c.l(retry);
c.CLREX();
c.MRS(X0, oaknut::SystemReg::TPIDR_EL0);
c.ADD(X0, X0, offsetof(NativeExecutionParameters, lock));
static_assert(SpinLockLocked == 0);
// Load-linked with acquire ordering.
c.LDAXR(W1, X0);
// If the value was SpinLockLocked, clear monitor and retry.
c.CBZ(W1, retry);
// Store-conditional SpinLockLocked with relaxed ordering.
c.STXR(W1, WZR, X0);
// If we failed to store, retry.
c.CBNZ(W1, retry);
// We succeeded! Reload scratches.
c.LDP(X0, X1, SP, POST_INDEXED, 16);
}
void Patcher::UnlockContext() {
// Save scratches.
c.STP(X0, X1, SP, PRE_INDEXED, -16);
// Load lock pointer.
c.MRS(X0, oaknut::SystemReg::TPIDR_EL0);
c.ADD(X0, X0, offsetof(NativeExecutionParameters, lock));
// Load SpinLockUnlocked.
c.MOV(W1, SpinLockUnlocked);
// Store value with release ordering.
c.STLR(W1, X0);
// Load scratches.
c.LDP(X0, X1, SP, POST_INDEXED, 16);
}
} // namespace Core::NCE

98
src/core/arm/nce/patch.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 <span>
#include <unordered_map>
#include <vector>
#include <oaknut/code_block.hpp>
#include <oaknut/oaknut.hpp>
#include "common/common_types.h"
#include "core/hle/kernel/code_set.h"
#include "core/hle/kernel/k_typed_address.h"
#include "core/hle/kernel/physical_memory.h"
namespace Core::NCE {
enum class PatchMode : u32 {
None,
PreText, ///< Patch section is inserted before .text
PostData, ///< Patch section is inserted after .data
};
using ModuleTextAddress = u64;
using PatchTextAddress = u64;
using EntryTrampolines = std::unordered_map<ModuleTextAddress, PatchTextAddress>;
class Patcher {
public:
explicit Patcher();
~Patcher();
void PatchText(const Kernel::PhysicalMemory& program_image,
const Kernel::CodeSet::Segment& code);
void RelocateAndCopy(Common::ProcessAddress load_base, const Kernel::CodeSet::Segment& code,
Kernel::PhysicalMemory& program_image, EntryTrampolines* out_trampolines);
size_t GetSectionSize() const noexcept;
[[nodiscard]] PatchMode GetPatchMode() const noexcept {
return mode;
}
private:
using ModuleDestLabel = uintptr_t;
struct Trampoline {
ptrdiff_t patch_offset;
uintptr_t module_offset;
};
void WriteLoadContext();
void WriteSaveContext();
void LockContext();
void UnlockContext();
void WriteSvcTrampoline(ModuleDestLabel module_dest, u32 svc_id);
void WriteMrsHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg,
oaknut::SystemReg src_reg);
void WriteMsrHandler(ModuleDestLabel module_dest, oaknut::XReg src_reg);
void WriteCntpctHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg);
private:
void BranchToPatch(uintptr_t module_dest) {
m_branch_to_patch_relocations.push_back({c.offset(), module_dest});
}
void BranchToModule(uintptr_t module_dest) {
m_branch_to_module_relocations.push_back({c.offset(), module_dest});
c.dw(0);
}
void WriteModulePc(uintptr_t module_dest) {
m_write_module_pc_relocations.push_back({c.offset(), module_dest});
c.dx(0);
}
private:
// List of patch instructions we have generated.
std::vector<u32> m_patch_instructions{};
// Relocation type for relative branch from module to patch.
struct Relocation {
ptrdiff_t patch_offset; ///< Offset in bytes from the start of the patch section.
uintptr_t module_offset; ///< Offset in bytes from the start of the text section.
};
oaknut::VectorCodeGenerator c;
std::vector<Trampoline> m_trampolines;
std::vector<Relocation> m_branch_to_patch_relocations{};
std::vector<Relocation> m_branch_to_module_relocations{};
std::vector<Relocation> m_write_module_pc_relocations{};
std::vector<ModuleTextAddress> m_exclusives{};
oaknut::Label m_save_context{};
oaknut::Label m_load_context{};
PatchMode mode{PatchMode::None};
};
} // namespace Core::NCE

View File

@ -211,6 +211,8 @@ void CpuManager::RunThread(std::stop_token token, std::size_t core) {
system.GPU().ObtainContext(); system.GPU().ObtainContext();
} }
system.ArmInterface(core).Initialize();
auto& kernel = system.Kernel(); auto& kernel = system.Kernel();
auto& scheduler = *kernel.CurrentScheduler(); auto& scheduler = *kernel.CurrentScheduler();
auto* thread = scheduler.GetSchedulerCurrentThread(); auto* thread = scheduler.GetSchedulerCurrentThread();

View File

@ -6,7 +6,7 @@
namespace Core { namespace Core {
#ifdef ANDROID #ifdef HAS_NCE
constexpr size_t VirtualReserveSize = 1ULL << 38; constexpr size_t VirtualReserveSize = 1ULL << 38;
#else #else
constexpr size_t VirtualReserveSize = 1ULL << 39; constexpr size_t VirtualReserveSize = 1ULL << 39;
@ -15,6 +15,7 @@ constexpr size_t VirtualReserveSize = 1ULL << 39;
DeviceMemory::DeviceMemory() DeviceMemory::DeviceMemory()
: buffer{Kernel::Board::Nintendo::Nx::KSystemControl::Init::GetIntendedMemorySize(), : buffer{Kernel::Board::Nintendo::Nx::KSystemControl::Init::GetIntendedMemorySize(),
VirtualReserveSize} {} VirtualReserveSize} {}
DeviceMemory::~DeviceMemory() = default; DeviceMemory::~DeviceMemory() = default;
} // namespace Core } // namespace Core

View File

@ -167,6 +167,11 @@ protected:
*/ */
std::pair<f32, f32> MapToTouchScreen(u32 framebuffer_x, u32 framebuffer_y) const; std::pair<f32, f32> MapToTouchScreen(u32 framebuffer_x, u32 framebuffer_y) const;
/**
* Clip the provided coordinates to be inside the touchscreen area.
*/
std::pair<u32, u32> ClipToTouchScreen(u32 new_x, u32 new_y) const;
WindowSystemInfo window_info; WindowSystemInfo window_info;
bool strict_context_required = false; bool strict_context_required = false;
@ -181,11 +186,6 @@ private:
// By default, ignore this request and do nothing. // By default, ignore this request and do nothing.
} }
/**
* Clip the provided coordinates to be inside the touchscreen area.
*/
std::pair<u32, u32> ClipToTouchScreen(u32 new_x, u32 new_y) const;
Layout::FramebufferLayout framebuffer_layout; ///< Current framebuffer layout Layout::FramebufferLayout framebuffer_layout; ///< Current framebuffer layout
u32 client_area_width; ///< Current client width, should be set by window impl. u32 client_area_width; ///< Current client width, should be set by window impl.

View File

@ -509,9 +509,11 @@ void EmulatedController::ReloadInput() {
}); });
} }
turbo_button_state = 0; turbo_button_state = 0;
is_initalized = true;
} }
void EmulatedController::UnloadInput() { void EmulatedController::UnloadInput() {
is_initalized = false;
for (auto& button : button_devices) { for (auto& button : button_devices) {
button.reset(); button.reset();
} }
@ -1207,6 +1209,9 @@ void EmulatedController::SetNfc(const Common::Input::CallbackStatus& callback) {
} }
bool EmulatedController::SetVibration(std::size_t device_index, VibrationValue vibration) { bool EmulatedController::SetVibration(std::size_t device_index, VibrationValue vibration) {
if (!is_initalized) {
return false;
}
if (device_index >= output_devices.size()) { if (device_index >= output_devices.size()) {
return false; return false;
} }
@ -1242,6 +1247,10 @@ bool EmulatedController::IsVibrationEnabled(std::size_t device_index) {
const auto player_index = Service::HID::NpadIdTypeToIndex(npad_id_type); const auto player_index = Service::HID::NpadIdTypeToIndex(npad_id_type);
const auto& player = Settings::values.players.GetValue()[player_index]; const auto& player = Settings::values.players.GetValue()[player_index];
if (!is_initalized) {
return false;
}
if (!player.vibration_enabled) { if (!player.vibration_enabled) {
return false; return false;
} }
@ -1261,6 +1270,10 @@ Common::Input::DriverResult EmulatedController::SetPollingMode(
EmulatedDeviceIndex device_index, Common::Input::PollingMode polling_mode) { EmulatedDeviceIndex device_index, Common::Input::PollingMode polling_mode) {
LOG_INFO(Service_HID, "Set polling mode {}, device_index={}", polling_mode, device_index); LOG_INFO(Service_HID, "Set polling mode {}, device_index={}", polling_mode, device_index);
if (!is_initalized) {
return Common::Input::DriverResult::InvalidHandle;
}
auto& left_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Left)]; auto& left_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Left)];
auto& right_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)]; auto& right_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
auto& nfc_output_device = output_devices[3]; auto& nfc_output_device = output_devices[3];
@ -1306,6 +1319,10 @@ bool EmulatedController::SetCameraFormat(
Core::IrSensor::ImageTransferProcessorFormat camera_format) { Core::IrSensor::ImageTransferProcessorFormat camera_format) {
LOG_INFO(Service_HID, "Set camera format {}", camera_format); LOG_INFO(Service_HID, "Set camera format {}", camera_format);
if (!is_initalized) {
return false;
}
auto& right_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)]; auto& right_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
auto& camera_output_device = output_devices[2]; auto& camera_output_device = output_devices[2];
@ -1329,6 +1346,11 @@ void EmulatedController::SetRingParam(Common::ParamPackage param) {
} }
bool EmulatedController::HasNfc() const { bool EmulatedController::HasNfc() const {
if (!is_initalized) {
return false;
}
const auto& nfc_output_device = output_devices[3]; const auto& nfc_output_device = output_devices[3];
switch (npad_type) { switch (npad_type) {
@ -1366,6 +1388,10 @@ bool EmulatedController::RemoveNfcHandle() {
} }
bool EmulatedController::StartNfcPolling() { bool EmulatedController::StartNfcPolling() {
if (!is_initalized) {
return false;
}
auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)]; auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
auto& nfc_virtual_output_device = output_devices[3]; auto& nfc_virtual_output_device = output_devices[3];
@ -1377,6 +1403,10 @@ bool EmulatedController::StartNfcPolling() {
} }
bool EmulatedController::StopNfcPolling() { bool EmulatedController::StopNfcPolling() {
if (!is_initalized) {
return false;
}
auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)]; auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
auto& nfc_virtual_output_device = output_devices[3]; auto& nfc_virtual_output_device = output_devices[3];
@ -1388,6 +1418,10 @@ bool EmulatedController::StopNfcPolling() {
} }
bool EmulatedController::ReadAmiiboData(std::vector<u8>& data) { bool EmulatedController::ReadAmiiboData(std::vector<u8>& data) {
if (!is_initalized) {
return false;
}
auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)]; auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
auto& nfc_virtual_output_device = output_devices[3]; auto& nfc_virtual_output_device = output_devices[3];
@ -1400,6 +1434,10 @@ bool EmulatedController::ReadAmiiboData(std::vector<u8>& data) {
bool EmulatedController::ReadMifareData(const Common::Input::MifareRequest& request, bool EmulatedController::ReadMifareData(const Common::Input::MifareRequest& request,
Common::Input::MifareRequest& out_data) { Common::Input::MifareRequest& out_data) {
if (!is_initalized) {
return false;
}
auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)]; auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
auto& nfc_virtual_output_device = output_devices[3]; auto& nfc_virtual_output_device = output_devices[3];
@ -1412,6 +1450,10 @@ bool EmulatedController::ReadMifareData(const Common::Input::MifareRequest& requ
} }
bool EmulatedController::WriteMifareData(const Common::Input::MifareRequest& request) { bool EmulatedController::WriteMifareData(const Common::Input::MifareRequest& request) {
if (!is_initalized) {
return false;
}
auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)]; auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
auto& nfc_virtual_output_device = output_devices[3]; auto& nfc_virtual_output_device = output_devices[3];
@ -1423,6 +1465,10 @@ bool EmulatedController::WriteMifareData(const Common::Input::MifareRequest& req
} }
bool EmulatedController::WriteNfc(const std::vector<u8>& data) { bool EmulatedController::WriteNfc(const std::vector<u8>& data) {
if (!is_initalized) {
return false;
}
auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)]; auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
auto& nfc_virtual_output_device = output_devices[3]; auto& nfc_virtual_output_device = output_devices[3];
@ -1434,6 +1480,10 @@ bool EmulatedController::WriteNfc(const std::vector<u8>& data) {
} }
void EmulatedController::SetLedPattern() { void EmulatedController::SetLedPattern() {
if (!is_initalized) {
return;
}
for (auto& device : output_devices) { for (auto& device : output_devices) {
if (!device) { if (!device) {
continue; continue;

View File

@ -559,6 +559,7 @@ private:
NpadStyleTag supported_style_tag{NpadStyleSet::All}; NpadStyleTag supported_style_tag{NpadStyleSet::All};
bool is_connected{false}; bool is_connected{false};
bool is_configuring{false}; bool is_configuring{false};
bool is_initalized{false};
bool system_buttons_enabled{true}; bool system_buttons_enabled{true};
f32 motion_sensitivity{Core::HID::MotionInput::IsAtRestStandard}; f32 motion_sensitivity{Core::HID::MotionInput::IsAtRestStandard};
u32 turbo_button_state{0}; u32 turbo_button_state{0};

View File

@ -75,12 +75,26 @@ struct CodeSet final {
return segments[2]; return segments[2];
} }
#ifdef HAS_NCE
Segment& PatchSegment() {
return patch_segment;
}
const Segment& PatchSegment() const {
return patch_segment;
}
#endif
/// The overall data that backs this code set. /// The overall data that backs this code set.
Kernel::PhysicalMemory memory; Kernel::PhysicalMemory memory;
/// The segments that comprise this code set. /// The segments that comprise this code set.
std::array<Segment, 3> segments; std::array<Segment, 3> segments;
#ifdef HAS_NCE
Segment patch_segment;
#endif
/// The entry point address for this code set. /// The entry point address for this code set.
KProcessAddress entrypoint = 0; KProcessAddress entrypoint = 0;
}; };

View File

@ -25,8 +25,8 @@ constexpr std::array<KAddressSpaceInfo, 13> AddressSpaceInfos{{
{ .bit_width = 36, .address = 2_GiB , .size = 64_GiB - 2_GiB , .type = KAddressSpaceInfo::Type::MapLarge, }, { .bit_width = 36, .address = 2_GiB , .size = 64_GiB - 2_GiB , .type = KAddressSpaceInfo::Type::MapLarge, },
{ .bit_width = 36, .address = Size_Invalid, .size = 8_GiB , .type = KAddressSpaceInfo::Type::Heap, }, { .bit_width = 36, .address = Size_Invalid, .size = 8_GiB , .type = KAddressSpaceInfo::Type::Heap, },
{ .bit_width = 36, .address = Size_Invalid, .size = 6_GiB , .type = KAddressSpaceInfo::Type::Alias, }, { .bit_width = 36, .address = Size_Invalid, .size = 6_GiB , .type = KAddressSpaceInfo::Type::Alias, },
#ifdef ANDROID #ifdef HAS_NCE
// With Android, we use a 38-bit address space due to memory limitations. This should (safely) truncate ASLR region. // With NCE, we use a 38-bit address space due to memory limitations. This should (safely) truncate ASLR region.
{ .bit_width = 39, .address = 128_MiB , .size = 256_GiB - 128_MiB, .type = KAddressSpaceInfo::Type::Map39Bit, }, { .bit_width = 39, .address = 128_MiB , .size = 256_GiB - 128_MiB, .type = KAddressSpaceInfo::Type::Map39Bit, },
#else #else
{ .bit_width = 39, .address = 128_MiB , .size = 512_GiB - 128_MiB, .type = KAddressSpaceInfo::Type::Map39Bit, }, { .bit_width = 39, .address = 128_MiB , .size = 512_GiB - 128_MiB, .type = KAddressSpaceInfo::Type::Map39Bit, },

View File

@ -88,6 +88,22 @@ Result FlushDataCache(AddressType addr, u64 size) {
R_SUCCEED(); R_SUCCEED();
} }
constexpr Common::MemoryPermission ConvertToMemoryPermission(KMemoryPermission perm) {
Common::MemoryPermission perms{};
if (True(perm & KMemoryPermission::UserRead)) {
perms |= Common::MemoryPermission::Read;
}
if (True(perm & KMemoryPermission::UserWrite)) {
perms |= Common::MemoryPermission::Write;
}
#ifdef HAS_NCE
if (True(perm & KMemoryPermission::UserExecute)) {
perms |= Common::MemoryPermission::Execute;
}
#endif
return perms;
}
} // namespace } // namespace
void KPageTableBase::MemoryRange::Open() { void KPageTableBase::MemoryRange::Open() {
@ -170,7 +186,8 @@ Result KPageTableBase::InitializeForProcess(Svc::CreateProcessFlag as_type, bool
KMemoryManager::Pool pool, KProcessAddress code_address, KMemoryManager::Pool pool, KProcessAddress code_address,
size_t code_size, KSystemResource* system_resource, size_t code_size, KSystemResource* system_resource,
KResourceLimit* resource_limit, KResourceLimit* resource_limit,
Core::Memory::Memory& memory) { Core::Memory::Memory& memory,
KProcessAddress aslr_space_start) {
// Calculate region extents. // Calculate region extents.
const size_t as_width = GetAddressSpaceWidth(as_type); const size_t as_width = GetAddressSpaceWidth(as_type);
const KProcessAddress start = 0; const KProcessAddress start = 0;
@ -211,7 +228,8 @@ Result KPageTableBase::InitializeForProcess(Svc::CreateProcessFlag as_type, bool
heap_region_size = GetSpaceSize(KAddressSpaceInfo::Type::Heap); heap_region_size = GetSpaceSize(KAddressSpaceInfo::Type::Heap);
stack_region_size = GetSpaceSize(KAddressSpaceInfo::Type::Stack); stack_region_size = GetSpaceSize(KAddressSpaceInfo::Type::Stack);
kernel_map_region_size = GetSpaceSize(KAddressSpaceInfo::Type::MapSmall); kernel_map_region_size = GetSpaceSize(KAddressSpaceInfo::Type::MapSmall);
m_code_region_start = GetSpaceStart(KAddressSpaceInfo::Type::Map39Bit); m_code_region_start = m_address_space_start + aslr_space_start +
GetSpaceStart(KAddressSpaceInfo::Type::Map39Bit);
m_code_region_end = m_code_region_start + GetSpaceSize(KAddressSpaceInfo::Type::Map39Bit); m_code_region_end = m_code_region_start + GetSpaceSize(KAddressSpaceInfo::Type::Map39Bit);
m_alias_code_region_start = m_code_region_start; m_alias_code_region_start = m_code_region_start;
m_alias_code_region_end = m_code_region_end; m_alias_code_region_end = m_code_region_end;
@ -5643,7 +5661,8 @@ Result KPageTableBase::Operate(PageLinkedList* page_list, KProcessAddress virt_a
case OperationType::Map: { case OperationType::Map: {
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));
// Open references to pages, if we should. // Open references to pages, if we should.
if (this->IsHeapPhysicalAddress(phys_addr)) { if (this->IsHeapPhysicalAddress(phys_addr)) {
@ -5658,8 +5677,11 @@ Result KPageTableBase::Operate(PageLinkedList* page_list, KProcessAddress virt_a
} }
case OperationType::ChangePermissions: case OperationType::ChangePermissions:
case OperationType::ChangePermissionsAndRefresh: case OperationType::ChangePermissionsAndRefresh:
case OperationType::ChangePermissionsAndRefreshAndFlush: case OperationType::ChangePermissionsAndRefreshAndFlush: {
m_memory->ProtectRegion(*m_impl, virt_addr, num_pages * PageSize,
ConvertToMemoryPermission(properties.perm));
R_SUCCEED(); R_SUCCEED();
}
default: default:
UNREACHABLE(); UNREACHABLE();
} }
@ -5687,7 +5709,8 @@ Result KPageTableBase::Operate(PageLinkedList* page_list, KProcessAddress virt_a
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));
virt_addr += size; virt_addr += size;
} }

View File

@ -235,7 +235,8 @@ public:
bool enable_device_address_space_merge, bool from_back, bool enable_device_address_space_merge, bool from_back,
KMemoryManager::Pool pool, KProcessAddress code_address, KMemoryManager::Pool pool, KProcessAddress code_address,
size_t code_size, KSystemResource* system_resource, size_t code_size, KSystemResource* system_resource,
KResourceLimit* resource_limit, Core::Memory::Memory& memory); KResourceLimit* resource_limit, Core::Memory::Memory& memory,
KProcessAddress aslr_space_start);
void Finalize(); void Finalize();

View File

@ -300,7 +300,7 @@ Result KProcess::Initialize(const Svc::CreateProcessParameter& params, const KPa
False(params.flags & Svc::CreateProcessFlag::DisableDeviceAddressSpaceMerge); False(params.flags & Svc::CreateProcessFlag::DisableDeviceAddressSpaceMerge);
R_TRY(m_page_table.Initialize(as_type, enable_aslr, enable_das_merge, !enable_aslr, pool, R_TRY(m_page_table.Initialize(as_type, enable_aslr, enable_das_merge, !enable_aslr, pool,
params.code_address, params.code_num_pages * PageSize, params.code_address, params.code_num_pages * PageSize,
m_system_resource, res_limit, this->GetMemory())); m_system_resource, res_limit, this->GetMemory(), 0));
} }
ON_RESULT_FAILURE_2 { ON_RESULT_FAILURE_2 {
m_page_table.Finalize(); m_page_table.Finalize();
@ -332,7 +332,7 @@ Result KProcess::Initialize(const Svc::CreateProcessParameter& params, const KPa
Result KProcess::Initialize(const Svc::CreateProcessParameter& params, Result KProcess::Initialize(const Svc::CreateProcessParameter& params,
std::span<const u32> user_caps, KResourceLimit* res_limit, std::span<const u32> user_caps, KResourceLimit* res_limit,
KMemoryManager::Pool pool) { KMemoryManager::Pool pool, KProcessAddress aslr_space_start) {
ASSERT(res_limit != nullptr); ASSERT(res_limit != nullptr);
// Set members. // Set members.
@ -393,7 +393,7 @@ Result KProcess::Initialize(const Svc::CreateProcessParameter& params,
False(params.flags & Svc::CreateProcessFlag::DisableDeviceAddressSpaceMerge); False(params.flags & Svc::CreateProcessFlag::DisableDeviceAddressSpaceMerge);
R_TRY(m_page_table.Initialize(as_type, enable_aslr, enable_das_merge, !enable_aslr, pool, R_TRY(m_page_table.Initialize(as_type, enable_aslr, enable_das_merge, !enable_aslr, pool,
params.code_address, code_size, m_system_resource, res_limit, params.code_address, code_size, m_system_resource, res_limit,
this->GetMemory())); this->GetMemory(), aslr_space_start));
} }
ON_RESULT_FAILURE_2 { ON_RESULT_FAILURE_2 {
m_page_table.Finalize(); m_page_table.Finalize();
@ -1128,7 +1128,7 @@ KProcess::KProcess(KernelCore& kernel)
KProcess::~KProcess() = default; KProcess::~KProcess() = default;
Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size, Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size,
bool is_hbl) { KProcessAddress aslr_space_start, bool is_hbl) {
// Create a resource limit for the process. // Create a resource limit for the process.
const auto physical_memory_size = const auto physical_memory_size =
m_kernel.MemoryManager().GetSize(Kernel::KMemoryManager::Pool::Application); m_kernel.MemoryManager().GetSize(Kernel::KMemoryManager::Pool::Application);
@ -1179,7 +1179,7 @@ Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std:
.name = {}, .name = {},
.version = {}, .version = {},
.program_id = metadata.GetTitleID(), .program_id = metadata.GetTitleID(),
.code_address = code_address, .code_address = code_address + GetInteger(aslr_space_start),
.code_num_pages = static_cast<s32>(code_size / PageSize), .code_num_pages = static_cast<s32>(code_size / PageSize),
.flags = flag, .flags = flag,
.reslimit = Svc::InvalidHandle, .reslimit = Svc::InvalidHandle,
@ -1193,7 +1193,7 @@ Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std:
// Initialize for application process. // Initialize for application process.
R_TRY(this->Initialize(params, metadata.GetKernelCapabilities(), res_limit, R_TRY(this->Initialize(params, metadata.GetKernelCapabilities(), res_limit,
KMemoryManager::Pool::Application)); KMemoryManager::Pool::Application, aslr_space_start));
// Assign remaining properties. // Assign remaining properties.
m_is_hbl = is_hbl; m_is_hbl = is_hbl;
@ -1214,6 +1214,17 @@ void KProcess::LoadModule(CodeSet code_set, KProcessAddress base_addr) {
ReprotectSegment(code_set.CodeSegment(), Svc::MemoryPermission::ReadExecute); ReprotectSegment(code_set.CodeSegment(), Svc::MemoryPermission::ReadExecute);
ReprotectSegment(code_set.RODataSegment(), Svc::MemoryPermission::Read); ReprotectSegment(code_set.RODataSegment(), Svc::MemoryPermission::Read);
ReprotectSegment(code_set.DataSegment(), Svc::MemoryPermission::ReadWrite); ReprotectSegment(code_set.DataSegment(), Svc::MemoryPermission::ReadWrite);
#ifdef HAS_NCE
if (Settings::IsNceEnabled()) {
auto& buffer = m_kernel.System().DeviceMemory().buffer;
const auto& code = code_set.CodeSegment();
const auto& patch = code_set.PatchSegment();
buffer.Protect(GetInteger(base_addr + code.addr), code.size, true, true, true);
buffer.Protect(GetInteger(base_addr + patch.addr), patch.size, true, true, true);
ReprotectSegment(code_set.PatchSegment(), Svc::MemoryPermission::None);
}
#endif
} }
bool KProcess::InsertWatchpoint(KProcessAddress addr, u64 size, DebugWatchpointType type) { bool KProcess::InsertWatchpoint(KProcessAddress addr, u64 size, DebugWatchpointType type) {

View File

@ -120,6 +120,9 @@ private:
std::atomic<s64> m_num_ipc_messages{}; std::atomic<s64> m_num_ipc_messages{};
std::atomic<s64> m_num_ipc_replies{}; std::atomic<s64> m_num_ipc_replies{};
std::atomic<s64> m_num_ipc_receives{}; std::atomic<s64> m_num_ipc_receives{};
#ifdef HAS_NCE
std::unordered_map<u64, u64> m_post_handlers{};
#endif
private: private:
Result StartTermination(); Result StartTermination();
@ -150,7 +153,8 @@ public:
std::span<const u32> caps, KResourceLimit* res_limit, std::span<const u32> caps, KResourceLimit* res_limit,
KMemoryManager::Pool pool, bool immortal); KMemoryManager::Pool pool, bool immortal);
Result Initialize(const Svc::CreateProcessParameter& params, std::span<const u32> user_caps, Result Initialize(const Svc::CreateProcessParameter& params, std::span<const u32> user_caps,
KResourceLimit* res_limit, KMemoryManager::Pool pool); KResourceLimit* res_limit, KMemoryManager::Pool pool,
KProcessAddress aslr_space_start);
void Exit(); void Exit();
const char* GetName() const { const char* GetName() const {
@ -466,6 +470,12 @@ public:
static void Switch(KProcess* cur_process, KProcess* next_process); static void Switch(KProcess* cur_process, KProcess* next_process);
#ifdef HAS_NCE
std::unordered_map<u64, u64>& GetPostHandlers() noexcept {
return m_post_handlers;
}
#endif
public: public:
// Attempts to insert a watchpoint into a free slot. Returns false if none are available. // Attempts to insert a watchpoint into a free slot. Returns false if none are available.
bool InsertWatchpoint(KProcessAddress addr, u64 size, DebugWatchpointType type); bool InsertWatchpoint(KProcessAddress addr, u64 size, DebugWatchpointType type);
@ -479,7 +489,7 @@ public:
public: public:
Result LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size, Result LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size,
bool is_hbl); KProcessAddress aslr_space_start, bool is_hbl);
void LoadModule(CodeSet code_set, KProcessAddress base_addr); void LoadModule(CodeSet code_set, KProcessAddress base_addr);

View File

@ -23,10 +23,11 @@ public:
Result Initialize(Svc::CreateProcessFlag as_type, bool enable_aslr, bool enable_das_merge, Result Initialize(Svc::CreateProcessFlag as_type, bool enable_aslr, bool enable_das_merge,
bool from_back, KMemoryManager::Pool pool, KProcessAddress code_address, bool from_back, KMemoryManager::Pool pool, KProcessAddress code_address,
size_t code_size, KSystemResource* system_resource, size_t code_size, KSystemResource* system_resource,
KResourceLimit* resource_limit, Core::Memory::Memory& memory) { KResourceLimit* resource_limit, Core::Memory::Memory& memory,
R_RETURN(m_page_table.InitializeForProcess(as_type, enable_aslr, enable_das_merge, KProcessAddress aslr_space_start) {
from_back, pool, code_address, code_size, R_RETURN(m_page_table.InitializeForProcess(
system_resource, resource_limit, memory)); as_type, enable_aslr, enable_das_merge, from_back, pool, code_address, code_size,
system_resource, resource_limit, memory, aslr_space_start));
} }
void Finalize() { void Finalize() {

View File

@ -655,6 +655,21 @@ public:
return m_stack_top; return m_stack_top;
} }
public:
// TODO: This shouldn't be defined in kernel namespace
struct NativeExecutionParameters {
u64 tpidr_el0{};
u64 tpidrro_el0{};
void* native_context{};
std::atomic<u32> lock{1};
bool is_running{};
u32 magic{Common::MakeMagic('Y', 'U', 'Z', 'U')};
};
NativeExecutionParameters& GetNativeExecutionParameters() {
return m_native_execution_parameters;
}
private: private:
KThread* RemoveWaiterByKey(bool* out_has_waiters, KProcessAddress key, KThread* RemoveWaiterByKey(bool* out_has_waiters, KProcessAddress key,
bool is_kernel_address_key); bool is_kernel_address_key);
@ -914,6 +929,7 @@ private:
ThreadWaitReasonForDebugging m_wait_reason_for_debugging{}; ThreadWaitReasonForDebugging m_wait_reason_for_debugging{};
uintptr_t m_argument{}; uintptr_t m_argument{};
KProcessAddress m_stack_top{}; KProcessAddress m_stack_top{};
NativeExecutionParameters m_native_execution_parameters{};
public: public:
using ConditionVariableThreadTreeType = ConditionVariableThreadTree; using ConditionVariableThreadTreeType = ConditionVariableThreadTree;

View File

@ -1,8 +1,12 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include "common/settings.h"
#include "core/arm/dynarmic/arm_dynarmic_32.h" #include "core/arm/dynarmic/arm_dynarmic_32.h"
#include "core/arm/dynarmic/arm_dynarmic_64.h" #include "core/arm/dynarmic/arm_dynarmic_64.h"
#ifdef HAS_NCE
#include "core/arm/nce/arm_nce.h"
#endif
#include "core/core.h" #include "core/core.h"
#include "core/hle/kernel/k_scheduler.h" #include "core/hle/kernel/k_scheduler.h"
#include "core/hle/kernel/kernel.h" #include "core/hle/kernel/kernel.h"
@ -14,7 +18,8 @@ PhysicalCore::PhysicalCore(std::size_t core_index, Core::System& system, KSchedu
: m_core_index{core_index}, m_system{system}, m_scheduler{scheduler} { : m_core_index{core_index}, m_system{system}, m_scheduler{scheduler} {
#if defined(ARCHITECTURE_x86_64) || defined(ARCHITECTURE_arm64) #if defined(ARCHITECTURE_x86_64) || defined(ARCHITECTURE_arm64)
// TODO(bunnei): Initialization relies on a core being available. We may later replace this with // TODO(bunnei): Initialization relies on a core being available. We may later replace this with
// a 32-bit instance of Dynarmic. This should be abstracted out to a CPU manager. // an NCE interface or a 32-bit instance of Dynarmic. This should be abstracted out to a CPU
// manager.
auto& kernel = system.Kernel(); auto& kernel = system.Kernel();
m_arm_interface = std::make_unique<Core::ARM_Dynarmic_64>( m_arm_interface = std::make_unique<Core::ARM_Dynarmic_64>(
system, kernel.IsMulticore(), system, kernel.IsMulticore(),
@ -28,6 +33,13 @@ PhysicalCore::PhysicalCore(std::size_t core_index, Core::System& system, KSchedu
PhysicalCore::~PhysicalCore() = default; PhysicalCore::~PhysicalCore() = default;
void PhysicalCore::Initialize(bool is_64_bit) { void PhysicalCore::Initialize(bool is_64_bit) {
#if defined(HAS_NCE)
if (Settings::IsNceEnabled()) {
m_arm_interface = std::make_unique<Core::ARM_NCE>(m_system, m_system.Kernel().IsMulticore(),
m_core_index);
return;
}
#endif
#if defined(ARCHITECTURE_x86_64) || defined(ARCHITECTURE_arm64) #if defined(ARCHITECTURE_x86_64) || defined(ARCHITECTURE_arm64)
auto& kernel = m_system.Kernel(); auto& kernel = m_system.Kernel();
if (!is_64_bit) { if (!is_64_bit) {

View File

@ -122,7 +122,8 @@ void Cabinet::DisplayCompleted(bool apply_changes, std::string_view amiibo_name)
Service::NFP::RegisterInfoPrivate register_info{}; Service::NFP::RegisterInfoPrivate register_info{};
std::memcpy(register_info.amiibo_name.data(), amiibo_name.data(), std::memcpy(register_info.amiibo_name.data(), amiibo_name.data(),
std::min(amiibo_name.size(), register_info.amiibo_name.size() - 1)); std::min(amiibo_name.size(), register_info.amiibo_name.size() - 1));
register_info.mii_store_data.BuildRandom(Mii::Age::All, Mii::Gender::All, Mii::Race::All);
register_info.mii_store_data.SetNickname({u'y', u'u', u'z', u'u'});
nfp_device->SetRegisterInfoPrivate(register_info); nfp_device->SetRegisterInfoPrivate(register_info);
break; break;
} }
@ -130,7 +131,7 @@ void Cabinet::DisplayCompleted(bool apply_changes, std::string_view amiibo_name)
nfp_device->DeleteApplicationArea(); nfp_device->DeleteApplicationArea();
break; break;
case Service::NFP::CabinetMode::StartRestorer: case Service::NFP::CabinetMode::StartRestorer:
nfp_device->RestoreAmiibo(); nfp_device->Restore();
break; break;
case Service::NFP::CabinetMode::StartFormatter: case Service::NFP::CabinetMode::StartFormatter:
nfp_device->Format(); nfp_device->Format();

View File

@ -98,7 +98,7 @@ void Ver3StoreData::BuildToStoreData(StoreData& out_store_data) const {
} }
void Ver3StoreData::BuildFromStoreData(const StoreData& store_data) { void Ver3StoreData::BuildFromStoreData(const StoreData& store_data) {
version = 1; version = 3;
mii_information.gender.Assign(static_cast<u8>(store_data.GetGender())); mii_information.gender.Assign(static_cast<u8>(store_data.GetGender()));
mii_information.favorite_color.Assign(static_cast<u8>(store_data.GetFavoriteColor())); mii_information.favorite_color.Assign(static_cast<u8>(store_data.GetFavoriteColor()));
height = store_data.GetHeight(); height = store_data.GetHeight();

View File

@ -401,6 +401,12 @@ Result NfcDevice::SendCommandByPassThrough(const Time::Clock::TimeSpanType& time
} }
Result NfcDevice::Mount(NFP::ModelType model_type, NFP::MountTarget mount_target_) { Result NfcDevice::Mount(NFP::ModelType model_type, NFP::MountTarget mount_target_) {
bool is_corrupted = false;
if (model_type != NFP::ModelType::Amiibo) {
return ResultInvalidArgument;
}
if (device_state != DeviceState::TagFound) { if (device_state != DeviceState::TagFound) {
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
return ResultWrongDeviceState; return ResultWrongDeviceState;
@ -420,26 +426,32 @@ Result NfcDevice::Mount(NFP::ModelType model_type, NFP::MountTarget mount_target
if (is_plain_amiibo) { if (is_plain_amiibo) {
std::vector<u8> data(sizeof(NFP::NTAG215File)); std::vector<u8> data(sizeof(NFP::NTAG215File));
memcpy(data.data(), &tag_data, sizeof(tag_data)); memcpy(data.data(), &tag_data, sizeof(tag_data));
WriteBackupData(tag_data.uid, data);
device_state = DeviceState::TagMounted;
mount_target = mount_target_;
return ResultSuccess;
} }
if (!NFP::AmiiboCrypto::DecodeAmiibo(encrypted_tag_data, tag_data)) { if (!is_plain_amiibo && !NFP::AmiiboCrypto::DecodeAmiibo(encrypted_tag_data, tag_data)) {
bool has_backup = HasBackup(encrypted_tag_data.uuid).IsSuccess(); LOG_ERROR(Service_NFP, "Can't decode amiibo");
LOG_ERROR(Service_NFP, "Can't decode amiibo, has_backup= {}", has_backup); is_corrupted = true;
return has_backup ? ResultCorruptedDataWithBackup : ResultCorruptedData;
} }
std::vector<u8> data(sizeof(NFP::EncryptedNTAG215File)); if (tag_data.settings.settings.amiibo_initialized && !tag_data.owner_mii.IsValid()) {
memcpy(data.data(), &encrypted_tag_data, sizeof(encrypted_tag_data)); LOG_ERROR(Service_NFP, "Invalid mii data");
WriteBackupData(encrypted_tag_data.uuid, data); is_corrupted = true;
}
if (!is_corrupted) {
std::vector<u8> data(sizeof(NFP::EncryptedNTAG215File));
memcpy(data.data(), &encrypted_tag_data, sizeof(encrypted_tag_data));
WriteBackupData(encrypted_tag_data.uuid, data);
}
device_state = DeviceState::TagMounted; device_state = DeviceState::TagMounted;
mount_target = mount_target_; mount_target = mount_target_;
if (is_corrupted) {
bool has_backup = HasBackup(encrypted_tag_data.uuid).IsSuccess();
return has_backup ? ResultCorruptedDataWithBackup : ResultCorruptedData;
}
return ResultSuccess; return ResultSuccess;
} }
@ -606,6 +618,17 @@ Result NfcDevice::Restore() {
} }
} }
// Restore mii data in case is corrupted by previous instances of yuzu
if (tag_data.settings.settings.amiibo_initialized && !tag_data.owner_mii.IsValid()) {
LOG_ERROR(Service_NFP, "Regenerating mii data");
Mii::StoreData new_mii{};
new_mii.BuildRandom(Mii::Age::All, Mii::Gender::All, Mii::Race::All);
new_mii.SetNickname({u'y', u'u', u'z', u'u', u'\0'});
tag_data.owner_mii.BuildFromStoreData(new_mii);
tag_data.mii_extension.SetFromStoreData(new_mii);
}
// Overwrite tag contents with backup and mount the tag // Overwrite tag contents with backup and mount the tag
tag_data = temporary_tag_data; tag_data = temporary_tag_data;
encrypted_tag_data = temporary_encrypted_tag_data; encrypted_tag_data = temporary_encrypted_tag_data;
@ -851,25 +874,6 @@ Result NfcDevice::SetRegisterInfoPrivate(const NFP::RegisterInfoPrivate& registe
return Flush(); return Flush();
} }
Result NfcDevice::RestoreAmiibo() {
if (device_state != DeviceState::TagMounted) {
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
if (device_state == DeviceState::TagRemoved) {
return ResultTagRemoved;
}
return ResultWrongDeviceState;
}
if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) {
LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
return ResultWrongDeviceState;
}
// TODO: Load amiibo from backup on system
LOG_ERROR(Service_NFP, "Not Implemented");
return ResultSuccess;
}
Result NfcDevice::Format() { Result NfcDevice::Format() {
Result result = ResultSuccess; Result result = ResultSuccess;
@ -877,7 +881,9 @@ Result NfcDevice::Format() {
result = Mount(NFP::ModelType::Amiibo, NFP::MountTarget::All); result = Mount(NFP::ModelType::Amiibo, NFP::MountTarget::All);
} }
if (result.IsError()) { // We are formatting all data. Corruption is not an issue.
if (result.IsError() &&
(result != ResultCorruptedData && result != ResultCorruptedDataWithBackup)) {
return result; return result;
} }

View File

@ -68,7 +68,6 @@ public:
Result DeleteRegisterInfo(); Result DeleteRegisterInfo();
Result SetRegisterInfoPrivate(const NFP::RegisterInfoPrivate& register_info); Result SetRegisterInfoPrivate(const NFP::RegisterInfoPrivate& register_info);
Result RestoreAmiibo();
Result Format(); Result Format();
Result OpenApplicationArea(u32 access_id); Result OpenApplicationArea(u32 access_id);

View File

@ -19,19 +19,8 @@
namespace Service::Set { namespace Service::Set {
namespace { Result GetFirmwareVersionImpl(FirmwareVersionFormat& out_firmware, Core::System& system,
constexpr u64 SYSTEM_VERSION_FILE_MINOR_REVISION_OFFSET = 0x05; GetFirmwareVersionType type) {
enum class GetFirmwareVersionType {
Version1,
Version2,
};
void GetFirmwareVersionImpl(Core::System& system, HLERequestContext& ctx,
GetFirmwareVersionType type) {
ASSERT_MSG(ctx.GetWriteBufferSize() == 0x100,
"FirmwareVersion output buffer must be 0x100 bytes in size!");
constexpr u64 FirmwareVersionSystemDataId = 0x0100000000000809; constexpr u64 FirmwareVersionSystemDataId = 0x0100000000000809;
auto& fsc = system.GetFileSystemController(); auto& fsc = system.GetFileSystemController();
@ -52,39 +41,34 @@ void GetFirmwareVersionImpl(Core::System& system, HLERequestContext& ctx,
FileSys::SystemArchive::SynthesizeSystemArchive(FirmwareVersionSystemDataId)); FileSys::SystemArchive::SynthesizeSystemArchive(FirmwareVersionSystemDataId));
} }
const auto early_exit_failure = [&ctx](std::string_view desc, Result code) { const auto early_exit_failure = [](std::string_view desc, Result code) {
LOG_ERROR(Service_SET, "General failure while attempting to resolve firmware version ({}).", LOG_ERROR(Service_SET, "General failure while attempting to resolve firmware version ({}).",
desc); desc);
IPC::ResponseBuilder rb{ctx, 2}; return code;
rb.Push(code);
}; };
const auto ver_file = romfs->GetFile("file"); const auto ver_file = romfs->GetFile("file");
if (ver_file == nullptr) { if (ver_file == nullptr) {
early_exit_failure("The system version archive didn't contain the file 'file'.", return early_exit_failure("The system version archive didn't contain the file 'file'.",
FileSys::ERROR_INVALID_ARGUMENT); FileSys::ERROR_INVALID_ARGUMENT);
return;
} }
auto data = ver_file->ReadAllBytes(); auto data = ver_file->ReadAllBytes();
if (data.size() != 0x100) { if (data.size() != sizeof(FirmwareVersionFormat)) {
early_exit_failure("The system version file 'file' was not the correct size.", return early_exit_failure("The system version file 'file' was not the correct size.",
FileSys::ERROR_OUT_OF_BOUNDS); FileSys::ERROR_OUT_OF_BOUNDS);
return;
} }
std::memcpy(&out_firmware, data.data(), sizeof(FirmwareVersionFormat));
// If the command is GetFirmwareVersion (as opposed to GetFirmwareVersion2), hardware will // If the command is GetFirmwareVersion (as opposed to GetFirmwareVersion2), hardware will
// zero out the REVISION_MINOR field. // zero out the REVISION_MINOR field.
if (type == GetFirmwareVersionType::Version1) { if (type == GetFirmwareVersionType::Version1) {
data[SYSTEM_VERSION_FILE_MINOR_REVISION_OFFSET] = 0; out_firmware.revision_minor = 0;
} }
ctx.WriteBuffer(data); return ResultSuccess;
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
} }
} // Anonymous namespace
void SET_SYS::SetLanguageCode(HLERequestContext& ctx) { void SET_SYS::SetLanguageCode(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx}; IPC::RequestParser rp{ctx};
@ -98,12 +82,32 @@ void SET_SYS::SetLanguageCode(HLERequestContext& ctx) {
void SET_SYS::GetFirmwareVersion(HLERequestContext& ctx) { void SET_SYS::GetFirmwareVersion(HLERequestContext& ctx) {
LOG_DEBUG(Service_SET, "called"); LOG_DEBUG(Service_SET, "called");
GetFirmwareVersionImpl(system, ctx, GetFirmwareVersionType::Version1);
FirmwareVersionFormat firmware_data{};
const auto result =
GetFirmwareVersionImpl(firmware_data, system, GetFirmwareVersionType::Version1);
if (result.IsSuccess()) {
ctx.WriteBuffer(firmware_data);
}
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
} }
void SET_SYS::GetFirmwareVersion2(HLERequestContext& ctx) { void SET_SYS::GetFirmwareVersion2(HLERequestContext& ctx) {
LOG_DEBUG(Service_SET, "called"); LOG_DEBUG(Service_SET, "called");
GetFirmwareVersionImpl(system, ctx, GetFirmwareVersionType::Version2);
FirmwareVersionFormat firmware_data{};
const auto result =
GetFirmwareVersionImpl(firmware_data, system, GetFirmwareVersionType::Version2);
if (result.IsSuccess()) {
ctx.WriteBuffer(firmware_data);
}
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
} }
void SET_SYS::GetAccountSettings(HLERequestContext& ctx) { void SET_SYS::GetAccountSettings(HLERequestContext& ctx) {

View File

@ -4,6 +4,7 @@
#pragma once #pragma once
#include "common/uuid.h" #include "common/uuid.h"
#include "core/hle/result.h"
#include "core/hle/service/service.h" #include "core/hle/service/service.h"
#include "core/hle/service/time/clock_types.h" #include "core/hle/service/time/clock_types.h"
@ -12,6 +13,29 @@ class System;
} }
namespace Service::Set { namespace Service::Set {
enum class LanguageCode : u64;
enum class GetFirmwareVersionType {
Version1,
Version2,
};
struct FirmwareVersionFormat {
u8 major;
u8 minor;
u8 micro;
INSERT_PADDING_BYTES(1);
u8 revision_major;
u8 revision_minor;
INSERT_PADDING_BYTES(2);
std::array<char, 0x20> platform;
std::array<u8, 0x40> version_hash;
std::array<char, 0x18> display_version;
std::array<char, 0x80> display_title;
};
static_assert(sizeof(FirmwareVersionFormat) == 0x100, "FirmwareVersionFormat is an invalid size");
Result GetFirmwareVersionImpl(FirmwareVersionFormat& out_firmware, Core::System& system,
GetFirmwareVersionType type);
class SET_SYS final : public ServiceFramework<SET_SYS> { class SET_SYS final : public ServiceFramework<SET_SYS> {
public: public:

Some files were not shown because too many files have changed in this diff Show More