Compare commits

..

1 Commits

Author SHA1 Message Date
27c3d35f2f Android #27 2023-08-02 00:58:07 +00:00
329 changed files with 7967 additions and 17245 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,zink ignore-words-list = aci,allright,ba,deques,froms,hda,inout,lod,masia,nam,nax,nd,optin,pullrequests,pullrequest,te,transfered,unstall,uscaled,zink

View File

@ -49,7 +49,7 @@ option(YUZU_TESTS "Compile tests" "${BUILD_TESTING}")
option(YUZU_USE_PRECOMPILED_HEADERS "Use precompiled headers" ON) option(YUZU_USE_PRECOMPILED_HEADERS "Use precompiled headers" ON)
CMAKE_DEPENDENT_OPTION(YUZU_ROOM "Compile LDN room server" ON "NOT ANDROID" OFF) cmake_dependent_option(YUZU_ROOM "Compile LDN room server" ON "NOT ANDROID" OFF)
CMAKE_DEPENDENT_OPTION(YUZU_CRASH_DUMPS "Compile Windows crash dump (Minidump) support" OFF "WIN32" OFF) CMAKE_DEPENDENT_OPTION(YUZU_CRASH_DUMPS "Compile Windows crash dump (Minidump) support" OFF "WIN32" OFF)
@ -63,8 +63,6 @@ option(YUZU_DOWNLOAD_TIME_ZONE_DATA "Always download time zone binaries" OFF)
CMAKE_DEPENDENT_OPTION(YUZU_USE_FASTER_LD "Check if a faster linker is available" ON "NOT WIN32" OFF) CMAKE_DEPENDENT_OPTION(YUZU_USE_FASTER_LD "Check if a faster linker is available" ON "NOT WIN32" OFF)
CMAKE_DEPENDENT_OPTION(USE_SYSTEM_MOLTENVK "Use the system MoltenVK lib (instead of the bundled one)" OFF "APPLE" OFF)
set(DEFAULT_ENABLE_OPENSSL ON) set(DEFAULT_ENABLE_OPENSSL ON)
if (ANDROID OR WIN32 OR APPLE) if (ANDROID OR WIN32 OR APPLE)
# - Windows defaults to the Schannel backend. # - Windows defaults to the Schannel backend.

View File

@ -36,21 +36,3 @@ endif()
message(STATUS "Using bundled binaries at ${prefix}") message(STATUS "Using bundled binaries at ${prefix}")
set(${prefix_var} "${prefix}" PARENT_SCOPE) set(${prefix_var} "${prefix}" PARENT_SCOPE)
endfunction() endfunction()
function(download_moltenvk_external platform version)
set(MOLTENVK_DIR "${CMAKE_BINARY_DIR}/externals/MoltenVK")
set(MOLTENVK_TAR "${CMAKE_BINARY_DIR}/externals/MoltenVK.tar")
if (NOT EXISTS ${MOLTENVK_DIR})
if (NOT EXISTS ${MOLTENVK_TAR})
file(DOWNLOAD https://github.com/KhronosGroup/MoltenVK/releases/download/${version}/MoltenVK-${platform}.tar
${MOLTENVK_TAR} SHOW_PROGRESS)
endif()
execute_process(COMMAND ${CMAKE_COMMAND} -E tar xf "${MOLTENVK_TAR}"
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/externals")
endif()
# Add the MoltenVK library path to the prefix so find_library can locate it.
list(APPEND CMAKE_PREFIX_PATH "${MOLTENVK_DIR}/MoltenVK/dylib/${platform}")
set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} PARENT_SCOPE)
endfunction()

View File

@ -48,7 +48,7 @@ It is written in C++ with portability in mind, and we actively maintain builds f
The emulator is capable of running most commercial games at full speed, provided you meet the [necessary hardware requirements](https://yuzu-emu.org/help/quickstart/#hardware-requirements). The emulator is capable of running most commercial games at full speed, provided you meet the [necessary hardware requirements](https://yuzu-emu.org/help/quickstart/#hardware-requirements).
For a full list of games yuzu supports, please visit our [Compatibility page](https://yuzu-emu.org/game/). For a full list of games yuzu support, please visit our [Compatibility page](https://yuzu-emu.org/game/)
Check out our [website](https://yuzu-emu.org/) for the latest news on exciting features, monthly progress reports, and more! Check out our [website](https://yuzu-emu.org/) for the latest news on exciting features, monthly progress reports, and more!

View File

@ -78,11 +78,6 @@ QPushButton#buttonRefreshDevices {
max-height: 21px; max-height: 21px;
} }
QPushButton#button_reset_defaults {
min-width: 57px;
padding: 4px 8px;
}
QWidget#bottomPerGameInput, QWidget#bottomPerGameInput,
QWidget#topControllerApplet, QWidget#topControllerApplet,
QWidget#bottomControllerApplet, QWidget#bottomControllerApplet,

View File

@ -2228,10 +2228,6 @@ QPushButton#buttonRefreshDevices {
padding: 0px 0px; padding: 0px 0px;
} }
QPushButton#button_reset_defaults {
padding: 3px 6px;
}
QSpinBox#spinboxLStickRange, QSpinBox#spinboxLStickRange,
QSpinBox#spinboxRStickRange, QSpinBox#spinboxRStickRange,
QSpinBox#vibrationSpinPlayer1, QSpinBox#vibrationSpinPlayer1,

View File

@ -42,11 +42,6 @@ endif()
# mbedtls # mbedtls
add_subdirectory(mbedtls) add_subdirectory(mbedtls)
target_include_directories(mbedtls PUBLIC ./mbedtls/include) target_include_directories(mbedtls PUBLIC ./mbedtls/include)
if (NOT MSVC)
target_compile_options(mbedcrypto PRIVATE
-Wno-unused-but-set-variable
-Wno-string-concatenation)
endif()
# MicroProfile # MicroProfile
add_library(microprofile INTERFACE) add_library(microprofile INTERFACE)
@ -99,12 +94,6 @@ if (ENABLE_CUBEB AND NOT TARGET cubeb::cubeb)
set(BUILD_TOOLS OFF) set(BUILD_TOOLS OFF)
add_subdirectory(cubeb) add_subdirectory(cubeb)
add_library(cubeb::cubeb ALIAS cubeb) add_library(cubeb::cubeb ALIAS cubeb)
if (NOT MSVC)
if (TARGET speex)
target_compile_options(speex PRIVATE -Wno-sign-compare)
endif()
target_compile_options(cubeb PRIVATE -Wno-implicit-const-int-float-conversion)
endif()
endif() endif()
# DiscordRPC # DiscordRPC
@ -162,9 +151,6 @@ endif()
if (NOT TARGET LLVM::Demangle) if (NOT TARGET LLVM::Demangle)
add_library(demangle demangle/ItaniumDemangle.cpp) add_library(demangle demangle/ItaniumDemangle.cpp)
target_include_directories(demangle PUBLIC ./demangle) target_include_directories(demangle PUBLIC ./demangle)
if (NOT MSVC)
target_compile_options(demangle PRIVATE -Wno-deprecated-declarations) # std::is_pod
endif()
add_library(LLVM::Demangle ALIAS demangle) add_library(LLVM::Demangle ALIAS demangle)
endif() endif()

View File

@ -35,7 +35,6 @@ if (MSVC)
# /volatile:iso - Use strict standards-compliant volatile semantics. # /volatile:iso - Use strict standards-compliant volatile semantics.
# /Zc:externConstexpr - Allow extern constexpr variables to have external linkage, like the standard mandates # /Zc:externConstexpr - Allow extern constexpr variables to have external linkage, like the standard mandates
# /Zc:inline - Let codegen omit inline functions in object files # /Zc:inline - Let codegen omit inline functions in object files
# /Zc:preprocessor - Enable standards-conforming preprocessor
# /Zc:throwingNew - Let codegen assume `operator new` (without std::nothrow) will never return null # /Zc:throwingNew - Let codegen assume `operator new` (without std::nothrow) will never return null
# /GT - Supports fiber safety for data allocated using static thread-local storage # /GT - Supports fiber safety for data allocated using static thread-local storage
add_compile_options( add_compile_options(
@ -49,7 +48,6 @@ if (MSVC)
/volatile:iso /volatile:iso
/Zc:externConstexpr /Zc:externConstexpr
/Zc:inline /Zc:inline
/Zc:preprocessor
/Zc:throwingNew /Zc:throwingNew
/GT /GT
@ -114,18 +112,15 @@ else()
-Wno-attributes -Wno-attributes
-Wno-invalid-offsetof -Wno-invalid-offsetof
-Wno-unused-parameter -Wno-unused-parameter
)
if (CMAKE_CXX_COMPILER_ID MATCHES Clang) # Clang or AppleClang $<$<CXX_COMPILER_ID:Clang>:-Wno-braced-scalar-init>
add_compile_options( $<$<CXX_COMPILER_ID:Clang>:-Wno-unused-private-field>
-Wno-braced-scalar-init $<$<CXX_COMPILER_ID:Clang>:-Werror=shadow-uncaptured-local>
-Wno-unused-private-field $<$<CXX_COMPILER_ID:Clang>:-Werror=implicit-fallthrough>
-Wno-nullability-completeness $<$<CXX_COMPILER_ID:Clang>:-Werror=type-limits>
-Werror=shadow-uncaptured-local $<$<CXX_COMPILER_ID:AppleClang>:-Wno-braced-scalar-init>
-Werror=implicit-fallthrough $<$<CXX_COMPILER_ID:AppleClang>:-Wno-unused-private-field>
-Werror=type-limits
) )
endif()
if (ARCHITECTURE_x86_64) if (ARCHITECTURE_x86_64)
add_compile_options("-mcx16") add_compile_options("-mcx16")
@ -137,7 +132,7 @@ else()
endif() endif()
# GCC bugs # GCC bugs
if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "11" AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "12" AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
# These diagnostics would be great if they worked, but are just completely broken # These diagnostics would be great if they worked, but are just completely broken
# and produce bogus errors on external libraries like fmt. # and produce bogus errors on external libraries like fmt.
add_compile_options( add_compile_options(

View File

@ -95,7 +95,6 @@ android {
// builds a release build that doesn't need signing // builds a release build that doesn't need signing
// Attaches 'debug' suffix to version and package name, allowing installation alongside the release build. // Attaches 'debug' suffix to version and package name, allowing installation alongside the release build.
register("relWithDebInfo") { register("relWithDebInfo") {
isDefault = true
resValue("string", "app_name_suffixed", "yuzu Debug Release") resValue("string", "app_name_suffixed", "yuzu Debug Release")
signingConfig = signingConfigs.getByName("debug") signingConfig = signingConfigs.getByName("debug")
isMinifyEnabled = true isMinifyEnabled = true
@ -123,7 +122,6 @@ android {
flavorDimensions.add("version") flavorDimensions.add("version")
productFlavors { productFlavors {
create("mainline") { create("mainline") {
isDefault = true
dimension = "version" dimension = "version"
buildConfigField("Boolean", "PREMIUM", "false") buildConfigField("Boolean", "PREMIUM", "false")
} }
@ -162,11 +160,6 @@ android {
} }
} }
tasks.create<Delete>("ktlintReset") {
delete(File(buildDir.path + File.separator + "intermediates/ktLint"))
}
tasks.getByPath("loadKtlintReporters").dependsOn("ktlintReset")
tasks.getByPath("preBuild").dependsOn("ktlintCheck") tasks.getByPath("preBuild").dependsOn("ktlintCheck")
ktlint { ktlint {

View File

@ -25,7 +25,6 @@ SPDX-License-Identifier: GPL-3.0-or-later
android:hasFragileUserData="false" android:hasFragileUserData="false"
android:supportsRtl="true" android:supportsRtl="true"
android:isGame="true" android:isGame="true"
android:appCategory="game"
android:localeConfig="@xml/locales_config" android:localeConfig="@xml/locales_config"
android:banner="@drawable/tv_banner" android:banner="@drawable/tv_banner"
android:extractNativeLibs="true" android:extractNativeLibs="true"

View File

@ -3,25 +3,19 @@
package org.yuzu.yuzu_emu.adapters package org.yuzu.yuzu_emu.adapters
import android.text.TextUtils
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.content.res.ResourcesCompat import androidx.core.content.res.ResourcesCompat
import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.CardHomeOptionBinding import org.yuzu.yuzu_emu.databinding.CardHomeOptionBinding
import org.yuzu.yuzu_emu.fragments.MessageDialogFragment import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
import org.yuzu.yuzu_emu.model.HomeSetting import org.yuzu.yuzu_emu.model.HomeSetting
class HomeSettingAdapter( class HomeSettingAdapter(private val activity: AppCompatActivity, var options: List<HomeSetting>) :
private val activity: AppCompatActivity,
private val viewLifecycle: LifecycleOwner,
var options: List<HomeSetting>
) :
RecyclerView.Adapter<HomeSettingAdapter.HomeOptionViewHolder>(), RecyclerView.Adapter<HomeSettingAdapter.HomeOptionViewHolder>(),
View.OnClickListener { View.OnClickListener {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HomeOptionViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HomeOptionViewHolder {
@ -85,22 +79,6 @@ class HomeSettingAdapter(
binding.optionDescription.alpha = 0.5f binding.optionDescription.alpha = 0.5f
binding.optionIcon.alpha = 0.5f binding.optionIcon.alpha = 0.5f
} }
option.details.observe(viewLifecycle) { updateOptionDetails(it) }
binding.optionDetail.postDelayed(
{
binding.optionDetail.ellipsize = TextUtils.TruncateAt.MARQUEE
binding.optionDetail.isSelected = true
},
3000
)
}
private fun updateOptionDetails(detailString: String) {
if (detailString.isNotEmpty()) {
binding.optionDetail.text = detailString
binding.optionDetail.visibility = View.VISIBLE
}
} }
} }
} }

View File

@ -49,7 +49,6 @@ class LicenseAdapter(private val activity: AppCompatActivity, var licenses: List
val context = YuzuApplication.appContext val context = YuzuApplication.appContext
binding.textSettingName.text = context.getString(license.titleId) binding.textSettingName.text = context.getString(license.titleId)
binding.textSettingDescription.text = context.getString(license.descriptionId) binding.textSettingDescription.text = context.getString(license.descriptionId)
binding.textSettingValue.visibility = View.GONE
} }
} }
} }

View File

@ -5,19 +5,13 @@ package org.yuzu.yuzu_emu.adapters
import android.text.Html import android.text.Html
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.res.ResourcesCompat import androidx.core.content.res.ResourcesCompat
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.button.MaterialButton import com.google.android.material.button.MaterialButton
import org.yuzu.yuzu_emu.databinding.PageSetupBinding import org.yuzu.yuzu_emu.databinding.PageSetupBinding
import org.yuzu.yuzu_emu.model.HomeViewModel
import org.yuzu.yuzu_emu.model.SetupCallback
import org.yuzu.yuzu_emu.model.SetupPage import org.yuzu.yuzu_emu.model.SetupPage
import org.yuzu.yuzu_emu.model.StepState
import org.yuzu.yuzu_emu.utils.ViewUtils
class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>) : class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>) :
RecyclerView.Adapter<SetupAdapter.SetupPageViewHolder>() { RecyclerView.Adapter<SetupAdapter.SetupPageViewHolder>() {
@ -32,7 +26,7 @@ class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>)
holder.bind(pages[position]) holder.bind(pages[position])
inner class SetupPageViewHolder(val binding: PageSetupBinding) : inner class SetupPageViewHolder(val binding: PageSetupBinding) :
RecyclerView.ViewHolder(binding.root), SetupCallback { RecyclerView.ViewHolder(binding.root) {
lateinit var page: SetupPage lateinit var page: SetupPage
init { init {
@ -41,12 +35,6 @@ class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>)
fun bind(page: SetupPage) { fun bind(page: SetupPage) {
this.page = page this.page = page
if (page.stepCompleted.invoke() == StepState.COMPLETE) {
binding.buttonAction.visibility = View.INVISIBLE
binding.textConfirmation.visibility = View.VISIBLE
}
binding.icon.setImageDrawable( binding.icon.setImageDrawable(
ResourcesCompat.getDrawable( ResourcesCompat.getDrawable(
activity.resources, activity.resources,
@ -74,15 +62,9 @@ class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>)
MaterialButton.ICON_GRAVITY_END MaterialButton.ICON_GRAVITY_END
} }
setOnClickListener { setOnClickListener {
page.buttonAction.invoke(this@SetupPageViewHolder) page.buttonAction.invoke()
} }
} }
} }
override fun onStepCompleted() {
ViewUtils.hideView(binding.buttonAction, 200)
ViewUtils.showView(binding.textConfirmation, 200)
ViewModelProvider(activity)[HomeViewModel::class.java].setShouldPageForward(true)
}
} }
} }

View File

@ -207,11 +207,8 @@ class SettingsAdapter(
val sliderBinding = DialogSliderBinding.inflate(inflater) val sliderBinding = DialogSliderBinding.inflate(inflater)
textSliderValue = sliderBinding.textValue textSliderValue = sliderBinding.textValue
textSliderValue!!.text = String.format( textSliderValue!!.text = sliderProgress.toString()
context.getString(R.string.value_with_units), sliderBinding.textUnits.text = item.units
sliderProgress.toString(),
item.units
)
sliderBinding.slider.apply { sliderBinding.slider.apply {
valueFrom = item.min.toFloat() valueFrom = item.min.toFloat()
@ -219,11 +216,7 @@ class SettingsAdapter(
value = sliderProgress.toFloat() value = sliderProgress.toFloat()
addOnChangeListener { _: Slider, value: Float, _: Boolean -> addOnChangeListener { _: Slider, value: Float, _: Boolean ->
sliderProgress = value.toInt() sliderProgress = value.toInt()
textSliderValue!!.text = String.format( textSliderValue!!.text = sliderProgress.toString()
context.getString(R.string.value_with_units),
sliderProgress.toString(),
item.units
)
} }
} }
@ -232,6 +225,10 @@ class SettingsAdapter(
.setView(sliderBinding.root) .setView(sliderBinding.root)
.setPositiveButton(android.R.string.ok, this) .setPositiveButton(android.R.string.ok, this)
.setNegativeButton(android.R.string.cancel, defaultCancelListener) .setNegativeButton(android.R.string.cancel, defaultCancelListener)
.setNeutralButton(R.string.slider_default) { dialog: DialogInterface, which: Int ->
sliderBinding.slider.value = item.defaultValue!!.toFloat()
onClick(dialog, which)
}
.show() .show()
} }

View File

@ -25,17 +25,12 @@ class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA
binding.textSettingDescription.setText(item.descriptionId) binding.textSettingDescription.setText(item.descriptionId)
binding.textSettingDescription.visibility = View.VISIBLE binding.textSettingDescription.visibility = View.VISIBLE
} else { } else {
binding.textSettingDescription.visibility = View.GONE
}
binding.textSettingValue.visibility = View.VISIBLE
val epochTime = setting.value.toLong() val epochTime = setting.value.toLong()
val instant = Instant.ofEpochMilli(epochTime * 1000) val instant = Instant.ofEpochMilli(epochTime * 1000)
val zonedTime = ZonedDateTime.ofInstant(instant, ZoneId.of("UTC")) val zonedTime = ZonedDateTime.ofInstant(instant, ZoneId.of("UTC"))
val dateFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM) val dateFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)
binding.textSettingValue.text = dateFormatter.format(zonedTime) binding.textSettingDescription.text = dateFormatter.format(zonedTime)
}
setStyle(setting.isEditable, binding)
} }
override fun onClick(clicked: View) { override fun onClick(clicked: View) {

View File

@ -23,9 +23,6 @@ class RunnableViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA
} else { } else {
binding.textSettingDescription.visibility = View.GONE binding.textSettingDescription.visibility = View.GONE
} }
binding.textSettingValue.visibility = View.GONE
setStyle(setting.isEditable, binding)
} }
override fun onClick(clicked: View) { override fun onClick(clicked: View) {

View File

@ -5,8 +5,6 @@ package org.yuzu.yuzu_emu.features.settings.ui.viewholder
import android.view.View import android.view.View
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
import org.yuzu.yuzu_emu.databinding.ListItemSettingSwitchBinding
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
@ -35,18 +33,4 @@ abstract class SettingViewHolder(itemView: View, protected val adapter: Settings
abstract override fun onClick(clicked: View) abstract override fun onClick(clicked: View)
abstract override fun onLongClick(clicked: View): Boolean abstract override fun onLongClick(clicked: View): Boolean
fun setStyle(isEditable: Boolean, binding: ListItemSettingBinding) {
val opacity = if (isEditable) 1.0f else 0.5f
binding.textSettingName.alpha = opacity
binding.textSettingDescription.alpha = opacity
binding.textSettingValue.alpha = opacity
}
fun setStyle(isEditable: Boolean, binding: ListItemSettingSwitchBinding) {
binding.switchWidget.isEnabled = isEditable
val opacity = if (isEditable) 1.0f else 0.5f
binding.textSettingName.alpha = opacity
binding.textSettingDescription.alpha = opacity
}
} }

View File

@ -17,33 +17,28 @@ class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: Setti
override fun bind(item: SettingsItem) { override fun bind(item: SettingsItem) {
setting = item setting = item
binding.textSettingName.setText(item.nameId) binding.textSettingName.setText(item.nameId)
binding.textSettingDescription.visibility = View.VISIBLE
if (item.descriptionId != 0) { if (item.descriptionId != 0) {
binding.textSettingDescription.setText(item.descriptionId) binding.textSettingDescription.setText(item.descriptionId)
binding.textSettingDescription.visibility = View.VISIBLE } else if (item is SingleChoiceSetting) {
} else { val resMgr = binding.textSettingDescription.context.resources
binding.textSettingDescription.visibility = View.GONE
}
binding.textSettingValue.visibility = View.VISIBLE
if (item is SingleChoiceSetting) {
val resMgr = binding.textSettingValue.context.resources
val values = resMgr.getIntArray(item.valuesId) val values = resMgr.getIntArray(item.valuesId)
for (i in values.indices) { for (i in values.indices) {
if (values[i] == item.selectedValue) { if (values[i] == item.selectedValue) {
binding.textSettingValue.text = resMgr.getStringArray(item.choicesId)[i] binding.textSettingDescription.text = resMgr.getStringArray(item.choicesId)[i]
break return
} }
} }
} else if (item is StringSingleChoiceSetting) { } else if (item is StringSingleChoiceSetting) {
for (i in item.values!!.indices) { for (i in item.values!!.indices) {
if (item.values[i] == item.selectedValue) { if (item.values[i] == item.selectedValue) {
binding.textSettingValue.text = item.choices[i] binding.textSettingDescription.text = item.choices[i]
break return
} }
} }
} else {
binding.textSettingDescription.visibility = View.GONE
} }
setStyle(setting.isEditable, binding)
} }
override fun onClick(clicked: View) { override fun onClick(clicked: View) {

View File

@ -4,7 +4,6 @@
package org.yuzu.yuzu_emu.features.settings.ui.viewholder package org.yuzu.yuzu_emu.features.settings.ui.viewholder
import android.view.View import android.view.View
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
import org.yuzu.yuzu_emu.features.settings.model.view.SliderSetting import org.yuzu.yuzu_emu.features.settings.model.view.SliderSetting
@ -23,14 +22,6 @@ class SliderViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAda
} else { } else {
binding.textSettingDescription.visibility = View.GONE binding.textSettingDescription.visibility = View.GONE
} }
binding.textSettingValue.visibility = View.VISIBLE
binding.textSettingValue.text = String.format(
binding.textSettingValue.context.getString(R.string.value_with_units),
setting.selectedValue,
setting.units
)
setStyle(setting.isEditable, binding)
} }
override fun onClick(clicked: View) { override fun onClick(clicked: View) {

View File

@ -22,7 +22,6 @@ class SubmenuViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAd
} else { } else {
binding.textSettingDescription.visibility = View.GONE binding.textSettingDescription.visibility = View.GONE
} }
binding.textSettingValue.visibility = View.GONE
} }
override fun onClick(clicked: View) { override fun onClick(clicked: View) {

View File

@ -25,12 +25,12 @@ class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter
binding.textSettingDescription.text = "" binding.textSettingDescription.text = ""
binding.textSettingDescription.visibility = View.GONE binding.textSettingDescription.visibility = View.GONE
} }
binding.switchWidget.isChecked = setting.isChecked
binding.switchWidget.setOnCheckedChangeListener { _: CompoundButton, _: Boolean -> binding.switchWidget.setOnCheckedChangeListener { _: CompoundButton, _: Boolean ->
adapter.onBooleanClick(item, bindingAdapterPosition, binding.switchWidget.isChecked) adapter.onBooleanClick(item, bindingAdapterPosition, binding.switchWidget.isChecked)
} }
binding.switchWidget.isChecked = setting.isChecked
setStyle(setting.isEditable, binding) binding.switchWidget.isEnabled = setting.isEditable
} }
override fun onClick(clicked: View) { override fun onClick(clicked: View) {

View File

@ -297,11 +297,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
emulationActivity?.let { emulationActivity?.let {
it.requestedOrientation = when (IntSetting.RENDERER_SCREEN_LAYOUT.int) { it.requestedOrientation = when (IntSetting.RENDERER_SCREEN_LAYOUT.int) {
Settings.LayoutOption_MobileLandscape -> Settings.LayoutOption_MobileLandscape ->
ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
Settings.LayoutOption_MobilePortrait -> Settings.LayoutOption_MobilePortrait ->
ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
Settings.LayoutOption_Unspecified -> ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED Settings.LayoutOption_Unspecified -> ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
else -> ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE else -> ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
} }
} }
} }

View File

@ -129,11 +129,7 @@ class HomeSettingsFragment : Fragment() {
mainActivity.getGamesDirectory.launch( mainActivity.getGamesDirectory.launch(
Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data
) )
}, }
{ true },
0,
0,
homeViewModel.gamesDir
) )
) )
add( add(
@ -205,11 +201,7 @@ class HomeSettingsFragment : Fragment() {
binding.homeSettingsList.apply { binding.homeSettingsList.apply {
layoutManager = LinearLayoutManager(requireContext()) layoutManager = LinearLayoutManager(requireContext())
adapter = HomeSettingAdapter( adapter = HomeSettingAdapter(requireActivity() as AppCompatActivity, optionsList)
requireActivity() as AppCompatActivity,
viewLifecycleOwner,
optionsList
)
} }
setInsets() setInsets()

View File

@ -19,7 +19,6 @@ import androidx.core.content.ContextCompat
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.navigation.findNavController import androidx.navigation.findNavController
@ -33,13 +32,10 @@ import org.yuzu.yuzu_emu.adapters.SetupAdapter
import org.yuzu.yuzu_emu.databinding.FragmentSetupBinding import org.yuzu.yuzu_emu.databinding.FragmentSetupBinding
import org.yuzu.yuzu_emu.features.settings.model.Settings import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.model.HomeViewModel import org.yuzu.yuzu_emu.model.HomeViewModel
import org.yuzu.yuzu_emu.model.SetupCallback
import org.yuzu.yuzu_emu.model.SetupPage import org.yuzu.yuzu_emu.model.SetupPage
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.GameHelper
import org.yuzu.yuzu_emu.utils.ViewUtils
class SetupFragment : Fragment() { class SetupFragment : Fragment() {
private var _binding: FragmentSetupBinding? = null private var _binding: FragmentSetupBinding? = null
@ -116,22 +112,14 @@ class SetupFragment : Fragment() {
0, 0,
false, false,
R.string.give_permission, R.string.give_permission,
{ { permissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) },
notificationCallback = it
permissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
},
true, true,
R.string.notification_warning, R.string.notification_warning,
R.string.notification_warning_description, R.string.notification_warning_description,
0, 0,
{ {
if (NotificationManagerCompat.from(requireContext()) NotificationManagerCompat.from(requireContext())
.areNotificationsEnabled() .areNotificationsEnabled()
) {
StepState.COMPLETE
} else {
StepState.INCOMPLETE
}
} }
) )
) )
@ -145,22 +133,12 @@ class SetupFragment : Fragment() {
R.drawable.ic_add, R.drawable.ic_add,
true, true,
R.string.select_keys, R.string.select_keys,
{ { mainActivity.getProdKey.launch(arrayOf("*/*")) },
keyCallback = it
getProdKey.launch(arrayOf("*/*"))
},
true, true,
R.string.install_prod_keys_warning, R.string.install_prod_keys_warning,
R.string.install_prod_keys_warning_description, R.string.install_prod_keys_warning_description,
R.string.install_prod_keys_warning_help, R.string.install_prod_keys_warning_help,
{ { File(DirectoryInitialization.userDirectory + "/keys/prod.keys").exists() }
val file = File(DirectoryInitialization.userDirectory + "/keys/prod.keys")
if (file.exists()) {
StepState.COMPLETE
} else {
StepState.INCOMPLETE
}
}
) )
) )
add( add(
@ -172,8 +150,9 @@ class SetupFragment : Fragment() {
true, true,
R.string.add_games, R.string.add_games,
{ {
gamesDirCallback = it mainActivity.getGamesDirectory.launch(
getGamesDirectory.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data) Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data
)
}, },
true, true,
R.string.add_games_warning, R.string.add_games_warning,
@ -184,11 +163,7 @@ class SetupFragment : Fragment() {
PreferenceManager.getDefaultSharedPreferences( PreferenceManager.getDefaultSharedPreferences(
YuzuApplication.appContext YuzuApplication.appContext
) )
if (preferences.getString(GameHelper.KEY_GAME_PATH, "")!!.isNotEmpty()) { preferences.getString(GameHelper.KEY_GAME_PATH, "")!!.isNotEmpty()
StepState.COMPLETE
} else {
StepState.INCOMPLETE
}
} }
) )
) )
@ -206,13 +181,6 @@ class SetupFragment : Fragment() {
) )
} }
homeViewModel.shouldPageForward.observe(viewLifecycleOwner) {
if (it) {
pageForward()
homeViewModel.setShouldPageForward(false)
}
}
binding.viewPager2.apply { binding.viewPager2.apply {
adapter = SetupAdapter(requireActivity() as AppCompatActivity, pages) adapter = SetupAdapter(requireActivity() as AppCompatActivity, pages)
offscreenPageLimit = 2 offscreenPageLimit = 2
@ -226,15 +194,15 @@ class SetupFragment : Fragment() {
super.onPageSelected(position) super.onPageSelected(position)
if (position == 1 && previousPosition == 0) { if (position == 1 && previousPosition == 0) {
ViewUtils.showView(binding.buttonNext) showView(binding.buttonNext)
ViewUtils.showView(binding.buttonBack) showView(binding.buttonBack)
} else if (position == 0 && previousPosition == 1) { } else if (position == 0 && previousPosition == 1) {
ViewUtils.hideView(binding.buttonBack) hideView(binding.buttonBack)
ViewUtils.hideView(binding.buttonNext) hideView(binding.buttonNext)
} else if (position == pages.size - 1 && previousPosition == pages.size - 2) { } else if (position == pages.size - 1 && previousPosition == pages.size - 2) {
ViewUtils.hideView(binding.buttonNext) hideView(binding.buttonNext)
} else if (position == pages.size - 2 && previousPosition == pages.size - 1) { } else if (position == pages.size - 2 && previousPosition == pages.size - 1) {
ViewUtils.showView(binding.buttonNext) showView(binding.buttonNext)
} }
previousPosition = position previousPosition = position
@ -247,8 +215,7 @@ class SetupFragment : Fragment() {
// Checks if the user has completed the task on the current page // Checks if the user has completed the task on the current page
if (currentPage.hasWarning) { if (currentPage.hasWarning) {
val stepState = currentPage.stepCompleted.invoke() if (currentPage.taskCompleted.invoke()) {
if (stepState != StepState.INCOMPLETE) {
pageForward() pageForward()
return@setOnClickListener return@setOnClickListener
} }
@ -297,15 +264,9 @@ class SetupFragment : Fragment() {
_binding = null _binding = null
} }
private lateinit var notificationCallback: SetupCallback
@RequiresApi(Build.VERSION_CODES.TIRAMISU) @RequiresApi(Build.VERSION_CODES.TIRAMISU)
private val permissionLauncher = private val permissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { registerForActivityResult(ActivityResultContracts.RequestPermission()) {
if (it) {
notificationCallback.onStepCompleted()
}
if (!it && if (!it &&
!shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS) !shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS)
) { ) {
@ -316,27 +277,6 @@ class SetupFragment : Fragment() {
} }
} }
private lateinit var keyCallback: SetupCallback
val getProdKey =
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
if (result != null) {
if (mainActivity.processKey(result)) {
keyCallback.onStepCompleted()
}
}
}
private lateinit var gamesDirCallback: SetupCallback
val getGamesDirectory =
registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result ->
if (result != null) {
mainActivity.processGamesDir(result)
gamesDirCallback.onStepCompleted()
}
}
private fun finishSetup() { private fun finishSetup() {
PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext).edit() PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext).edit()
.putBoolean(Settings.PREF_FIRST_APP_LAUNCH, false) .putBoolean(Settings.PREF_FIRST_APP_LAUNCH, false)
@ -344,6 +284,33 @@ class SetupFragment : Fragment() {
mainActivity.finishSetup(binding.root.findNavController()) mainActivity.finishSetup(binding.root.findNavController())
} }
private fun showView(view: View) {
view.apply {
alpha = 0f
visibility = View.VISIBLE
isClickable = true
}.animate().apply {
duration = 300
alpha(1f)
}.start()
}
private fun hideView(view: View) {
if (view.visibility == View.INVISIBLE) {
return
}
view.apply {
alpha = 1f
isClickable = false
}.animate().apply {
duration = 300
alpha(0f)
}.withEndAction {
view.visibility = View.INVISIBLE
}
}
fun pageForward() { fun pageForward() {
binding.viewPager2.currentItem = binding.viewPager2.currentItem + 1 binding.viewPager2.currentItem = binding.viewPager2.currentItem + 1
} }
@ -359,29 +326,15 @@ class SetupFragment : Fragment() {
private fun setInsets() = private fun setInsets() =
ViewCompat.setOnApplyWindowInsetsListener( ViewCompat.setOnApplyWindowInsetsListener(
binding.root binding.root
) { _: View, windowInsets: WindowInsetsCompat -> ) { view: View, windowInsets: WindowInsetsCompat ->
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
view.setPadding(
val leftPadding = barInsets.left + cutoutInsets.left barInsets.left + cutoutInsets.left,
val topPadding = barInsets.top + cutoutInsets.top barInsets.top + cutoutInsets.top,
val rightPadding = barInsets.right + cutoutInsets.right barInsets.right + cutoutInsets.right,
val bottomPadding = barInsets.bottom + cutoutInsets.bottom barInsets.bottom + cutoutInsets.bottom
if (resources.getBoolean(R.bool.small_layout)) {
binding.viewPager2
.updatePadding(left = leftPadding, top = topPadding, right = rightPadding)
binding.constraintButtons
.updatePadding(left = leftPadding, right = rightPadding, bottom = bottomPadding)
} else {
binding.viewPager2.updatePadding(top = topPadding, bottom = bottomPadding)
binding.constraintButtons
.updatePadding(
left = leftPadding,
right = rightPadding,
bottom = bottomPadding
) )
}
windowInsets windowInsets
} }
} }

View File

@ -3,9 +3,6 @@
package org.yuzu.yuzu_emu.model package org.yuzu.yuzu_emu.model
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
data class HomeSetting( data class HomeSetting(
val titleId: Int, val titleId: Int,
val descriptionId: Int, val descriptionId: Int,
@ -13,6 +10,5 @@ data class HomeSetting(
val onClick: () -> Unit, val onClick: () -> Unit,
val isEnabled: () -> Boolean = { true }, val isEnabled: () -> Boolean = { true },
val disabledTitleId: Int = 0, val disabledTitleId: Int = 0,
val disabledMessageId: Int = 0, val disabledMessageId: Int = 0
val details: LiveData<String> = MutableLiveData("")
) )

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.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.preference.PreferenceManager
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.utils.GameHelper
class HomeViewModel : ViewModel() { class HomeViewModel : ViewModel() {
private val _navigationVisible = MutableLiveData<Pair<Boolean, Boolean>>() private val _navigationVisible = MutableLiveData<Pair<Boolean, Boolean>>()
@ -20,17 +14,6 @@ class HomeViewModel : ViewModel() {
private val _statusBarShadeVisible = MutableLiveData(true) private val _statusBarShadeVisible = MutableLiveData(true)
val statusBarShadeVisible: LiveData<Boolean> get() = _statusBarShadeVisible val statusBarShadeVisible: LiveData<Boolean> get() = _statusBarShadeVisible
private val _shouldPageForward = MutableLiveData(false)
val shouldPageForward: LiveData<Boolean> get() = _shouldPageForward
private val _gamesDir = MutableLiveData(
Uri.parse(
PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
.getString(GameHelper.KEY_GAME_PATH, "")
).path ?: ""
)
val gamesDir: LiveData<String> get() = _gamesDir
var navigatedToSetup = false var navigatedToSetup = false
init { init {
@ -50,13 +33,4 @@ class HomeViewModel : ViewModel() {
} }
_statusBarShadeVisible.value = visible _statusBarShadeVisible.value = visible
} }
fun setShouldPageForward(pageForward: Boolean) {
_shouldPageForward.value = pageForward
}
fun setGamesDir(activity: FragmentActivity, dir: String) {
ViewModelProvider(activity)[GamesViewModel::class.java].reloadGames(true)
_gamesDir.value = dir
}
} }

View File

@ -10,20 +10,10 @@ data class SetupPage(
val buttonIconId: Int, val buttonIconId: Int,
val leftAlignedIcon: Boolean, val leftAlignedIcon: Boolean,
val buttonTextId: Int, val buttonTextId: Int,
val buttonAction: (callback: SetupCallback) -> Unit, val buttonAction: () -> Unit,
val hasWarning: Boolean, val hasWarning: Boolean,
val warningTitleId: Int = 0, val warningTitleId: Int = 0,
val warningDescriptionId: Int = 0, val warningDescriptionId: Int = 0,
val warningHelpLinkId: Int = 0, val warningHelpLinkId: Int = 0,
val stepCompleted: () -> StepState = { StepState.UNDEFINED } val taskCompleted: () -> Boolean = { true }
) )
interface SetupCallback {
fun onStepCompleted()
}
enum class StepState {
COMPLETE,
INCOMPLETE,
UNDEFINED
}

View File

@ -266,12 +266,10 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
val getGamesDirectory = val getGamesDirectory =
registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result -> registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result ->
if (result != null) { if (result == null) {
processGamesDir(result) return@registerForActivityResult
}
} }
fun processGamesDir(result: Uri) {
contentResolver.takePersistableUriPermission( contentResolver.takePersistableUriPermission(
result, result,
Intent.FLAG_GRANT_READ_URI_PERMISSION Intent.FLAG_GRANT_READ_URI_PERMISSION
@ -290,23 +288,20 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
).show() ).show()
gamesViewModel.reloadGames(true) gamesViewModel.reloadGames(true)
homeViewModel.setGamesDir(this, result.path!!)
} }
val getProdKey = val getProdKey =
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
if (result != null) { if (result == null) {
processKey(result) return@registerForActivityResult
}
} }
fun processKey(result: Uri): Boolean {
if (FileUtil.getExtension(result) != "keys") { if (FileUtil.getExtension(result) != "keys") {
MessageDialogFragment.newInstance( MessageDialogFragment.newInstance(
R.string.reading_keys_failure, R.string.reading_keys_failure,
R.string.install_prod_keys_failure_extension_description R.string.install_prod_keys_failure_extension_description
).show(supportFragmentManager, MessageDialogFragment.TAG) ).show(supportFragmentManager, MessageDialogFragment.TAG)
return false return@registerForActivityResult
} }
contentResolver.takePersistableUriPermission( contentResolver.takePersistableUriPermission(
@ -329,17 +324,14 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
Toast.LENGTH_SHORT Toast.LENGTH_SHORT
).show() ).show()
gamesViewModel.reloadGames(true) gamesViewModel.reloadGames(true)
return true
} else { } else {
MessageDialogFragment.newInstance( MessageDialogFragment.newInstance(
R.string.invalid_keys_error, R.string.invalid_keys_error,
R.string.install_keys_failure_description, R.string.install_keys_failure_description,
R.string.dumping_keys_quickstart_link R.string.dumping_keys_quickstart_link
).show(supportFragmentManager, MessageDialogFragment.TAG) ).show(supportFragmentManager, MessageDialogFragment.TAG)
return false
} }
} }
return false
} }
val getFirmware = val getFirmware =

View File

@ -11,7 +11,6 @@ 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.MinimalDocumentFile
object GameHelper { object GameHelper {
const val KEY_GAME_PATH = "game_path" const val KEY_GAME_PATH = "game_path"
@ -30,7 +29,15 @@ object GameHelper {
// 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(context, gamesUri), 3) val children = FileUtil.listFiles(context, gamesUri)
for (file in children) {
if (!file.isDirectory) {
// Check that the file has an extension we care about before trying to read out of it.
if (Game.extensions.contains(FileUtil.getExtension(file.uri))) {
games.add(getGame(file.uri))
}
}
}
// Cache list of games found on disk // Cache list of games found on disk
val serializedGames = mutableSetOf<String>() val serializedGames = mutableSetOf<String>()
@ -45,30 +52,6 @@ object GameHelper {
return games.toList() return games.toList()
} }
private fun addGamesRecursive(
games: MutableList<Game>,
files: Array<MinimalDocumentFile>,
depth: Int
) {
if (depth <= 0) {
return
}
files.forEach {
if (it.isDirectory) {
addGamesRecursive(
games,
FileUtil.listFiles(YuzuApplication.appContext, it.uri),
depth - 1
)
} else {
if (Game.extensions.contains(FileUtil.getExtension(it.uri))) {
games.add(getGame(it.uri))
}
}
}
}
private fun getGame(uri: Uri): Game { private fun getGame(uri: Uri): Game {
val filePath = uri.toString() val filePath = uri.toString()
var name = NativeLibrary.getTitle(filePath) var name = NativeLibrary.getTitle(filePath)

View File

@ -1,35 +0,0 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.utils
import android.view.View
object ViewUtils {
fun showView(view: View, length: Long = 300) {
view.apply {
alpha = 0f
visibility = View.VISIBLE
isClickable = true
}.animate().apply {
duration = length
alpha(1f)
}.start()
}
fun hideView(view: View, length: Long = 300) {
if (view.visibility == View.INVISIBLE) {
return
}
view.apply {
alpha = 1f
isClickable = false
}.animate().apply {
duration = length
alpha(0f)
}.withEndAction {
view.visibility = View.INVISIBLE
}.start()
}
}

View File

@ -11,7 +11,6 @@
#include "common/fs/path_util.h" #include "common/fs/path_util.h"
#include "common/logging/log.h" #include "common/logging/log.h"
#include "common/settings.h" #include "common/settings.h"
#include "common/settings_enums.h"
#include "core/hle/service/acc/profile_manager.h" #include "core/hle/service/acc/profile_manager.h"
#include "input_common/main.h" #include "input_common/main.h"
#include "jni/config.h" #include "jni/config.h"
@ -145,25 +144,21 @@ void Config::ReadValues() {
Service::Account::MAX_USERS - 1); Service::Account::MAX_USERS - 1);
// Disable docked mode by default on Android // Disable docked mode by default on Android
Settings::values.use_docked_mode.SetValue(config->GetBoolean("System", "use_docked_mode", false) Settings::values.use_docked_mode = 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); const auto rng_seed_enabled = config->GetBoolean("System", "rng_seed_enabled", false);
if (rng_seed_enabled) { if (rng_seed_enabled) {
Settings::values.rng_seed.SetValue(config->GetInteger("System", "rng_seed", 0)); Settings::values.rng_seed.SetValue(config->GetInteger("System", "rng_seed", 0));
} else { } else {
Settings::values.rng_seed.SetValue(0); Settings::values.rng_seed.SetValue(std::nullopt);
} }
Settings::values.rng_seed_enabled.SetValue(rng_seed_enabled);
const auto custom_rtc_enabled = config->GetBoolean("System", "custom_rtc_enabled", false); const auto custom_rtc_enabled = config->GetBoolean("System", "custom_rtc_enabled", false);
if (custom_rtc_enabled) { if (custom_rtc_enabled) {
Settings::values.custom_rtc = config->GetInteger("System", "custom_rtc", 0); Settings::values.custom_rtc = config->GetInteger("System", "custom_rtc", 0);
} else { } else {
Settings::values.custom_rtc = 0; Settings::values.custom_rtc = std::nullopt;
} }
Settings::values.custom_rtc_enabled = custom_rtc_enabled;
ReadSetting("System", Settings::values.language_index); ReadSetting("System", Settings::values.language_index);
ReadSetting("System", Settings::values.region_index); ReadSetting("System", Settings::values.region_index);
@ -172,7 +167,7 @@ void Config::ReadValues() {
// Core // Core
ReadSetting("Core", Settings::values.use_multi_core); ReadSetting("Core", Settings::values.use_multi_core);
ReadSetting("Core", Settings::values.memory_layout_mode); ReadSetting("Core", Settings::values.use_unsafe_extended_memory_layout);
// Cpu // Cpu
ReadSetting("Cpu", Settings::values.cpu_accuracy); ReadSetting("Cpu", Settings::values.cpu_accuracy);
@ -227,17 +222,14 @@ void Config::ReadValues() {
ReadSetting("Renderer", Settings::values.bg_blue); ReadSetting("Renderer", Settings::values.bg_blue);
// Use GPU accuracy normal by default on Android // Use GPU accuracy normal by default on Android
Settings::values.gpu_accuracy = static_cast<Settings::GpuAccuracy>(config->GetInteger( Settings::values.gpu_accuracy = static_cast<Settings::GPUAccuracy>(config->GetInteger(
"Renderer", "gpu_accuracy", static_cast<u32>(Settings::GpuAccuracy::Normal))); "Renderer", "gpu_accuracy", static_cast<u32>(Settings::GPUAccuracy::Normal)));
// Use GPU default anisotropic filtering on Android // Use GPU default anisotropic filtering on Android
Settings::values.max_anisotropy = Settings::values.max_anisotropy = config->GetInteger("Renderer", "max_anisotropy", 1);
static_cast<Settings::AnisotropyMode>(config->GetInteger("Renderer", "max_anisotropy", 1));
// Disable ASTC compute by default on Android // Disable ASTC compute by default on Android
Settings::values.accelerate_astc.SetValue( Settings::values.accelerate_astc = config->GetBoolean("Renderer", "accelerate_astc", false);
config->GetBoolean("Renderer", "accelerate_astc", false) ? Settings::AstcDecodeMode::Gpu
: Settings::AstcDecodeMode::Cpu);
// Enable asynchronous presentation by default on Android // Enable asynchronous presentation by default on Android
Settings::values.async_presentation = Settings::values.async_presentation =

View File

@ -30,7 +30,6 @@
#include "core/cpu_manager.h" #include "core/cpu_manager.h"
#include "core/crypto/key_manager.h" #include "core/crypto/key_manager.h"
#include "core/file_sys/card_image.h" #include "core/file_sys/card_image.h"
#include "core/file_sys/content_archive.h"
#include "core/file_sys/registered_cache.h" #include "core/file_sys/registered_cache.h"
#include "core/file_sys/submission_package.h" #include "core/file_sys/submission_package.h"
#include "core/file_sys/vfs.h" #include "core/file_sys/vfs.h"
@ -225,42 +224,6 @@ public:
m_system.Renderer().NotifySurfaceChanged(); m_system.Renderer().NotifySurfaceChanged();
} }
void ConfigureFilesystemProvider(const std::string& filepath) {
const auto file = m_system.GetFilesystem()->OpenFile(filepath, FileSys::Mode::Read);
if (!file) {
return;
}
auto loader = Loader::GetLoader(m_system, file);
if (!loader) {
return;
}
const auto file_type = loader->GetFileType();
if (file_type == Loader::FileType::Unknown || file_type == Loader::FileType::Error) {
return;
}
u64 program_id = 0;
const auto res2 = loader->ReadProgramId(program_id);
if (res2 == Loader::ResultStatus::Success && file_type == Loader::FileType::NCA) {
m_manual_provider->AddEntry(FileSys::TitleType::Application,
FileSys::GetCRTypeFromNCAType(FileSys::NCA{file}.GetType()),
program_id, file);
} else if (res2 == Loader::ResultStatus::Success &&
(file_type == Loader::FileType::XCI || file_type == Loader::FileType::NSP)) {
const auto nsp = file_type == Loader::FileType::NSP
? std::make_shared<FileSys::NSP>(file)
: FileSys::XCI{file}.GetSecurePartitionNSP();
for (const auto& title : nsp->GetNCAs()) {
for (const auto& entry : title.second) {
m_manual_provider->AddEntry(entry.first.first, entry.first.second, title.first,
entry.second->GetBaseFile());
}
}
}
}
Core::SystemResultStatus InitializeEmulation(const std::string& filepath) { Core::SystemResultStatus InitializeEmulation(const std::string& filepath) {
std::scoped_lock lock(m_mutex); std::scoped_lock lock(m_mutex);
@ -291,14 +254,8 @@ public:
std::move(android_keyboard), // Software Keyboard std::move(android_keyboard), // Software Keyboard
nullptr, // Web Browser nullptr, // Web Browser
}); });
// Initialize filesystem.
m_manual_provider = std::make_unique<FileSys::ManualContentProvider>();
m_system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>()); m_system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
m_system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::FrontendManual,
m_manual_provider.get());
m_system.GetFileSystemController().CreateFactories(*m_vfs); m_system.GetFileSystemController().CreateFactories(*m_vfs);
ConfigureFilesystemProvider(filepath);
// Initialize account manager // Initialize account manager
m_profile_manager = std::make_unique<Service::Account::ProfileManager>(); m_profile_manager = std::make_unique<Service::Account::ProfileManager>();
@ -420,7 +377,7 @@ public:
return false; return false;
} }
return !Settings::IsDockedMode(); return !Settings::values.use_docked_mode.GetValue();
} }
void SetDeviceType([[maybe_unused]] int index, int type) { void SetDeviceType([[maybe_unused]] int index, int type) {
@ -532,7 +489,6 @@ private:
bool m_is_paused{}; bool m_is_paused{};
SoftwareKeyboard::AndroidKeyboard* m_software_keyboard{}; SoftwareKeyboard::AndroidKeyboard* m_software_keyboard{};
std::unique_ptr<Service::Account::ProfileManager> m_profile_manager; std::unique_ptr<Service::Account::ProfileManager> m_profile_manager;
std::unique_ptr<FileSys::ManualContentProvider> m_manual_provider;
// GPU driver parameters // GPU driver parameters
std::shared_ptr<Common::DynamicLibrary> m_vulkan_library; std::shared_ptr<Common::DynamicLibrary> m_vulkan_library;

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout <androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/setup_root" android:id="@+id/setup_root"
@ -8,24 +8,19 @@
<androidx.viewpager2.widget.ViewPager2 <androidx.viewpager2.widget.ViewPager2
android:id="@+id/viewPager2" android:id="@+id/viewPager2"
android:layout_width="match_parent" android:layout_width="0dp"
android:layout_height="match_parent" android:layout_height="0dp"
android:layout_alignParentTop="true" app:layout_constraintBottom_toBottomOf="parent"
android:layout_alignParentBottom="true" app:layout_constraintEnd_toEndOf="parent"
android:clipToPadding="false" /> app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/constraint_buttons"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_margin="8dp">
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/button_next"
style="@style/Widget.Material3.Button.TextButton" style="@style/Widget.Material3.Button.TextButton"
android:id="@+id/button_next"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="16dp"
android:text="@string/next" android:text="@string/next"
android:visibility="invisible" android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
@ -36,11 +31,10 @@
style="@style/Widget.Material3.Button.TextButton" style="@style/Widget.Material3.Button.TextButton"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="16dp"
android:text="@string/back" android:text="@string/back"
android:visibility="invisible" android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" /> app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</RelativeLayout>

View File

@ -21,76 +21,45 @@
</LinearLayout> </LinearLayout>
<androidx.constraintlayout.widget.ConstraintLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_weight="1"> android:layout_weight="1"
android:orientation="vertical"
android:gravity="center">
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
android:id="@+id/text_title"
style="@style/TextAppearance.Material3.DisplaySmall" style="@style/TextAppearance.Material3.DisplaySmall"
android:layout_width="0dp" android:id="@+id/text_title"
android:layout_height="0dp" android:layout_width="match_parent"
android:gravity="center" android:layout_height="wrap_content"
android:textAlignment="center"
android:textColor="?attr/colorOnSurface" android:textColor="?attr/colorOnSurface"
android:textStyle="bold" android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/text_description"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_weight="2"
tools:text="@string/welcome" /> tools:text="@string/welcome" />
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
style="@style/TextAppearance.Material3.TitleLarge"
android:id="@+id/text_description" android:id="@+id/text_description"
style="@style/TextAppearance.Material3.TitleLarge" android:layout_width="match_parent"
android:layout_width="0dp" android:layout_height="wrap_content"
android:layout_height="0dp" android:layout_marginTop="16dp"
android:gravity="center" android:paddingHorizontal="32dp"
android:textSize="20sp" android:textAlignment="center"
android:paddingHorizontal="16dp" android:textSize="26sp"
app:layout_constraintBottom_toTopOf="@+id/button_action" app:lineHeight="40sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_title"
app:layout_constraintVertical_weight="2"
app:lineHeight="30sp"
tools:text="@string/welcome_description" /> tools:text="@string/welcome_description" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/text_confirmation"
style="@style/TextAppearance.Material3.TitleLarge"
android:layout_width="0dp"
android:layout_height="0dp"
android:paddingHorizontal="16dp"
android:paddingBottom="20dp"
android:gravity="center"
android:textSize="30sp"
android:visibility="invisible"
android:text="@string/step_complete"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_description"
app:layout_constraintVertical_weight="1"
app:lineHeight="30sp" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/button_action" android:id="@+id/button_action"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="56dp" android:layout_height="56dp"
android:layout_marginTop="16dp" android:layout_marginTop="32dp"
android:layout_marginBottom="48dp"
android:textSize="20sp" android:textSize="20sp"
app:iconGravity="end"
app:iconSize="24sp" app:iconSize="24sp"
app:layout_constraintBottom_toBottomOf="parent" app:iconGravity="end"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_description"
tools:text="Get started" /> tools:text="Get started" />
</androidx.constraintlayout.widget.ConstraintLayout> </LinearLayout>
</LinearLayout> </LinearLayout>

View File

@ -53,23 +53,6 @@
android:layout_marginTop="5dp" android:layout_marginTop="5dp"
tools:text="@string/install_prod_keys_description" /> tools:text="@string/install_prod_keys_description" />
<com.google.android.material.textview.MaterialTextView
style="@style/TextAppearance.Material3.LabelMedium"
android:id="@+id/option_detail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAlignment="viewStart"
android:textSize="14sp"
android:textStyle="bold"
android:singleLine="true"
android:marqueeRepeatLimit="marquee_forever"
android:ellipsize="none"
android:requiresFadingEdge="horizontal"
android:layout_marginTop="5dp"
android:visibility="gone"
tools:visibility="visible"
tools:text="/tree/primary:Games" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>

View File

@ -5,16 +5,23 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:orientation="vertical">
<com.google.android.material.textview.MaterialTextView <TextView
android:id="@+id/text_value" android:id="@+id/text_value"
style="@style/TextAppearance.Material3.LabelMedium"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentTop="true" android:layout_alignParentTop="true"
android:layout_centerHorizontal="true" android:layout_centerHorizontal="true"
android:layout_marginBottom="@dimen/spacing_medlarge" android:layout_marginBottom="@dimen/spacing_medlarge"
android:layout_marginTop="@dimen/spacing_medlarge" android:layout_marginTop="@dimen/spacing_medlarge"
tools:text="75%" /> tools:text="75" />
<TextView
android:id="@+id/text_units"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@+id/text_value"
android:layout_toEndOf="@+id/text_value"
tools:text="%" />
<com.google.android.material.slider.Slider <com.google.android.material.slider.Slider
android:id="@+id/slider" android:id="@+id/slider"

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout <androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/setup_root" android:id="@+id/setup_root"
@ -8,39 +8,35 @@
<androidx.viewpager2.widget.ViewPager2 <androidx.viewpager2.widget.ViewPager2
android:id="@+id/viewPager2" android:id="@+id/viewPager2"
android:layout_width="match_parent" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="0dp"
android:layout_above="@+id/constraint_buttons" android:clipToPadding="false"
android:layout_alignParentTop="true" android:layout_marginBottom="16dp"
android:clipToPadding="false" /> app:layout_constraintBottom_toTopOf="@+id/button_next"
app:layout_constraintEnd_toEndOf="parent"
<androidx.constraintlayout.widget.ConstraintLayout app:layout_constraintStart_toStartOf="parent"
android:id="@+id/constraint_buttons" app:layout_constraintTop_toTopOf="parent" />
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:layout_alignParentBottom="true">
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/button_next"
style="@style/Widget.Material3.Button.TextButton" style="@style/Widget.Material3.Button.TextButton"
android:id="@+id/button_next"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="12dp"
android:text="@string/next" android:text="@string/next"
android:visibility="invisible" android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" /> app:layout_constraintEnd_toEndOf="parent" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/button_back"
style="@style/Widget.Material3.Button.TextButton" style="@style/Widget.Material3.Button.TextButton"
android:id="@+id/button_back"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="12dp"
android:text="@string/back" android:text="@string/back"
android:visibility="invisible" android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" /> app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</RelativeLayout>

View File

@ -1,10 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
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" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:background="?android:attr/selectableItemBackground" android:background="?android:attr/selectableItemBackground"
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
@ -12,40 +11,31 @@
android:minHeight="72dp" android:minHeight="72dp"
android:padding="@dimen/spacing_large"> android:padding="@dimen/spacing_large">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
android:id="@+id/text_setting_name"
style="@style/TextAppearance.Material3.HeadlineMedium" style="@style/TextAppearance.Material3.HeadlineMedium"
android:layout_width="match_parent" android:id="@+id/text_setting_name"
android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textAlignment="viewStart" android:layout_alignParentEnd="true"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:textSize="16sp" android:textSize="16sp"
app:lineHeight="22dp" android:textAlignment="viewStart"
app:lineHeight="28dp"
tools:text="Setting Name" /> tools:text="Setting Name" />
<com.google.android.material.textview.MaterialTextView <TextView
android:id="@+id/text_setting_description"
style="@style/TextAppearance.Material3.BodySmall" style="@style/TextAppearance.Material3.BodySmall"
android:layout_width="match_parent" android:id="@+id/text_setting_description"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentStart="true"
android:layout_alignStart="@+id/text_setting_name"
android:layout_below="@+id/text_setting_name"
android:layout_marginTop="@dimen/spacing_small" android:layout_marginTop="@dimen/spacing_small"
android:visibility="visible"
android:textAlignment="viewStart" android:textAlignment="viewStart"
tools:text="@string/app_disclaimer" /> tools:text="@string/app_disclaimer" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/text_setting_value"
style="@style/TextAppearance.Material3.LabelMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/spacing_small"
android:textAlignment="viewStart"
android:textStyle="bold"
tools:text="1x" />
</LinearLayout>
</RelativeLayout> </RelativeLayout>

View File

@ -21,12 +21,11 @@
app:layout_constraintVertical_chainStyle="spread" app:layout_constraintVertical_chainStyle="spread"
app:layout_constraintWidth_max="220dp" app:layout_constraintWidth_max="220dp"
app:layout_constraintWidth_min="110dp" app:layout_constraintWidth_min="110dp"
app:layout_constraintVertical_weight="3" app:layout_constraintVertical_weight="3" />
tools:src="@drawable/ic_notification" />
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
android:id="@+id/text_title" android:id="@+id/text_title"
style="@style/TextAppearance.Material3.DisplaySmall" style="@style/TextAppearance.Material3.DisplayMedium"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
android:textAlignment="center" android:textAlignment="center"
@ -45,42 +44,23 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
android:textAlignment="center" android:textAlignment="center"
android:textSize="20sp" android:textSize="26sp"
android:paddingHorizontal="16dp" android:paddingHorizontal="16dp"
app:layout_constraintBottom_toTopOf="@+id/button_action" app:layout_constraintBottom_toTopOf="@+id/button_action"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_title" app:layout_constraintTop_toBottomOf="@+id/text_title"
app:layout_constraintVertical_weight="2" app:layout_constraintVertical_weight="2"
app:lineHeight="30sp" app:lineHeight="40sp"
tools:text="@string/welcome_description" /> tools:text="@string/welcome_description" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/text_confirmation"
style="@style/TextAppearance.Material3.TitleLarge"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:paddingHorizontal="16dp"
android:paddingTop="24dp"
android:textAlignment="center"
android:textSize="30sp"
android:visibility="invisible"
android:text="@string/step_complete"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_description"
app:layout_constraintVertical_weight="1"
app:lineHeight="30sp" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/button_action" android:id="@+id/button_action"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="56dp" android:layout_height="56dp"
android:textSize="20sp"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:layout_marginBottom="48dp" android:layout_marginBottom="48dp"
android:textSize="20sp"
app:iconGravity="end" app:iconGravity="end"
app:iconSize="24sp" app:iconSize="24sp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"

View File

@ -29,7 +29,6 @@
<string name="back">Back</string> <string name="back">Back</string>
<string name="add_games">Add Games</string> <string name="add_games">Add Games</string>
<string name="add_games_description">Select your games folder</string> <string name="add_games_description">Select your games folder</string>
<string name="step_complete">Complete!</string>
<!-- Home strings --> <!-- Home strings -->
<string name="home_games">Games</string> <string name="home_games">Games</string>
@ -150,7 +149,6 @@
<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_accuracy">CPU accuracy</string> <string name="cpu_accuracy">CPU accuracy</string>
<string name="value_with_units">%1$s%2$s</string>
<!-- System settings strings --> <!-- System settings strings -->
<string name="use_docked_mode">Docked Mode</string> <string name="use_docked_mode">Docked Mode</string>

View File

@ -8,7 +8,6 @@
#include "audio_core/sink/cubeb_sink.h" #include "audio_core/sink/cubeb_sink.h"
#include "audio_core/sink/sink_stream.h" #include "audio_core/sink/sink_stream.h"
#include "common/logging/log.h" #include "common/logging/log.h"
#include "common/scope_exit.h"
#include "core/core.h" #include "core/core.h"
#ifdef _WIN32 #ifdef _WIN32
@ -333,38 +332,25 @@ std::vector<std::string> ListCubebSinkDevices(bool capture) {
return device_list; return device_list;
} }
namespace { u32 GetCubebLatency() {
static long TmpDataCallback(cubeb_stream*, void*, const void*, void*, long) { cubeb* ctx;
return TargetSampleCount;
}
static void TmpStateCallback(cubeb_stream*, void*, cubeb_state) {}
} // namespace
bool IsCubebSuitable() {
#if !defined(HAVE_CUBEB)
return false;
#else
cubeb* ctx{nullptr};
#ifdef _WIN32 #ifdef _WIN32
auto com_init_result = CoInitializeEx(nullptr, COINIT_MULTITHREADED); auto com_init_result = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
#endif #endif
// Init cubeb
if (cubeb_init(&ctx, "yuzu Latency Getter", nullptr) != CUBEB_OK) { if (cubeb_init(&ctx, "yuzu Latency Getter", nullptr) != CUBEB_OK) {
LOG_ERROR(Audio_Sink, "Cubeb failed to init, it is not suitable."); LOG_CRITICAL(Audio_Sink, "cubeb_init failed");
return false; // Return a large latency so we choose SDL instead.
return 10000u;
} }
SCOPE_EXIT({ cubeb_destroy(ctx); });
#ifdef _WIN32 #ifdef _WIN32
if (SUCCEEDED(com_init_result)) { if (SUCCEEDED(com_init_result)) {
CoUninitialize(); CoUninitialize();
} }
#endif #endif
// Get min latency
cubeb_stream_params params{}; cubeb_stream_params params{};
params.rate = TargetSampleRate; params.rate = TargetSampleRate;
params.channels = 2; params.channels = 2;
@ -375,27 +361,12 @@ bool IsCubebSuitable() {
u32 latency{0}; u32 latency{0};
const auto latency_error = cubeb_get_min_latency(ctx, &params, &latency); const auto latency_error = cubeb_get_min_latency(ctx, &params, &latency);
if (latency_error != CUBEB_OK) { if (latency_error != CUBEB_OK) {
LOG_ERROR(Audio_Sink, "Cubeb could not get min latency, it is not suitable."); LOG_CRITICAL(Audio_Sink, "Error getting minimum latency, error: {}", latency_error);
return false; latency = TargetSampleCount * 2;
} }
latency = std::max(latency, TargetSampleCount * 2); latency = std::max(latency, TargetSampleCount * 2);
cubeb_destroy(ctx);
// Test opening a device with standard parameters return latency;
cubeb_devid output_device{0};
cubeb_devid input_device{0};
std::string name{"Yuzu test"};
cubeb_stream* stream{nullptr};
if (cubeb_stream_init(ctx, &stream, name.c_str(), input_device, nullptr, output_device, &params,
latency, &TmpDataCallback, &TmpStateCallback, nullptr) != CUBEB_OK) {
LOG_CRITICAL(Audio_Sink, "Cubeb could not open a device, it is not suitable.");
return false;
}
cubeb_stream_stop(stream);
cubeb_stream_destroy(stream);
return true;
#endif
} }
} // namespace AudioCore::Sink } // namespace AudioCore::Sink

View File

@ -97,11 +97,10 @@ private:
std::vector<std::string> ListCubebSinkDevices(bool capture); std::vector<std::string> ListCubebSinkDevices(bool capture);
/** /**
* Check if this backend is suitable for use. * Get the reported latency for this sink.
* Checks if enabled, its latency, whether it opens successfully, etc.
* *
* @return True is this backend is suitable, false otherwise. * @return Minimum latency for this sink.
*/ */
bool IsCubebSuitable(); u32 GetCubebLatency();
} // namespace AudioCore::Sink } // namespace AudioCore::Sink

View File

@ -9,7 +9,6 @@
#include "audio_core/sink/sdl2_sink.h" #include "audio_core/sink/sdl2_sink.h"
#include "audio_core/sink/sink_stream.h" #include "audio_core/sink/sink_stream.h"
#include "common/logging/log.h" #include "common/logging/log.h"
#include "common/scope_exit.h"
#include "core/core.h" #include "core/core.h"
namespace AudioCore::Sink { namespace AudioCore::Sink {
@ -85,7 +84,6 @@ public:
} }
Stop(); Stop();
SDL_ClearQueuedAudio(device);
SDL_CloseAudioDevice(device); SDL_CloseAudioDevice(device);
} }
@ -229,42 +227,8 @@ std::vector<std::string> ListSDLSinkDevices(bool capture) {
return device_list; return device_list;
} }
bool IsSDLSuitable() { u32 GetSDLLatency() {
#if !defined(HAVE_SDL2) return TargetSampleCount * 2;
return false;
#else
// Check SDL can init
if (!SDL_WasInit(SDL_INIT_AUDIO)) {
if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) {
LOG_ERROR(Audio_Sink, "SDL failed to init, it is not suitable. Error: {}",
SDL_GetError());
return false;
}
}
// We can set any latency frequency we want with SDL, so no need to check that.
// Check we can open a device with standard parameters
SDL_AudioSpec spec;
spec.freq = TargetSampleRate;
spec.channels = 2u;
spec.format = AUDIO_S16SYS;
spec.samples = TargetSampleCount * 2;
spec.callback = nullptr;
spec.userdata = nullptr;
SDL_AudioSpec obtained;
auto device = SDL_OpenAudioDevice(nullptr, false, &spec, &obtained, false);
if (device == 0) {
LOG_ERROR(Audio_Sink, "SDL failed to open a device, it is not suitable. Error: {}",
SDL_GetError());
return false;
}
SDL_CloseAudioDevice(device);
return true;
#endif
} }
} // namespace AudioCore::Sink } // namespace AudioCore::Sink

View File

@ -88,11 +88,10 @@ private:
std::vector<std::string> ListSDLSinkDevices(bool capture); std::vector<std::string> ListSDLSinkDevices(bool capture);
/** /**
* Check if this backend is suitable for use. * Get the reported latency for this sink.
* Checks if enabled, its latency, whether it opens successfully, etc.
* *
* @return True is this backend is suitable, false otherwise. * @return Minimum latency for this sink.
*/ */
bool IsSDLSuitable(); u32 GetSDLLatency();
} // namespace AudioCore::Sink } // namespace AudioCore::Sink

View File

@ -15,95 +15,86 @@
#endif #endif
#include "audio_core/sink/null_sink.h" #include "audio_core/sink/null_sink.h"
#include "common/logging/log.h" #include "common/logging/log.h"
#include "common/settings_enums.h"
namespace AudioCore::Sink { namespace AudioCore::Sink {
namespace { namespace {
struct SinkDetails { struct SinkDetails {
using FactoryFn = std::unique_ptr<Sink> (*)(std::string_view); using FactoryFn = std::unique_ptr<Sink> (*)(std::string_view);
using ListDevicesFn = std::vector<std::string> (*)(bool); using ListDevicesFn = std::vector<std::string> (*)(bool);
using SuitableFn = bool (*)(); using LatencyFn = u32 (*)();
/// Name for this sink. /// Name for this sink.
Settings::AudioEngine id; std::string_view id;
/// A method to call to construct an instance of this type of sink. /// A method to call to construct an instance of this type of sink.
FactoryFn factory; FactoryFn factory;
/// A method to call to list available devices. /// A method to call to list available devices.
ListDevicesFn list_devices; ListDevicesFn list_devices;
/// Check whether this backend is suitable to be used. /// Method to get the latency of this backend.
SuitableFn is_suitable; LatencyFn latency;
}; };
// sink_details is ordered in terms of desirability, with the best choice at the top. // sink_details is ordered in terms of desirability, with the best choice at the top.
constexpr SinkDetails sink_details[] = { constexpr SinkDetails sink_details[] = {
#ifdef HAVE_CUBEB #ifdef HAVE_CUBEB
SinkDetails{ SinkDetails{
Settings::AudioEngine::Cubeb, "cubeb",
[](std::string_view device_id) -> std::unique_ptr<Sink> { [](std::string_view device_id) -> std::unique_ptr<Sink> {
return std::make_unique<CubebSink>(device_id); return std::make_unique<CubebSink>(device_id);
}, },
&ListCubebSinkDevices, &ListCubebSinkDevices,
&IsCubebSuitable, &GetCubebLatency,
}, },
#endif #endif
#ifdef HAVE_SDL2 #ifdef HAVE_SDL2
SinkDetails{ SinkDetails{
Settings::AudioEngine::Sdl2, "sdl2",
[](std::string_view device_id) -> std::unique_ptr<Sink> { [](std::string_view device_id) -> std::unique_ptr<Sink> {
return std::make_unique<SDLSink>(device_id); return std::make_unique<SDLSink>(device_id);
}, },
&ListSDLSinkDevices, &ListSDLSinkDevices,
&IsSDLSuitable, &GetSDLLatency,
}, },
#endif #endif
SinkDetails{ SinkDetails{"null",
Settings::AudioEngine::Null,
[](std::string_view device_id) -> std::unique_ptr<Sink> { [](std::string_view device_id) -> std::unique_ptr<Sink> {
return std::make_unique<NullSink>(device_id); return std::make_unique<NullSink>(device_id);
}, },
[](bool capture) { return std::vector<std::string>{"null"}; }, [](bool capture) { return std::vector<std::string>{"null"}; }, []() { return 0u; }},
[]() { return true; },
},
}; };
const SinkDetails& GetOutputSinkDetails(Settings::AudioEngine sink_id) { const SinkDetails& GetOutputSinkDetails(std::string_view sink_id) {
const auto find_backend{[](Settings::AudioEngine id) { const auto find_backend{[](std::string_view id) {
return std::find_if(std::begin(sink_details), std::end(sink_details), return std::find_if(std::begin(sink_details), std::end(sink_details),
[&id](const auto& sink_detail) { return sink_detail.id == id; }); [&id](const auto& sink_detail) { return sink_detail.id == id; });
}}; }};
auto iter = find_backend(sink_id); auto iter = find_backend(sink_id);
if (sink_id == Settings::AudioEngine::Auto) { if (sink_id == "auto") {
// Auto-select a backend. Use the sink details ordering, preferring cubeb first, checking // Auto-select a backend. Prefer CubeB, but it may report a large minimum latency which
// that the backend is available and suitable to use. // causes audio issues, in that case go with SDL.
for (auto& details : sink_details) { #if defined(HAVE_CUBEB) && defined(HAVE_SDL2)
if (details.is_suitable()) { iter = find_backend("cubeb");
iter = &details; if (iter->latency() > TargetSampleCount * 3) {
break; iter = find_backend("sdl2");
}
}
LOG_INFO(Service_Audio, "Auto-selecting the {} backend",
Settings::CanonicalizeEnum(iter->id));
} else {
if (iter != std::end(sink_details) && !iter->is_suitable()) {
LOG_ERROR(Service_Audio, "Selected backend {} is not suitable, falling back to null",
Settings::CanonicalizeEnum(iter->id));
iter = find_backend(Settings::AudioEngine::Null);
} }
#else
iter = std::begin(sink_details);
#endif
LOG_INFO(Service_Audio, "Auto-selecting the {} backend", iter->id);
} }
if (iter == std::end(sink_details)) { if (iter == std::end(sink_details)) {
LOG_ERROR(Audio, "Invalid sink_id {}", Settings::CanonicalizeEnum(sink_id)); LOG_ERROR(Audio, "Invalid sink_id {}", sink_id);
iter = find_backend(Settings::AudioEngine::Null); iter = find_backend("null");
} }
return *iter; return *iter;
} }
} // Anonymous namespace } // Anonymous namespace
std::vector<Settings::AudioEngine> GetSinkIDs() { std::vector<std::string_view> GetSinkIDs() {
std::vector<Settings::AudioEngine> sink_ids(std::size(sink_details)); std::vector<std::string_view> sink_ids(std::size(sink_details));
std::transform(std::begin(sink_details), std::end(sink_details), std::begin(sink_ids), std::transform(std::begin(sink_details), std::end(sink_details), std::begin(sink_ids),
[](const auto& sink) { return sink.id; }); [](const auto& sink) { return sink.id; });
@ -111,11 +102,11 @@ std::vector<Settings::AudioEngine> GetSinkIDs() {
return sink_ids; return sink_ids;
} }
std::vector<std::string> GetDeviceListForSink(Settings::AudioEngine sink_id, bool capture) { std::vector<std::string> GetDeviceListForSink(std::string_view sink_id, bool capture) {
return GetOutputSinkDetails(sink_id).list_devices(capture); return GetOutputSinkDetails(sink_id).list_devices(capture);
} }
std::unique_ptr<Sink> CreateSinkFromID(Settings::AudioEngine sink_id, std::string_view device_id) { std::unique_ptr<Sink> CreateSinkFromID(std::string_view sink_id, std::string_view device_id) {
return GetOutputSinkDetails(sink_id).factory(device_id); return GetOutputSinkDetails(sink_id).factory(device_id);
} }

View File

@ -3,11 +3,9 @@
#pragma once #pragma once
#include <memory>
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <vector> #include <vector>
#include "common/settings_enums.h"
namespace AudioCore { namespace AudioCore {
class AudioManager; class AudioManager;
@ -21,7 +19,7 @@ class Sink;
* *
* @return Vector of available sink names. * @return Vector of available sink names.
*/ */
std::vector<Settings::AudioEngine> GetSinkIDs(); std::vector<std::string_view> GetSinkIDs();
/** /**
* Gets the list of devices for a particular sink identified by the given ID. * Gets the list of devices for a particular sink identified by the given ID.
@ -30,7 +28,7 @@ std::vector<Settings::AudioEngine> GetSinkIDs();
* @param capture - Get capture (input) devices, or output devices? * @param capture - Get capture (input) devices, or output devices?
* @return Vector of device names. * @return Vector of device names.
*/ */
std::vector<std::string> GetDeviceListForSink(Settings::AudioEngine sink_id, bool capture); std::vector<std::string> GetDeviceListForSink(std::string_view sink_id, bool capture);
/** /**
* Creates an audio sink identified by the given device ID. * Creates an audio sink identified by the given device ID.
@ -39,7 +37,7 @@ std::vector<std::string> GetDeviceListForSink(Settings::AudioEngine sink_id, boo
* @param device_id - Name of the device to create. * @param device_id - Name of the device to create.
* @return Pointer to the created sink. * @return Pointer to the created sink.
*/ */
std::unique_ptr<Sink> CreateSinkFromID(Settings::AudioEngine sink_id, std::string_view device_id); std::unique_ptr<Sink> CreateSinkFromID(std::string_view sink_id, std::string_view device_id);
} // namespace Sink } // namespace Sink
} // namespace AudioCore } // namespace AudioCore

View File

@ -110,12 +110,8 @@ add_library(common STATIC
scratch_buffer.h scratch_buffer.h
settings.cpp settings.cpp
settings.h settings.h
settings_common.cpp
settings_common.h
settings_enums.h
settings_input.cpp settings_input.cpp
settings_input.h settings_input.h
settings_setting.h
socket_types.h socket_types.h
spin_lock.cpp spin_lock.cpp
spin_lock.h spin_lock.h
@ -197,16 +193,9 @@ if (MSVC)
/we4254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data /we4254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data
/we4800 # Implicit conversion from 'type' to bool. Possible information loss /we4800 # Implicit conversion from 'type' to bool. Possible information loss
) )
endif() else()
if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
target_compile_options(common PRIVATE target_compile_options(common PRIVATE
-fsized-deallocation $<$<CXX_COMPILER_ID:Clang>:-fsized-deallocation>
-Werror=unreachable-code-aggressive
)
target_compile_definitions(common PRIVATE
# Clang 14 and earlier have errors when explicitly instantiating Settings::Setting
$<$<VERSION_LESS:$<CXX_COMPILER_VERSION>,15>:CANNOT_EXPLICITLY_INSTANTIATE>
) )
endif() endif()

View File

@ -3,7 +3,6 @@
#pragma once #pragma once
#include <bit>
#include <cstddef> #include <cstddef>
#include <new> #include <new>
#include <type_traits> #include <type_traits>
@ -11,10 +10,8 @@
namespace Common { namespace Common {
template <typename T> template <typename T>
requires std::is_integral_v<T> requires std::is_unsigned_v<T>
[[nodiscard]] constexpr T AlignUp(T value_, size_t size) { [[nodiscard]] constexpr T AlignUp(T value, size_t size) {
using U = typename std::make_unsigned_t<T>;
auto value{static_cast<U>(value_)};
auto mod{static_cast<T>(value % size)}; auto mod{static_cast<T>(value % size)};
value -= mod; value -= mod;
return static_cast<T>(mod == T{0} ? value : value + size); return static_cast<T>(mod == T{0} ? value : value + size);
@ -27,10 +24,8 @@ template <typename T>
} }
template <typename T> template <typename T>
requires std::is_integral_v<T> requires std::is_unsigned_v<T>
[[nodiscard]] constexpr T AlignDown(T value_, size_t size) { [[nodiscard]] constexpr T AlignDown(T value, size_t size) {
using U = typename std::make_unsigned_t<T>;
const auto value{static_cast<U>(value_)};
return static_cast<T>(value - value % size); return static_cast<T>(value - value % size);
} }
@ -60,30 +55,6 @@ template <typename T, typename U>
return (x + (y - 1)) / y; return (x + (y - 1)) / y;
} }
template <typename T>
requires std::is_integral_v<T>
[[nodiscard]] constexpr T LeastSignificantOneBit(T x) {
return x & ~(x - 1);
}
template <typename T>
requires std::is_integral_v<T>
[[nodiscard]] constexpr T ResetLeastSignificantOneBit(T x) {
return x & (x - 1);
}
template <typename T>
requires std::is_integral_v<T>
[[nodiscard]] constexpr bool IsPowerOfTwo(T x) {
return x > 0 && ResetLeastSignificantOneBit(x) == 0;
}
template <typename T>
requires std::is_integral_v<T>
[[nodiscard]] constexpr T FloorPowerOfTwo(T x) {
return T{1} << (sizeof(T) * 8 - std::countl_zero(x) - 1);
}
template <typename T, size_t Align = 16> template <typename T, size_t Align = 16>
class AlignmentAllocator { class AlignmentAllocator {
public: public:

View File

@ -108,7 +108,7 @@ public:
using namespace Common::Literals; using namespace Common::Literals;
// Prevent logs from exceeding a set maximum size in the event that log entries are spammed. // Prevent logs from exceeding a set maximum size in the event that log entries are spammed.
const auto write_limit = Settings::values.extended_logging.GetValue() ? 1_GiB : 100_MiB; const auto write_limit = Settings::values.extended_logging ? 1_GiB : 100_MiB;
const bool write_limit_exceeded = bytes_written > write_limit; const bool write_limit_exceeded = bytes_written > write_limit;
if (entry.log_level >= Level::Error || write_limit_exceeded) { if (entry.log_level >= Level::Error || write_limit_exceeded) {
if (write_limit_exceeded) { if (write_limit_exceeded) {

View File

@ -71,10 +71,4 @@ std::vector<u8> DecompressDataLZ4(std::span<const u8> compressed, std::size_t un
return uncompressed; return uncompressed;
} }
int DecompressDataLZ4(void* dst, size_t dst_size, const void* src, size_t src_size) {
// This is just a thin wrapper around LZ4.
return LZ4_decompress_safe(reinterpret_cast<const char*>(src), reinterpret_cast<char*>(dst),
static_cast<int>(src_size), static_cast<int>(dst_size));
}
} // namespace Common::Compression } // namespace Common::Compression

View File

@ -56,6 +56,4 @@ namespace Common::Compression {
[[nodiscard]] std::vector<u8> DecompressDataLZ4(std::span<const u8> compressed, [[nodiscard]] std::vector<u8> DecompressDataLZ4(std::span<const u8> compressed,
std::size_t uncompressed_size); std::size_t uncompressed_size);
[[nodiscard]] int DecompressDataLZ4(void* dst, size_t dst_size, const void* src, size_t src_size);
} // namespace Common::Compression } // namespace Common::Compression

View File

@ -2,22 +2,14 @@
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include <version> #include <version>
#include "common/settings_enums.h"
#if __cpp_lib_chrono >= 201907L #if __cpp_lib_chrono >= 201907L
#include <chrono> #include <chrono>
#include <exception> #include <exception>
#include <stdexcept> #include <stdexcept>
#endif #endif
#include <compare>
#include <cstddef>
#include <filesystem>
#include <functional>
#include <string_view> #include <string_view>
#include <type_traits>
#include <fmt/core.h>
#include "common/assert.h" #include "common/assert.h"
#include "common/fs/fs_util.h"
#include "common/fs/path_util.h" #include "common/fs/path_util.h"
#include "common/logging/log.h" #include "common/logging/log.h"
#include "common/settings.h" #include "common/settings.h"
@ -25,50 +17,11 @@
namespace Settings { namespace Settings {
// Clang 14 and earlier have errors when explicitly instantiating these classes
#ifndef CANNOT_EXPLICITLY_INSTANTIATE
#define SETTING(TYPE, RANGED) template class Setting<TYPE, RANGED>
#define SWITCHABLE(TYPE, RANGED) template class SwitchableSetting<TYPE, RANGED>
SETTING(AudioEngine, false);
SETTING(bool, false);
SETTING(int, false);
SETTING(std::string, false);
SETTING(u16, false);
SWITCHABLE(AnisotropyMode, true);
SWITCHABLE(AntiAliasing, false);
SWITCHABLE(AspectRatio, true);
SWITCHABLE(AstcDecodeMode, true);
SWITCHABLE(AstcRecompression, true);
SWITCHABLE(AudioMode, true);
SWITCHABLE(CpuAccuracy, true);
SWITCHABLE(FullscreenMode, true);
SWITCHABLE(GpuAccuracy, true);
SWITCHABLE(Language, true);
SWITCHABLE(NvdecEmulation, false);
SWITCHABLE(Region, true);
SWITCHABLE(RendererBackend, true);
SWITCHABLE(ScalingFilter, false);
SWITCHABLE(ShaderBackend, true);
SWITCHABLE(TimeZone, true);
SETTING(VSyncMode, true);
SWITCHABLE(bool, false);
SWITCHABLE(int, false);
SWITCHABLE(int, true);
SWITCHABLE(s64, false);
SWITCHABLE(u16, true);
SWITCHABLE(u32, false);
SWITCHABLE(u8, false);
SWITCHABLE(u8, true);
#undef SETTING
#undef SWITCHABLE
#endif
Values values; Values values;
static bool configuring_global = true;
std::string GetTimeZoneString(TimeZone time_zone) { std::string GetTimeZoneString() {
const auto time_zone_index = static_cast<std::size_t>(time_zone); const auto time_zone_index = static_cast<std::size_t>(values.time_zone_index.GetValue());
ASSERT(time_zone_index < Common::TimeZone::GetTimeZoneStrings().size()); ASSERT(time_zone_index < Common::TimeZone::GetTimeZoneStrings().size());
std::string location_name; std::string location_name;
@ -108,35 +61,73 @@ void LogSettings() {
}; };
LOG_INFO(Config, "yuzu Configuration:"); LOG_INFO(Config, "yuzu Configuration:");
for (auto& [category, settings] : values.linkage.by_category) { log_setting("Controls_UseDockedMode", values.use_docked_mode.GetValue());
for (const auto& setting : settings) { log_setting("System_RngSeed", values.rng_seed.GetValue().value_or(0));
if (setting->Id() == values.yuzu_token.Id()) { log_setting("System_DeviceName", values.device_name.GetValue());
// Hide the token secret, for security reasons. log_setting("System_CurrentUser", values.current_user.GetValue());
continue; log_setting("System_LanguageIndex", values.language_index.GetValue());
} log_setting("System_RegionIndex", values.region_index.GetValue());
log_setting("System_TimeZoneIndex", values.time_zone_index.GetValue());
const auto name = fmt::format( log_setting("System_UnsafeMemoryLayout", values.use_unsafe_extended_memory_layout.GetValue());
"{:c}{:c} {}.{}", setting->ToString() == setting->DefaultToString() ? '-' : 'M', log_setting("Core_UseMultiCore", values.use_multi_core.GetValue());
setting->UsingGlobal() ? '-' : 'C', TranslateCategory(category), log_setting("CPU_Accuracy", values.cpu_accuracy.GetValue());
setting->GetLabel()); log_setting("Renderer_UseResolutionScaling", values.resolution_setup.GetValue());
log_setting("Renderer_ScalingFilter", values.scaling_filter.GetValue());
log_setting(name, setting->Canonicalize()); log_setting("Renderer_FSRSlider", values.fsr_sharpening_slider.GetValue());
} log_setting("Renderer_AntiAliasing", values.anti_aliasing.GetValue());
} log_setting("Renderer_UseSpeedLimit", values.use_speed_limit.GetValue());
log_setting("Renderer_SpeedLimit", values.speed_limit.GetValue());
log_setting("Renderer_UseDiskShaderCache", values.use_disk_shader_cache.GetValue());
log_setting("Renderer_GPUAccuracyLevel", values.gpu_accuracy.GetValue());
log_setting("Renderer_UseAsynchronousGpuEmulation",
values.use_asynchronous_gpu_emulation.GetValue());
log_setting("Renderer_NvdecEmulation", values.nvdec_emulation.GetValue());
log_setting("Renderer_AccelerateASTC", values.accelerate_astc.GetValue());
log_setting("Renderer_AsyncASTC", values.async_astc.GetValue());
log_setting("Renderer_AstcRecompression", values.astc_recompression.GetValue());
log_setting("Renderer_UseVsync", values.vsync_mode.GetValue());
log_setting("Renderer_UseReactiveFlushing", values.use_reactive_flushing.GetValue());
log_setting("Renderer_ShaderBackend", values.shader_backend.GetValue());
log_setting("Renderer_UseAsynchronousShaders", values.use_asynchronous_shaders.GetValue());
log_setting("Renderer_AnisotropicFilteringLevel", values.max_anisotropy.GetValue());
log_setting("Audio_OutputEngine", values.sink_id.GetValue());
log_setting("Audio_OutputDevice", values.audio_output_device_id.GetValue());
log_setting("Audio_InputDevice", values.audio_input_device_id.GetValue());
log_setting("DataStorage_UseVirtualSd", values.use_virtual_sd.GetValue());
log_path("DataStorage_CacheDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir)); log_path("DataStorage_CacheDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir));
log_path("DataStorage_ConfigDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::ConfigDir)); log_path("DataStorage_ConfigDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::ConfigDir));
log_path("DataStorage_LoadDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::LoadDir)); log_path("DataStorage_LoadDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::LoadDir));
log_path("DataStorage_NANDDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir)); log_path("DataStorage_NANDDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir));
log_path("DataStorage_SDMCDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::SDMCDir)); log_path("DataStorage_SDMCDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::SDMCDir));
log_setting("Debugging_ProgramArgs", values.program_args.GetValue());
log_setting("Debugging_GDBStub", values.use_gdbstub.GetValue());
log_setting("Input_EnableMotion", values.motion_enabled.GetValue());
log_setting("Input_EnableVibration", values.vibration_enabled.GetValue());
log_setting("Input_EnableTouch", values.touchscreen.enabled);
log_setting("Input_EnableMouse", values.mouse_enabled.GetValue());
log_setting("Input_EnableKeyboard", values.keyboard_enabled.GetValue());
log_setting("Input_EnableRingController", values.enable_ring_controller.GetValue());
log_setting("Input_EnableIrSensor", values.enable_ir_sensor.GetValue());
log_setting("Input_EnableCustomJoycon", values.enable_joycon_driver.GetValue());
log_setting("Input_EnableCustomProController", values.enable_procon_driver.GetValue());
log_setting("Input_EnableRawInput", values.enable_raw_input.GetValue());
}
bool IsConfiguringGlobal() {
return configuring_global;
}
void SetConfiguringGlobal(bool is_global) {
configuring_global = is_global;
} }
bool IsGPULevelExtreme() { bool IsGPULevelExtreme() {
return values.gpu_accuracy.GetValue() == GpuAccuracy::Extreme; return values.gpu_accuracy.GetValue() == GPUAccuracy::Extreme;
} }
bool IsGPULevelHigh() { bool IsGPULevelHigh() {
return values.gpu_accuracy.GetValue() == GpuAccuracy::Extreme || return values.gpu_accuracy.GetValue() == GPUAccuracy::Extreme ||
values.gpu_accuracy.GetValue() == GpuAccuracy::High; values.gpu_accuracy.GetValue() == GPUAccuracy::High;
} }
bool IsFastmemEnabled() { bool IsFastmemEnabled() {
@ -146,10 +137,6 @@ bool IsFastmemEnabled() {
return true; return true;
} }
bool IsDockedMode() {
return values.use_docked_mode.GetValue() == Settings::ConsoleMode::Docked;
}
float Volume() { float Volume() {
if (values.audio_muted) { if (values.audio_muted) {
return 0.0f; return 0.0f;
@ -157,62 +144,9 @@ float Volume() {
return values.volume.GetValue() / static_cast<f32>(values.volume.GetDefault()); return values.volume.GetValue() / static_cast<f32>(values.volume.GetDefault());
} }
const char* TranslateCategory(Category category) { void UpdateRescalingInfo() {
switch (category) { const auto setup = values.resolution_setup.GetValue();
case Category::Audio: auto& info = values.resolution_info;
return "Audio";
case Category::Core:
return "Core";
case Category::Cpu:
case Category::CpuDebug:
case Category::CpuUnsafe:
return "Cpu";
case Category::Renderer:
case Category::RendererAdvanced:
case Category::RendererDebug:
return "Renderer";
case Category::System:
case Category::SystemAudio:
return "System";
case Category::DataStorage:
return "Data Storage";
case Category::Debugging:
case Category::DebuggingGraphics:
return "Debugging";
case Category::Miscellaneous:
return "Miscellaneous";
case Category::Network:
return "Network";
case Category::WebService:
return "WebService";
case Category::AddOns:
return "DisabledAddOns";
case Category::Controls:
return "Controls";
case Category::Ui:
case Category::UiGeneral:
return "UI";
case Category::UiLayout:
return "UiLayout";
case Category::UiGameList:
return "UiGameList";
case Category::Screenshots:
return "Screenshots";
case Category::Shortcuts:
return "Shortcuts";
case Category::Multiplayer:
return "Multiplayer";
case Category::Services:
return "Services";
case Category::Paths:
return "Paths";
case Category::MaxEnum:
break;
}
return "Miscellaneous";
}
void TranslateResolutionInfo(ResolutionSetup setup, ResolutionScalingInfo& info) {
info.downscale = false; info.downscale = false;
switch (setup) { switch (setup) {
case ResolutionSetup::Res1_2X: case ResolutionSetup::Res1_2X:
@ -272,31 +206,72 @@ void TranslateResolutionInfo(ResolutionSetup setup, ResolutionScalingInfo& info)
info.active = info.up_scale != 1 || info.down_shift != 0; info.active = info.up_scale != 1 || info.down_shift != 0;
} }
void UpdateRescalingInfo() {
const auto setup = values.resolution_setup.GetValue();
auto& info = values.resolution_info;
TranslateResolutionInfo(setup, info);
}
void RestoreGlobalState(bool is_powered_on) { void RestoreGlobalState(bool is_powered_on) {
// If a game is running, DO NOT restore the global settings state // If a game is running, DO NOT restore the global settings state
if (is_powered_on) { if (is_powered_on) {
return; return;
} }
for (const auto& reset : values.linkage.restore_functions) { // Audio
reset(); values.volume.SetGlobal(true);
}
}
static bool configuring_global = true; // Core
values.use_multi_core.SetGlobal(true);
values.use_unsafe_extended_memory_layout.SetGlobal(true);
bool IsConfiguringGlobal() { // CPU
return configuring_global; values.cpu_accuracy.SetGlobal(true);
} values.cpuopt_unsafe_unfuse_fma.SetGlobal(true);
values.cpuopt_unsafe_reduce_fp_error.SetGlobal(true);
values.cpuopt_unsafe_ignore_standard_fpcr.SetGlobal(true);
values.cpuopt_unsafe_inaccurate_nan.SetGlobal(true);
values.cpuopt_unsafe_fastmem_check.SetGlobal(true);
values.cpuopt_unsafe_ignore_global_monitor.SetGlobal(true);
void SetConfiguringGlobal(bool is_global) { // Renderer
configuring_global = is_global; values.fsr_sharpening_slider.SetGlobal(true);
values.renderer_backend.SetGlobal(true);
values.async_presentation.SetGlobal(true);
values.renderer_force_max_clock.SetGlobal(true);
values.vulkan_device.SetGlobal(true);
values.fullscreen_mode.SetGlobal(true);
values.aspect_ratio.SetGlobal(true);
values.resolution_setup.SetGlobal(true);
values.scaling_filter.SetGlobal(true);
values.anti_aliasing.SetGlobal(true);
values.max_anisotropy.SetGlobal(true);
values.use_speed_limit.SetGlobal(true);
values.speed_limit.SetGlobal(true);
values.use_disk_shader_cache.SetGlobal(true);
values.gpu_accuracy.SetGlobal(true);
values.use_asynchronous_gpu_emulation.SetGlobal(true);
values.nvdec_emulation.SetGlobal(true);
values.accelerate_astc.SetGlobal(true);
values.async_astc.SetGlobal(true);
values.astc_recompression.SetGlobal(true);
values.use_reactive_flushing.SetGlobal(true);
values.shader_backend.SetGlobal(true);
values.use_asynchronous_shaders.SetGlobal(true);
values.use_fast_gpu_time.SetGlobal(true);
values.use_vulkan_driver_pipeline_cache.SetGlobal(true);
values.bg_red.SetGlobal(true);
values.bg_green.SetGlobal(true);
values.bg_blue.SetGlobal(true);
values.enable_compute_pipelines.SetGlobal(true);
values.use_video_framerate.SetGlobal(true);
// System
values.language_index.SetGlobal(true);
values.region_index.SetGlobal(true);
values.time_zone_index.SetGlobal(true);
values.rng_seed.SetGlobal(true);
values.sound_index.SetGlobal(true);
// Controls
values.players.SetGlobal(true);
values.use_docked_mode.SetGlobal(true);
values.vibration_enabled.SetGlobal(true);
values.motion_enabled.SetGlobal(true);
} }
} // namespace Settings } // namespace Settings

View File

@ -6,21 +6,95 @@
#include <algorithm> #include <algorithm>
#include <array> #include <array>
#include <map> #include <map>
#include <memory> #include <optional>
#include <stdexcept>
#include <string> #include <string>
#include <utility> #include <utility>
#include <vector> #include <vector>
#include "common/common_types.h" #include "common/common_types.h"
#include "common/settings_common.h"
#include "common/settings_enums.h"
#include "common/settings_input.h" #include "common/settings_input.h"
#include "common/settings_setting.h"
namespace Settings { namespace Settings {
const char* TranslateCategory(Settings::Category category); enum class VSyncMode : u32 {
Immediate = 0,
Mailbox = 1,
FIFO = 2,
FIFORelaxed = 3,
};
enum class RendererBackend : u32 {
OpenGL = 0,
Vulkan = 1,
Null = 2,
};
enum class ShaderBackend : u32 {
GLSL = 0,
GLASM = 1,
SPIRV = 2,
};
enum class GPUAccuracy : u32 {
Normal = 0,
High = 1,
Extreme = 2,
};
enum class CPUAccuracy : u32 {
Auto = 0,
Accurate = 1,
Unsafe = 2,
Paranoid = 3,
};
enum class FullscreenMode : u32 {
Borderless = 0,
Exclusive = 1,
};
enum class NvdecEmulation : u32 {
Off = 0,
CPU = 1,
GPU = 2,
};
enum class ResolutionSetup : u32 {
Res1_2X = 0,
Res3_4X = 1,
Res1X = 2,
Res3_2X = 3,
Res2X = 4,
Res3X = 5,
Res4X = 6,
Res5X = 7,
Res6X = 8,
Res7X = 9,
Res8X = 10,
};
enum class ScalingFilter : u32 {
NearestNeighbor = 0,
Bilinear = 1,
Bicubic = 2,
Gaussian = 3,
ScaleForce = 4,
Fsr = 5,
LastFilter = Fsr,
};
enum class AntiAliasing : u32 {
None = 0,
Fxaa = 1,
Smaa = 2,
LastAA = Smaa,
};
enum class AstcRecompression : u32 {
Uncompressed = 0,
Bc1 = 1,
Bc3 = 2,
};
struct ResolutionScalingInfo { struct ResolutionScalingInfo {
u32 up_scale{1}; u32 up_scale{1};
@ -45,47 +119,239 @@ struct ResolutionScalingInfo {
} }
}; };
#ifndef CANNOT_EXPLICITLY_INSTANTIATE /** The Setting class is a simple resource manager. It defines a label and default value alongside
// Instantiate the classes elsewhere (settings.cpp) to reduce compiler/linker work * the actual value of the setting for simpler and less-error prone use with frontend
#define SETTING(TYPE, RANGED) extern template class Setting<TYPE, RANGED> * configurations. Specifying a default value and label is required. A minimum and maximum range can
#define SWITCHABLE(TYPE, RANGED) extern template class SwitchableSetting<TYPE, RANGED> * be specified for sanitization.
*/
template <typename Type, bool ranged = false>
class Setting {
protected:
Setting() = default;
SETTING(AudioEngine, false); /**
SETTING(bool, false); * Only sets the setting to the given initializer, leaving the other members to their default
SETTING(int, false); * initializers.
SETTING(s32, false); *
SETTING(std::string, false); * @param global_val Initial value of the setting
SETTING(std::string, false); */
SETTING(u16, false); explicit Setting(const Type& val) : value{val} {}
SWITCHABLE(AnisotropyMode, true);
SWITCHABLE(AntiAliasing, false);
SWITCHABLE(AspectRatio, true);
SWITCHABLE(AstcDecodeMode, true);
SWITCHABLE(AstcRecompression, true);
SWITCHABLE(AudioMode, true);
SWITCHABLE(CpuAccuracy, true);
SWITCHABLE(FullscreenMode, true);
SWITCHABLE(GpuAccuracy, true);
SWITCHABLE(Language, true);
SWITCHABLE(NvdecEmulation, false);
SWITCHABLE(Region, true);
SWITCHABLE(RendererBackend, true);
SWITCHABLE(ScalingFilter, false);
SWITCHABLE(ShaderBackend, true);
SWITCHABLE(TimeZone, true);
SETTING(VSyncMode, true);
SWITCHABLE(bool, false);
SWITCHABLE(int, false);
SWITCHABLE(int, true);
SWITCHABLE(s64, false);
SWITCHABLE(u16, true);
SWITCHABLE(u32, false);
SWITCHABLE(u8, false);
SWITCHABLE(u8, true);
#undef SETTING public:
#undef SWITCHABLE /**
#endif * Sets a default value, label, and setting value.
*
* @param default_val Initial value of the setting, and default value of the setting
* @param name Label for the setting
*/
explicit Setting(const Type& default_val, const std::string& name)
requires(!ranged)
: value{default_val}, default_value{default_val}, label{name} {}
virtual ~Setting() = default;
/**
* Sets a default value, minimum value, maximum value, and label.
*
* @param default_val Initial value of the setting, and default value of the setting
* @param min_val Sets the minimum allowed value of the setting
* @param max_val Sets the maximum allowed value of the setting
* @param name Label for the setting
*/
explicit Setting(const Type& default_val, const Type& min_val, const Type& max_val,
const std::string& name)
requires(ranged)
: value{default_val},
default_value{default_val}, maximum{max_val}, minimum{min_val}, label{name} {}
/**
* Returns a reference to the setting's value.
*
* @returns A reference to the setting
*/
[[nodiscard]] virtual const Type& GetValue() const {
return value;
}
/**
* Sets the setting to the given value.
*
* @param val The desired value
*/
virtual void SetValue(const Type& val) {
Type temp{ranged ? std::clamp(val, minimum, maximum) : val};
std::swap(value, temp);
}
/**
* Returns the value that this setting was created with.
*
* @returns A reference to the default value
*/
[[nodiscard]] const Type& GetDefault() const {
return default_value;
}
/**
* Returns the label this setting was created with.
*
* @returns A reference to the label
*/
[[nodiscard]] const std::string& GetLabel() const {
return label;
}
/**
* Assigns a value to the setting.
*
* @param val The desired setting value
*
* @returns A reference to the setting
*/
virtual const Type& operator=(const Type& val) {
Type temp{ranged ? std::clamp(val, minimum, maximum) : val};
std::swap(value, temp);
return value;
}
/**
* Returns a reference to the setting.
*
* @returns A reference to the setting
*/
explicit virtual operator const Type&() const {
return value;
}
protected:
Type value{}; ///< The setting
const Type default_value{}; ///< The default value
const Type maximum{}; ///< Maximum allowed value of the setting
const Type minimum{}; ///< Minimum allowed value of the setting
const std::string label{}; ///< The setting's label
};
/**
* The SwitchableSetting class is a slightly more complex version of the Setting class. This adds a
* custom setting to switch to when a guest application specifically requires it. The effect is that
* other components of the emulator can access the setting's intended value without any need for the
* component to ask whether the custom or global setting is needed at the moment.
*
* By default, the global setting is used.
*/
template <typename Type, bool ranged = false>
class SwitchableSetting : virtual public Setting<Type, ranged> {
public:
/**
* Sets a default value, label, and setting value.
*
* @param default_val Initial value of the setting, and default value of the setting
* @param name Label for the setting
*/
explicit SwitchableSetting(const Type& default_val, const std::string& name)
requires(!ranged)
: Setting<Type>{default_val, name} {}
virtual ~SwitchableSetting() = default;
/**
* Sets a default value, minimum value, maximum value, and label.
*
* @param default_val Initial value of the setting, and default value of the setting
* @param min_val Sets the minimum allowed value of the setting
* @param max_val Sets the maximum allowed value of the setting
* @param name Label for the setting
*/
explicit SwitchableSetting(const Type& default_val, const Type& min_val, const Type& max_val,
const std::string& name)
requires(ranged)
: Setting<Type, true>{default_val, min_val, max_val, name} {}
/**
* Tells this setting to represent either the global or custom setting when other member
* functions are used.
*
* @param to_global Whether to use the global or custom setting.
*/
void SetGlobal(bool to_global) {
use_global = to_global;
}
/**
* Returns whether this setting is using the global setting or not.
*
* @returns The global state
*/
[[nodiscard]] bool UsingGlobal() const {
return use_global;
}
/**
* Returns either the global or custom setting depending on the values of this setting's global
* state or if the global value was specifically requested.
*
* @param need_global Request global value regardless of setting's state; defaults to false
*
* @returns The required value of the setting
*/
[[nodiscard]] virtual const Type& GetValue() const override {
if (use_global) {
return this->value;
}
return custom;
}
[[nodiscard]] virtual const Type& GetValue(bool need_global) const {
if (use_global || need_global) {
return this->value;
}
return custom;
}
/**
* Sets the current setting value depending on the global state.
*
* @param val The new value
*/
void SetValue(const Type& val) override {
Type temp{ranged ? std::clamp(val, this->minimum, this->maximum) : val};
if (use_global) {
std::swap(this->value, temp);
} else {
std::swap(custom, temp);
}
}
/**
* Assigns the current setting value depending on the global state.
*
* @param val The new value
*
* @returns A reference to the current setting value
*/
const Type& operator=(const Type& val) override {
Type temp{ranged ? std::clamp(val, this->minimum, this->maximum) : val};
if (use_global) {
std::swap(this->value, temp);
return this->value;
}
std::swap(custom, temp);
return custom;
}
/**
* Returns the current setting value depending on the global state.
*
* @returns A reference to the current setting value
*/
virtual explicit operator const Type&() const override {
if (use_global) {
return this->value;
}
return custom;
}
protected:
bool use_global{true}; ///< The setting's global state
Type custom{}; ///< The custom value of the setting
};
/** /**
* The InputSetting class allows for getting a reference to either the global or custom members. * The InputSetting class allows for getting a reference to either the global or custom members.
@ -125,394 +391,208 @@ struct TouchFromButtonMap {
}; };
struct Values { struct Values {
Linkage linkage{};
// Audio // Audio
Setting<AudioEngine> sink_id{linkage, AudioEngine::Auto, "output_engine", Category::Audio, Setting<std::string> sink_id{"auto", "output_engine"};
Specialization::RuntimeList}; Setting<std::string> audio_output_device_id{"auto", "output_device"};
Setting<std::string> audio_output_device_id{linkage, "auto", "output_device", Category::Audio, Setting<std::string> audio_input_device_id{"auto", "input_device"};
Specialization::RuntimeList}; Setting<bool> audio_muted{false, "audio_muted"};
Setting<std::string> audio_input_device_id{linkage, "auto", "input_device", Category::Audio, SwitchableSetting<u8, true> volume{100, 0, 200, "volume"};
Specialization::RuntimeList}; Setting<bool> dump_audio_commands{false, "dump_audio_commands"};
SwitchableSetting<AudioMode, true> sound_index{
linkage, AudioMode::Stereo, AudioMode::Mono, AudioMode::Surround,
"sound_index", Category::SystemAudio, Specialization::Default, true,
true};
SwitchableSetting<u8, true> volume{linkage,
100,
0,
200,
"volume",
Category::Audio,
Specialization::Scalar | Specialization::Percentage,
true,
true};
Setting<bool, false> audio_muted{
linkage, false, "audio_muted", Category::Audio, Specialization::Default, false, true};
Setting<bool, false> dump_audio_commands{
linkage, false, "dump_audio_commands", Category::Audio, Specialization::Default, false};
// Core // Core
SwitchableSetting<bool> use_multi_core{linkage, true, "use_multi_core", Category::Core}; SwitchableSetting<bool> use_multi_core{true, "use_multi_core"};
SwitchableSetting<MemoryLayout, true> memory_layout_mode{linkage, SwitchableSetting<bool> use_unsafe_extended_memory_layout{false,
MemoryLayout::Memory_4Gb, "use_unsafe_extended_memory_layout"};
MemoryLayout::Memory_4Gb,
MemoryLayout::Memory_8Gb,
"memory_layout_mode",
Category::Core};
SwitchableSetting<bool> use_speed_limit{
linkage, true, "use_speed_limit", Category::Core, Specialization::Paired, false, true};
SwitchableSetting<u16, true> speed_limit{linkage,
100,
0,
9999,
"speed_limit",
Category::Core,
Specialization::Countable | Specialization::Percentage,
true,
true,
&use_speed_limit};
// Cpu // Cpu
SwitchableSetting<CpuAccuracy, true> cpu_accuracy{linkage, CpuAccuracy::Auto, SwitchableSetting<CPUAccuracy, true> cpu_accuracy{CPUAccuracy::Auto, CPUAccuracy::Auto,
CpuAccuracy::Auto, CpuAccuracy::Paranoid, CPUAccuracy::Paranoid, "cpu_accuracy"};
"cpu_accuracy", Category::Cpu}; // TODO: remove cpu_accuracy_first_time, migration setting added 8 July 2021
Setting<bool> cpu_debug_mode{linkage, false, "cpu_debug_mode", Category::CpuDebug}; Setting<bool> cpu_accuracy_first_time{true, "cpu_accuracy_first_time"};
Setting<bool> cpu_debug_mode{false, "cpu_debug_mode"};
Setting<bool> cpuopt_page_tables{linkage, true, "cpuopt_page_tables", Category::CpuDebug}; Setting<bool> cpuopt_page_tables{true, "cpuopt_page_tables"};
Setting<bool> cpuopt_block_linking{linkage, true, "cpuopt_block_linking", Category::CpuDebug}; Setting<bool> cpuopt_block_linking{true, "cpuopt_block_linking"};
Setting<bool> cpuopt_return_stack_buffer{linkage, true, "cpuopt_return_stack_buffer", Setting<bool> cpuopt_return_stack_buffer{true, "cpuopt_return_stack_buffer"};
Category::CpuDebug}; Setting<bool> cpuopt_fast_dispatcher{true, "cpuopt_fast_dispatcher"};
Setting<bool> cpuopt_fast_dispatcher{linkage, true, "cpuopt_fast_dispatcher", Setting<bool> cpuopt_context_elimination{true, "cpuopt_context_elimination"};
Category::CpuDebug}; Setting<bool> cpuopt_const_prop{true, "cpuopt_const_prop"};
Setting<bool> cpuopt_context_elimination{linkage, true, "cpuopt_context_elimination", Setting<bool> cpuopt_misc_ir{true, "cpuopt_misc_ir"};
Category::CpuDebug}; Setting<bool> cpuopt_reduce_misalign_checks{true, "cpuopt_reduce_misalign_checks"};
Setting<bool> cpuopt_const_prop{linkage, true, "cpuopt_const_prop", Category::CpuDebug}; Setting<bool> cpuopt_fastmem{true, "cpuopt_fastmem"};
Setting<bool> cpuopt_misc_ir{linkage, true, "cpuopt_misc_ir", Category::CpuDebug}; Setting<bool> cpuopt_fastmem_exclusives{true, "cpuopt_fastmem_exclusives"};
Setting<bool> cpuopt_reduce_misalign_checks{linkage, true, "cpuopt_reduce_misalign_checks", Setting<bool> cpuopt_recompile_exclusives{true, "cpuopt_recompile_exclusives"};
Category::CpuDebug}; Setting<bool> cpuopt_ignore_memory_aborts{true, "cpuopt_ignore_memory_aborts"};
Setting<bool> cpuopt_fastmem{linkage, true, "cpuopt_fastmem", Category::CpuDebug};
Setting<bool> cpuopt_fastmem_exclusives{linkage, true, "cpuopt_fastmem_exclusives",
Category::CpuDebug};
Setting<bool> cpuopt_recompile_exclusives{linkage, true, "cpuopt_recompile_exclusives",
Category::CpuDebug};
Setting<bool> cpuopt_ignore_memory_aborts{linkage, true, "cpuopt_ignore_memory_aborts",
Category::CpuDebug};
SwitchableSetting<bool> cpuopt_unsafe_unfuse_fma{linkage, true, "cpuopt_unsafe_unfuse_fma", SwitchableSetting<bool> cpuopt_unsafe_unfuse_fma{true, "cpuopt_unsafe_unfuse_fma"};
Category::CpuUnsafe}; SwitchableSetting<bool> cpuopt_unsafe_reduce_fp_error{true, "cpuopt_unsafe_reduce_fp_error"};
SwitchableSetting<bool> cpuopt_unsafe_reduce_fp_error{
linkage, true, "cpuopt_unsafe_reduce_fp_error", Category::CpuUnsafe};
SwitchableSetting<bool> cpuopt_unsafe_ignore_standard_fpcr{ SwitchableSetting<bool> cpuopt_unsafe_ignore_standard_fpcr{
linkage, true, "cpuopt_unsafe_ignore_standard_fpcr", Category::CpuUnsafe}; true, "cpuopt_unsafe_ignore_standard_fpcr"};
SwitchableSetting<bool> cpuopt_unsafe_inaccurate_nan{ SwitchableSetting<bool> cpuopt_unsafe_inaccurate_nan{true, "cpuopt_unsafe_inaccurate_nan"};
linkage, true, "cpuopt_unsafe_inaccurate_nan", Category::CpuUnsafe}; SwitchableSetting<bool> cpuopt_unsafe_fastmem_check{true, "cpuopt_unsafe_fastmem_check"};
SwitchableSetting<bool> cpuopt_unsafe_fastmem_check{
linkage, true, "cpuopt_unsafe_fastmem_check", Category::CpuUnsafe};
SwitchableSetting<bool> cpuopt_unsafe_ignore_global_monitor{ SwitchableSetting<bool> cpuopt_unsafe_ignore_global_monitor{
linkage, true, "cpuopt_unsafe_ignore_global_monitor", Category::CpuUnsafe}; true, "cpuopt_unsafe_ignore_global_monitor"};
// Renderer // Renderer
SwitchableSetting<RendererBackend, true> renderer_backend{ SwitchableSetting<RendererBackend, true> renderer_backend{
linkage, RendererBackend::Vulkan, RendererBackend::OpenGL, RendererBackend::Null, RendererBackend::Vulkan, RendererBackend::OpenGL, RendererBackend::Null, "backend"};
"backend", Category::Renderer}; SwitchableSetting<bool> async_presentation{false, "async_presentation"};
SwitchableSetting<ShaderBackend, true> shader_backend{ SwitchableSetting<bool> renderer_force_max_clock{false, "force_max_clock"};
linkage, ShaderBackend::Glsl, ShaderBackend::Glsl, ShaderBackend::SpirV, Setting<bool> renderer_debug{false, "debug"};
"shader_backend", Category::Renderer, Specialization::RuntimeList}; Setting<bool> renderer_shader_feedback{false, "shader_feedback"};
SwitchableSetting<int> vulkan_device{linkage, 0, "vulkan_device", Category::Renderer, Setting<bool> enable_nsight_aftermath{false, "nsight_aftermath"};
Specialization::RuntimeList}; Setting<bool> disable_shader_loop_safety_checks{false, "disable_shader_loop_safety_checks"};
SwitchableSetting<int> vulkan_device{0, "vulkan_device"};
SwitchableSetting<bool> use_disk_shader_cache{linkage, true, "use_disk_shader_cache", ResolutionScalingInfo resolution_info{};
Category::Renderer}; SwitchableSetting<ResolutionSetup> resolution_setup{ResolutionSetup::Res1X, "resolution_setup"};
SwitchableSetting<bool> use_asynchronous_gpu_emulation{ SwitchableSetting<ScalingFilter> scaling_filter{ScalingFilter::Bilinear, "scaling_filter"};
linkage, true, "use_asynchronous_gpu_emulation", Category::Renderer}; SwitchableSetting<int, true> fsr_sharpening_slider{25, 0, 200, "fsr_sharpening_slider"};
SwitchableSetting<AstcDecodeMode, true> accelerate_astc{linkage, SwitchableSetting<AntiAliasing> anti_aliasing{AntiAliasing::None, "anti_aliasing"};
AstcDecodeMode::Gpu,
AstcDecodeMode::Cpu,
AstcDecodeMode::CpuAsynchronous,
"accelerate_astc",
Category::Renderer};
Setting<VSyncMode, true> vsync_mode{
linkage, VSyncMode::Fifo, VSyncMode::Immediate, VSyncMode::FifoRelaxed,
"use_vsync", Category::Renderer, Specialization::RuntimeList, true,
true};
SwitchableSetting<NvdecEmulation> nvdec_emulation{linkage, NvdecEmulation::Gpu,
"nvdec_emulation", Category::Renderer};
// *nix platforms may have issues with the borderless windowed fullscreen mode. // *nix platforms may have issues with the borderless windowed fullscreen mode.
// Default to exclusive fullscreen on these platforms for now. // Default to exclusive fullscreen on these platforms for now.
SwitchableSetting<FullscreenMode, true> fullscreen_mode{linkage, SwitchableSetting<FullscreenMode, true> fullscreen_mode{
#ifdef _WIN32 #ifdef _WIN32
FullscreenMode::Borderless, FullscreenMode::Borderless,
#else #else
FullscreenMode::Exclusive, FullscreenMode::Exclusive,
#endif #endif
FullscreenMode::Borderless, FullscreenMode::Borderless, FullscreenMode::Exclusive, "fullscreen_mode"};
FullscreenMode::Exclusive, SwitchableSetting<int, true> aspect_ratio{0, 0, 4, "aspect_ratio"};
"fullscreen_mode", SwitchableSetting<int, true> max_anisotropy{0, 0, 5, "max_anisotropy"};
Category::Renderer, SwitchableSetting<bool> use_speed_limit{true, "use_speed_limit"};
Specialization::Default, SwitchableSetting<u16, true> speed_limit{100, 0, 9999, "speed_limit"};
true, SwitchableSetting<bool> use_disk_shader_cache{true, "use_disk_shader_cache"};
true}; SwitchableSetting<GPUAccuracy, true> gpu_accuracy{GPUAccuracy::High, GPUAccuracy::Normal,
SwitchableSetting<AspectRatio, true> aspect_ratio{linkage, GPUAccuracy::Extreme, "gpu_accuracy"};
AspectRatio::R16_9, SwitchableSetting<bool> use_asynchronous_gpu_emulation{true, "use_asynchronous_gpu_emulation"};
AspectRatio::R16_9, SwitchableSetting<NvdecEmulation> nvdec_emulation{NvdecEmulation::GPU, "nvdec_emulation"};
AspectRatio::Stretch, SwitchableSetting<bool> accelerate_astc{true, "accelerate_astc"};
"aspect_ratio", SwitchableSetting<bool> async_astc{false, "async_astc"};
Category::Renderer, Setting<VSyncMode, true> vsync_mode{VSyncMode::FIFO, VSyncMode::Immediate,
Specialization::Default, VSyncMode::FIFORelaxed, "use_vsync"};
true, SwitchableSetting<bool> use_reactive_flushing{true, "use_reactive_flushing"};
true}; SwitchableSetting<ShaderBackend, true> shader_backend{ShaderBackend::GLSL, ShaderBackend::GLSL,
ShaderBackend::SPIRV, "shader_backend"};
SwitchableSetting<bool> use_asynchronous_shaders{false, "use_asynchronous_shaders"};
SwitchableSetting<bool> use_fast_gpu_time{true, "use_fast_gpu_time"};
SwitchableSetting<bool> use_vulkan_driver_pipeline_cache{true,
"use_vulkan_driver_pipeline_cache"};
SwitchableSetting<bool> enable_compute_pipelines{false, "enable_compute_pipelines"};
SwitchableSetting<AstcRecompression, true> astc_recompression{
AstcRecompression::Uncompressed, AstcRecompression::Uncompressed, AstcRecompression::Bc3,
"astc_recompression"};
SwitchableSetting<bool> use_video_framerate{false, "use_video_framerate"};
SwitchableSetting<bool> barrier_feedback_loops{true, "barrier_feedback_loops"};
ResolutionScalingInfo resolution_info{}; SwitchableSetting<u8> bg_red{0, "bg_red"};
SwitchableSetting<ResolutionSetup> resolution_setup{linkage, ResolutionSetup::Res1X, SwitchableSetting<u8> bg_green{0, "bg_green"};
"resolution_setup", Category::Renderer}; SwitchableSetting<u8> bg_blue{0, "bg_blue"};
SwitchableSetting<ScalingFilter> scaling_filter{linkage,
ScalingFilter::Bilinear,
"scaling_filter",
Category::Renderer,
Specialization::Default,
true,
true};
SwitchableSetting<AntiAliasing> anti_aliasing{linkage,
AntiAliasing::None,
"anti_aliasing",
Category::Renderer,
Specialization::Default,
true,
true};
SwitchableSetting<int, true> fsr_sharpening_slider{linkage,
25,
0,
200,
"fsr_sharpening_slider",
Category::Renderer,
Specialization::Scalar |
Specialization::Percentage,
true,
true};
SwitchableSetting<u8, false> bg_red{
linkage, 0, "bg_red", Category::Renderer, Specialization::Default, true, true};
SwitchableSetting<u8, false> bg_green{
linkage, 0, "bg_green", Category::Renderer, Specialization::Default, true, true};
SwitchableSetting<u8, false> bg_blue{
linkage, 0, "bg_blue", Category::Renderer, Specialization::Default, true, true};
SwitchableSetting<GpuAccuracy, true> gpu_accuracy{linkage,
GpuAccuracy::High,
GpuAccuracy::Normal,
GpuAccuracy::Extreme,
"gpu_accuracy",
Category::RendererAdvanced,
Specialization::Default,
true,
true};
SwitchableSetting<AnisotropyMode, true> max_anisotropy{
linkage, AnisotropyMode::Automatic, AnisotropyMode::Automatic, AnisotropyMode::X16,
"max_anisotropy", Category::RendererAdvanced};
SwitchableSetting<AstcRecompression, true> astc_recompression{linkage,
AstcRecompression::Uncompressed,
AstcRecompression::Uncompressed,
AstcRecompression::Bc3,
"astc_recompression",
Category::RendererAdvanced};
SwitchableSetting<bool> async_presentation{linkage, false, "async_presentation",
Category::RendererAdvanced};
SwitchableSetting<bool> renderer_force_max_clock{linkage, false, "force_max_clock",
Category::RendererAdvanced};
SwitchableSetting<bool> use_reactive_flushing{linkage, true, "use_reactive_flushing",
Category::RendererAdvanced};
SwitchableSetting<bool> use_asynchronous_shaders{linkage, false, "use_asynchronous_shaders",
Category::RendererAdvanced};
SwitchableSetting<bool> use_fast_gpu_time{
linkage, true, "use_fast_gpu_time", Category::RendererAdvanced, Specialization::Default,
true, true};
SwitchableSetting<bool> use_vulkan_driver_pipeline_cache{linkage,
true,
"use_vulkan_driver_pipeline_cache",
Category::RendererAdvanced,
Specialization::Default,
true,
true};
SwitchableSetting<bool> enable_compute_pipelines{linkage, false, "enable_compute_pipelines",
Category::RendererAdvanced};
SwitchableSetting<bool> use_video_framerate{linkage, false, "use_video_framerate",
Category::RendererAdvanced};
SwitchableSetting<bool> barrier_feedback_loops{linkage, true, "barrier_feedback_loops",
Category::RendererAdvanced};
Setting<bool> renderer_debug{linkage, false, "debug", Category::RendererDebug};
Setting<bool> renderer_shader_feedback{linkage, false, "shader_feedback",
Category::RendererDebug};
Setting<bool> enable_nsight_aftermath{linkage, false, "nsight_aftermath",
Category::RendererDebug};
Setting<bool> disable_shader_loop_safety_checks{
linkage, false, "disable_shader_loop_safety_checks", Category::RendererDebug};
// System // System
SwitchableSetting<Language, true> language_index{linkage, SwitchableSetting<std::optional<u32>> rng_seed{std::optional<u32>(), "rng_seed"};
Language::EnglishAmerican, Setting<std::string> device_name{"Yuzu", "device_name"};
Language::Japanese,
Language::PortugueseBrazilian,
"language_index",
Category::System};
SwitchableSetting<Region, true> region_index{linkage, Region::Usa, Region::Japan,
Region::Taiwan, "region_index", Category::System};
SwitchableSetting<TimeZone, true> time_zone_index{linkage, TimeZone::Auto,
TimeZone::Auto, TimeZone::Zulu,
"time_zone_index", Category::System};
// Measured in seconds since epoch // Measured in seconds since epoch
SwitchableSetting<bool> custom_rtc_enabled{ std::optional<s64> custom_rtc;
linkage, false, "custom_rtc_enabled", Category::System, Specialization::Paired, true, true};
SwitchableSetting<s64> custom_rtc{
linkage, 0, "custom_rtc", Category::System, Specialization::Time,
true, true, &custom_rtc_enabled};
// Set on game boot, reset on stop. Seconds difference between current time and `custom_rtc` // Set on game boot, reset on stop. Seconds difference between current time and `custom_rtc`
s64 custom_rtc_differential; s64 custom_rtc_differential;
SwitchableSetting<bool> rng_seed_enabled{
linkage, false, "rng_seed_enabled", Category::System, Specialization::Paired, true, true};
SwitchableSetting<u32> rng_seed{
linkage, 0, "rng_seed", Category::System, Specialization::Hex,
true, true, &rng_seed_enabled};
Setting<std::string> device_name{
linkage, "yuzu", "device_name", Category::System, Specialization::Default, true, true};
Setting<s32> current_user{linkage, 0, "current_user", Category::System}; Setting<s32> current_user{0, "current_user"};
SwitchableSetting<s32, true> language_index{1, 0, 17, "language_index"};
SwitchableSetting<ConsoleMode> use_docked_mode{linkage, SwitchableSetting<s32, true> region_index{1, 0, 6, "region_index"};
ConsoleMode::Docked, SwitchableSetting<s32, true> time_zone_index{0, 0, 45, "time_zone_index"};
"use_docked_mode", SwitchableSetting<s32, true> sound_index{1, 0, 2, "sound_index"};
Category::System,
Specialization::Radio,
true,
true};
// Controls // Controls
InputSetting<std::array<PlayerInput, 10>> players; InputSetting<std::array<PlayerInput, 10>> players;
Setting<bool> enable_raw_input{ SwitchableSetting<bool> use_docked_mode{true, "use_docked_mode"};
linkage, false, "enable_raw_input", Category::Controls, Specialization::Default,
// Only read/write enable_raw_input on Windows platforms
#ifdef _WIN32
true
#else
false
#endif
};
Setting<bool> controller_navigation{linkage, true, "controller_navigation", Category::Controls};
Setting<bool> enable_joycon_driver{linkage, true, "enable_joycon_driver", Category::Controls};
Setting<bool> enable_procon_driver{linkage, false, "enable_procon_driver", Category::Controls};
SwitchableSetting<bool> vibration_enabled{linkage, true, "vibration_enabled", Setting<bool> enable_raw_input{false, "enable_raw_input"};
Category::Controls}; Setting<bool> controller_navigation{true, "controller_navigation"};
SwitchableSetting<bool> enable_accurate_vibrations{linkage, false, "enable_accurate_vibrations", Setting<bool> enable_joycon_driver{true, "enable_joycon_driver"};
Category::Controls}; Setting<bool> enable_procon_driver{false, "enable_procon_driver"};
SwitchableSetting<bool> motion_enabled{linkage, true, "motion_enabled", Category::Controls}; SwitchableSetting<bool> vibration_enabled{true, "vibration_enabled"};
Setting<std::string> udp_input_servers{linkage, "127.0.0.1:26760", "udp_input_servers", SwitchableSetting<bool> enable_accurate_vibrations{false, "enable_accurate_vibrations"};
Category::Controls};
Setting<bool> enable_udp_controller{linkage, false, "enable_udp_controller",
Category::Controls};
Setting<bool> pause_tas_on_load{linkage, true, "pause_tas_on_load", Category::Controls}; SwitchableSetting<bool> motion_enabled{true, "motion_enabled"};
Setting<bool> tas_enable{linkage, false, "tas_enable", Category::Controls}; Setting<std::string> udp_input_servers{"127.0.0.1:26760", "udp_input_servers"};
Setting<bool> tas_loop{linkage, false, "tas_loop", Category::Controls}; Setting<bool> enable_udp_controller{false, "enable_udp_controller"};
Setting<bool> mouse_panning{ Setting<bool> pause_tas_on_load{true, "pause_tas_on_load"};
linkage, false, "mouse_panning", Category::Controls, Specialization::Default, false}; Setting<bool> tas_enable{false, "tas_enable"};
Setting<u8, true> mouse_panning_sensitivity{ Setting<bool> tas_loop{false, "tas_loop"};
linkage, 50, 1, 100, "mouse_panning_sensitivity", Category::Controls};
Setting<bool> mouse_enabled{linkage, false, "mouse_enabled", Category::Controls};
Setting<u8, true> mouse_panning_x_sensitivity{ Setting<bool> mouse_panning{false, "mouse_panning"};
linkage, 50, 1, 100, "mouse_panning_x_sensitivity", Category::Controls}; Setting<u8, true> mouse_panning_x_sensitivity{50, 1, 100, "mouse_panning_x_sensitivity"};
Setting<u8, true> mouse_panning_y_sensitivity{ Setting<u8, true> mouse_panning_y_sensitivity{50, 1, 100, "mouse_panning_y_sensitivity"};
linkage, 50, 1, 100, "mouse_panning_y_sensitivity", Category::Controls}; Setting<u8, true> mouse_panning_deadzone_counterweight{20, 0, 100,
Setting<u8, true> mouse_panning_deadzone_counterweight{ "mouse_panning_deadzone_counterweight"};
linkage, 20, 0, 100, "mouse_panning_deadzone_counterweight", Category::Controls}; Setting<u8, true> mouse_panning_decay_strength{18, 0, 100, "mouse_panning_decay_strength"};
Setting<u8, true> mouse_panning_decay_strength{ Setting<u8, true> mouse_panning_min_decay{6, 0, 100, "mouse_panning_min_decay"};
linkage, 18, 0, 100, "mouse_panning_decay_strength", Category::Controls};
Setting<u8, true> mouse_panning_min_decay{
linkage, 6, 0, 100, "mouse_panning_min_decay", Category::Controls};
Setting<bool> emulate_analog_keyboard{linkage, false, "emulate_analog_keyboard", Setting<bool> mouse_enabled{false, "mouse_enabled"};
Category::Controls}; Setting<bool> emulate_analog_keyboard{false, "emulate_analog_keyboard"};
Setting<bool> keyboard_enabled{linkage, false, "keyboard_enabled", Category::Controls}; Setting<bool> keyboard_enabled{false, "keyboard_enabled"};
Setting<bool> debug_pad_enabled{linkage, false, "debug_pad_enabled", Category::Controls}; Setting<bool> debug_pad_enabled{false, "debug_pad_enabled"};
ButtonsRaw debug_pad_buttons; ButtonsRaw debug_pad_buttons;
AnalogsRaw debug_pad_analogs; AnalogsRaw debug_pad_analogs;
TouchscreenInput touchscreen; TouchscreenInput touchscreen;
Setting<std::string> touch_device{linkage, "min_x:100,min_y:50,max_x:1800,max_y:850", Setting<std::string> touch_device{"min_x:100,min_y:50,max_x:1800,max_y:850", "touch_device"};
"touch_device", Category::Controls}; Setting<int> touch_from_button_map_index{0, "touch_from_button_map"};
Setting<int> touch_from_button_map_index{linkage, 0, "touch_from_button_map",
Category::Controls};
std::vector<TouchFromButtonMap> touch_from_button_maps; std::vector<TouchFromButtonMap> touch_from_button_maps;
Setting<bool> enable_ring_controller{linkage, true, "enable_ring_controller", Setting<bool> enable_ring_controller{true, "enable_ring_controller"};
Category::Controls};
RingconRaw ringcon_analogs; RingconRaw ringcon_analogs;
Setting<bool> enable_ir_sensor{linkage, false, "enable_ir_sensor", Category::Controls}; Setting<bool> enable_ir_sensor{false, "enable_ir_sensor"};
Setting<std::string> ir_sensor_device{linkage, "auto", "ir_sensor_device", Category::Controls}; Setting<std::string> ir_sensor_device{"auto", "ir_sensor_device"};
Setting<bool> random_amiibo_id{linkage, false, "random_amiibo_id", Category::Controls}; Setting<bool> random_amiibo_id{false, "random_amiibo_id"};
// Data Storage // Data Storage
Setting<bool> use_virtual_sd{linkage, true, "use_virtual_sd", Category::DataStorage}; Setting<bool> use_virtual_sd{true, "use_virtual_sd"};
Setting<bool> gamecard_inserted{linkage, false, "gamecard_inserted", Category::DataStorage}; Setting<bool> gamecard_inserted{false, "gamecard_inserted"};
Setting<bool> gamecard_current_game{linkage, false, "gamecard_current_game", Setting<bool> gamecard_current_game{false, "gamecard_current_game"};
Category::DataStorage}; Setting<std::string> gamecard_path{std::string(), "gamecard_path"};
Setting<std::string> gamecard_path{linkage, std::string(), "gamecard_path",
Category::DataStorage};
// Debugging // Debugging
bool record_frame_times; bool record_frame_times;
Setting<bool> use_gdbstub{linkage, false, "use_gdbstub", Category::Debugging}; Setting<bool> use_gdbstub{false, "use_gdbstub"};
Setting<u16> gdbstub_port{linkage, 6543, "gdbstub_port", Category::Debugging}; Setting<u16> gdbstub_port{6543, "gdbstub_port"};
Setting<std::string> program_args{linkage, std::string(), "program_args", Category::Debugging}; Setting<std::string> program_args{std::string(), "program_args"};
Setting<bool> dump_exefs{linkage, false, "dump_exefs", Category::Debugging}; Setting<bool> dump_exefs{false, "dump_exefs"};
Setting<bool> dump_nso{linkage, false, "dump_nso", Category::Debugging}; Setting<bool> dump_nso{false, "dump_nso"};
Setting<bool> dump_shaders{ Setting<bool> dump_shaders{false, "dump_shaders"};
linkage, false, "dump_shaders", Category::DebuggingGraphics, Specialization::Default, Setting<bool> dump_macros{false, "dump_macros"};
false}; Setting<bool> enable_fs_access_log{false, "enable_fs_access_log"};
Setting<bool> dump_macros{ Setting<bool> reporting_services{false, "reporting_services"};
linkage, false, "dump_macros", Category::DebuggingGraphics, Specialization::Default, false}; Setting<bool> quest_flag{false, "quest_flag"};
Setting<bool> enable_fs_access_log{linkage, false, "enable_fs_access_log", Category::Debugging}; Setting<bool> disable_macro_jit{false, "disable_macro_jit"};
Setting<bool> reporting_services{ Setting<bool> disable_macro_hle{false, "disable_macro_hle"};
linkage, false, "reporting_services", Category::Debugging, Specialization::Default, false}; Setting<bool> extended_logging{false, "extended_logging"};
Setting<bool> quest_flag{linkage, false, "quest_flag", Category::Debugging}; Setting<bool> use_debug_asserts{false, "use_debug_asserts"};
Setting<bool> disable_macro_jit{linkage, false, "disable_macro_jit", Setting<bool> use_auto_stub{false, "use_auto_stub"};
Category::DebuggingGraphics}; Setting<bool> enable_all_controllers{false, "enable_all_controllers"};
Setting<bool> disable_macro_hle{linkage, false, "disable_macro_hle", Setting<bool> create_crash_dumps{false, "create_crash_dumps"};
Category::DebuggingGraphics}; Setting<bool> perform_vulkan_check{true, "perform_vulkan_check"};
Setting<bool> extended_logging{
linkage, false, "extended_logging", Category::Debugging, Specialization::Default, false};
Setting<bool> use_debug_asserts{linkage, false, "use_debug_asserts", Category::Debugging};
Setting<bool> use_auto_stub{
linkage, false, "use_auto_stub", Category::Debugging, Specialization::Default, false};
Setting<bool> enable_all_controllers{linkage, false, "enable_all_controllers",
Category::Debugging};
Setting<bool> create_crash_dumps{linkage, false, "create_crash_dumps", Category::Debugging};
Setting<bool> perform_vulkan_check{linkage, true, "perform_vulkan_check", Category::Debugging};
// Miscellaneous // Miscellaneous
Setting<std::string> log_filter{linkage, "*:Info", "log_filter", Category::Miscellaneous}; Setting<std::string> log_filter{"*:Info", "log_filter"};
Setting<bool> use_dev_keys{linkage, false, "use_dev_keys", Category::Miscellaneous}; Setting<bool> use_dev_keys{false, "use_dev_keys"};
// Network // Network
Setting<std::string> network_interface{linkage, std::string(), "network_interface", Setting<std::string> network_interface{std::string(), "network_interface"};
Category::Network};
// WebService // WebService
Setting<bool> enable_telemetry{linkage, true, "enable_telemetry", Category::WebService}; Setting<bool> enable_telemetry{true, "enable_telemetry"};
Setting<std::string> web_api_url{linkage, "https://api.yuzu-emu.org", "web_api_url", Setting<std::string> web_api_url{"https://api.yuzu-emu.org", "web_api_url"};
Category::WebService}; Setting<std::string> yuzu_username{std::string(), "yuzu_username"};
Setting<std::string> yuzu_username{linkage, std::string(), "yuzu_username", Setting<std::string> yuzu_token{std::string(), "yuzu_token"};
Category::WebService};
Setting<std::string> yuzu_token{linkage, std::string(), "yuzu_token", Category::WebService};
// Add-Ons // Add-Ons
std::map<u64, std::vector<std::string>> disabled_addons; std::map<u64, std::vector<std::string>> disabled_addons;
@ -520,26 +600,23 @@ struct Values {
extern Values values; extern Values values;
bool IsConfiguringGlobal();
void SetConfiguringGlobal(bool is_global);
bool IsGPULevelExtreme(); bool IsGPULevelExtreme();
bool IsGPULevelHigh(); bool IsGPULevelHigh();
bool IsFastmemEnabled(); bool IsFastmemEnabled();
bool IsDockedMode();
float Volume(); float Volume();
std::string GetTimeZoneString(TimeZone time_zone); std::string GetTimeZoneString();
void LogSettings(); void LogSettings();
void TranslateResolutionInfo(ResolutionSetup setup, ResolutionScalingInfo& info);
void UpdateRescalingInfo(); void UpdateRescalingInfo();
// Restore the global state of all applicable settings in the Values struct // Restore the global state of all applicable settings in the Values struct
void RestoreGlobalState(bool is_powered_on); void RestoreGlobalState(bool is_powered_on);
bool IsConfiguringGlobal();
void SetConfiguringGlobal(bool is_global);
} // namespace Settings } // namespace Settings

View File

@ -1,60 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <functional>
#include <string>
#include <vector>
#include "common/settings_common.h"
namespace Settings {
BasicSetting::BasicSetting(Linkage& linkage, const std::string& name, enum Category category_,
bool save_, bool runtime_modifiable_, u32 specialization_,
BasicSetting* other_setting_)
: label{name}, category{category_}, id{linkage.count}, save{save_},
runtime_modifiable{runtime_modifiable_}, specialization{specialization_},
other_setting{other_setting_} {
linkage.by_category[category].push_back(this);
linkage.count++;
}
BasicSetting::~BasicSetting() = default;
std::string BasicSetting::ToStringGlobal() const {
return this->ToString();
}
bool BasicSetting::UsingGlobal() const {
return true;
}
void BasicSetting::SetGlobal(bool global) {}
bool BasicSetting::Save() const {
return save;
}
bool BasicSetting::RuntimeModfiable() const {
return runtime_modifiable;
}
Category BasicSetting::GetCategory() const {
return category;
}
u32 BasicSetting::Specialization() const {
return specialization;
}
BasicSetting* BasicSetting::PairedSetting() const {
return other_setting;
}
const std::string& BasicSetting::GetLabel() const {
return label;
}
Linkage::Linkage(u32 initial_count) : count{initial_count} {}
Linkage::~Linkage() = default;
} // namespace Settings

View File

@ -1,257 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <functional>
#include <map>
#include <string>
#include <typeindex>
#include "common/common_types.h"
namespace Settings {
enum class Category : u32 {
Audio,
Core,
Cpu,
CpuDebug,
CpuUnsafe,
Renderer,
RendererAdvanced,
RendererDebug,
System,
SystemAudio,
DataStorage,
Debugging,
DebuggingGraphics,
Miscellaneous,
Network,
WebService,
AddOns,
Controls,
Ui,
UiGeneral,
UiLayout,
UiGameList,
Screenshots,
Shortcuts,
Multiplayer,
Services,
Paths,
MaxEnum,
};
constexpr u8 SpecializationTypeMask = 0xf;
constexpr u8 SpecializationAttributeMask = 0xf0;
constexpr u8 SpecializationAttributeOffset = 4;
// Scalar and countable could have better names
enum Specialization : u8 {
Default = 0,
Time = 1, // Duration or specific moment in time
Hex = 2, // Hexadecimal number
List = 3, // Setting has specific members
RuntimeList = 4, // Members of the list are determined during runtime
Scalar = 5, // Values are continuous
Countable = 6, // Can be stepped through
Paired = 7, // Another setting is associated with this setting
Radio = 8, // Setting should be presented in a radio group
Percentage = (1 << SpecializationAttributeOffset), // Should be represented as a percentage
};
class BasicSetting;
class Linkage {
public:
explicit Linkage(u32 initial_count = 0);
~Linkage();
std::map<Category, std::vector<BasicSetting*>> by_category{};
std::vector<std::function<void()>> restore_functions{};
u32 count;
};
/**
* BasicSetting is an abstract class that only keeps track of metadata. The string methods are
* available to get data values out.
*/
class BasicSetting {
protected:
explicit BasicSetting(Linkage& linkage, const std::string& name, Category category_, bool save_,
bool runtime_modifiable_, u32 specialization,
BasicSetting* other_setting);
public:
virtual ~BasicSetting();
/*
* Data retrieval
*/
/**
* Returns a string representation of the internal data. If the Setting is Switchable, it
* respects the internal global state: it is based on GetValue().
*
* @returns A string representation of the internal data.
*/
[[nodiscard]] virtual std::string ToString() const = 0;
/**
* Returns a string representation of the global version of internal data. If the Setting is
* not Switchable, it behaves like ToString.
*
* @returns A string representation of the global version of internal data.
*/
[[nodiscard]] virtual std::string ToStringGlobal() const;
/**
* @returns A string representation of the Setting's default value.
*/
[[nodiscard]] virtual std::string DefaultToString() const = 0;
/**
* Returns a string representation of the minimum value of the setting. If the Setting is not
* ranged, the string represents the default initialization of the data type.
*
* @returns A string representation of the minimum value of the setting.
*/
[[nodiscard]] virtual std::string MinVal() const = 0;
/**
* Returns a string representation of the maximum value of the setting. If the Setting is not
* ranged, the string represents the default initialization of the data type.
*
* @returns A string representation of the maximum value of the setting.
*/
[[nodiscard]] virtual std::string MaxVal() const = 0;
/**
* Takes a string input, converts it to the internal data type if necessary, and then runs
* SetValue with it.
*
* @param load String of the input data.
*/
virtual void LoadString(const std::string& load) = 0;
/**
* Returns a string representation of the data. If the data is an enum, it returns a string of
* the enum value. If the internal data type is not an enum, this is equivalent to ToString.
*
* e.g. renderer_backend.Canonicalize() == "OpenGL"
*
* @returns Canonicalized string representation of the internal data
*/
[[nodiscard]] virtual std::string Canonicalize() const = 0;
/*
* Metadata
*/
/**
* @returns A unique identifier for the Setting's internal data type.
*/
[[nodiscard]] virtual std::type_index TypeId() const = 0;
/**
* Returns true if the Setting's internal data type is an enum.
*
* @returns True if the Setting's internal data type is an enum
*/
[[nodiscard]] virtual constexpr bool IsEnum() const = 0;
/**
* Returns true if the current setting is Switchable.
*
* @returns If the setting is a SwitchableSetting
*/
[[nodiscard]] virtual constexpr bool Switchable() const {
return false;
}
/**
* Returns true to suggest that a frontend can read or write the setting to a configuration
* file.
*
* @returns The save preference
*/
[[nodiscard]] bool Save() const;
/**
* @returns true if the current setting can be changed while the guest is running.
*/
[[nodiscard]] bool RuntimeModfiable() const;
/**
* @returns A unique number corresponding to the setting.
*/
[[nodiscard]] constexpr u32 Id() const {
return id;
}
/**
* Returns the setting's category AKA INI group.
*
* @returns The setting's category
*/
[[nodiscard]] Category GetCategory() const;
/**
* @returns Extra metadata for data representation in frontend implementations.
*/
[[nodiscard]] u32 Specialization() const;
/**
* @returns Another BasicSetting if one is paired, or nullptr otherwise.
*/
[[nodiscard]] BasicSetting* PairedSetting() const;
/**
* Returns the label this setting was created with.
*
* @returns A reference to the label
*/
[[nodiscard]] const std::string& GetLabel() const;
/**
* @returns If the Setting checks input values for valid ranges.
*/
[[nodiscard]] virtual constexpr bool Ranged() const = 0;
/**
* @returns The index of the enum if the underlying setting type is an enum, else max of u32.
*/
[[nodiscard]] virtual constexpr u32 EnumIndex() const = 0;
/*
* Switchable settings
*/
/**
* Sets a setting's global state. True means use the normal setting, false to use a custom
* value. Has no effect if the Setting is not Switchable.
*
* @param global The desired state
*/
virtual void SetGlobal(bool global);
/**
* Returns true if the setting is using the normal setting value. Always true if the setting is
* not Switchable.
*
* @returns The Setting's global state
*/
[[nodiscard]] virtual bool UsingGlobal() const;
private:
const std::string label; ///< The setting's label
const Category category; ///< The setting's category AKA INI group
const u32 id; ///< Unique integer for the setting
const bool save; ///< Suggests if the setting should be saved and read to a frontend config
const bool
runtime_modifiable; ///< Suggests if the setting can be modified while a guest is running
const u32 specialization; ///< Extra data to identify representation of a setting
BasicSetting* const other_setting; ///< A paired setting
};
} // namespace Settings

View File

@ -1,216 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <string>
#include <utility>
#include <vector>
#include "common/common_types.h"
namespace Settings {
template <typename T>
struct EnumMetadata {
static std::vector<std::pair<std::string, T>> Canonicalizations();
static u32 Index();
};
#define PAIR_45(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_46(N, __VA_ARGS__))
#define PAIR_44(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_45(N, __VA_ARGS__))
#define PAIR_43(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_44(N, __VA_ARGS__))
#define PAIR_42(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_43(N, __VA_ARGS__))
#define PAIR_41(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_42(N, __VA_ARGS__))
#define PAIR_40(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_41(N, __VA_ARGS__))
#define PAIR_39(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_40(N, __VA_ARGS__))
#define PAIR_38(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_39(N, __VA_ARGS__))
#define PAIR_37(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_38(N, __VA_ARGS__))
#define PAIR_36(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_37(N, __VA_ARGS__))
#define PAIR_35(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_36(N, __VA_ARGS__))
#define PAIR_34(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_35(N, __VA_ARGS__))
#define PAIR_33(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_34(N, __VA_ARGS__))
#define PAIR_32(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_33(N, __VA_ARGS__))
#define PAIR_31(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_32(N, __VA_ARGS__))
#define PAIR_30(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_31(N, __VA_ARGS__))
#define PAIR_29(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_30(N, __VA_ARGS__))
#define PAIR_28(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_29(N, __VA_ARGS__))
#define PAIR_27(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_28(N, __VA_ARGS__))
#define PAIR_26(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_27(N, __VA_ARGS__))
#define PAIR_25(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_26(N, __VA_ARGS__))
#define PAIR_24(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_25(N, __VA_ARGS__))
#define PAIR_23(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_24(N, __VA_ARGS__))
#define PAIR_22(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_23(N, __VA_ARGS__))
#define PAIR_21(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_22(N, __VA_ARGS__))
#define PAIR_20(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_21(N, __VA_ARGS__))
#define PAIR_19(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_20(N, __VA_ARGS__))
#define PAIR_18(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_19(N, __VA_ARGS__))
#define PAIR_17(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_18(N, __VA_ARGS__))
#define PAIR_16(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_17(N, __VA_ARGS__))
#define PAIR_15(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_16(N, __VA_ARGS__))
#define PAIR_14(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_15(N, __VA_ARGS__))
#define PAIR_13(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_14(N, __VA_ARGS__))
#define PAIR_12(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_13(N, __VA_ARGS__))
#define PAIR_11(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_12(N, __VA_ARGS__))
#define PAIR_10(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_11(N, __VA_ARGS__))
#define PAIR_9(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_10(N, __VA_ARGS__))
#define PAIR_8(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_9(N, __VA_ARGS__))
#define PAIR_7(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_8(N, __VA_ARGS__))
#define PAIR_6(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_7(N, __VA_ARGS__))
#define PAIR_5(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_6(N, __VA_ARGS__))
#define PAIR_4(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_5(N, __VA_ARGS__))
#define PAIR_3(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_4(N, __VA_ARGS__))
#define PAIR_2(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_3(N, __VA_ARGS__))
#define PAIR_1(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_2(N, __VA_ARGS__))
#define PAIR(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_1(N, __VA_ARGS__))
#define ENUM(NAME, ...) \
enum class NAME : u32 { __VA_ARGS__ }; \
template <> \
inline std::vector<std::pair<std::string, NAME>> EnumMetadata<NAME>::Canonicalizations() { \
return {PAIR(NAME, __VA_ARGS__)}; \
} \
template <> \
inline u32 EnumMetadata<NAME>::Index() { \
return __COUNTER__; \
}
// AudioEngine must be specified discretely due to having existing but slightly different
// canonicalizations
// TODO (lat9nq): Remove explicit definition of AudioEngine/sink_id
enum class AudioEngine : u32 {
Auto,
Cubeb,
Sdl2,
Null,
};
template <>
inline std::vector<std::pair<std::string, AudioEngine>>
EnumMetadata<AudioEngine>::Canonicalizations() {
return {
{"auto", AudioEngine::Auto},
{"cubeb", AudioEngine::Cubeb},
{"sdl2", AudioEngine::Sdl2},
{"null", AudioEngine::Null},
};
}
template <>
inline u32 EnumMetadata<AudioEngine>::Index() {
// This is just a sufficiently large number that is more than the number of other enums declared
// here
return 100;
}
ENUM(AudioMode, Mono, Stereo, Surround);
ENUM(Language, Japanese, EnglishAmerican, French, German, Italian, Spanish, Chinese, Korean, Dutch,
Portuguese, Russian, Taiwanese, EnglishBritish, FrenchCanadian, SpanishLatin,
ChineseSimplified, ChineseTraditional, PortugueseBrazilian);
ENUM(Region, Japan, Usa, Europe, Australia, China, Korea, Taiwan);
ENUM(TimeZone, Auto, Default, Cet, Cst6Cdt, Cuba, Eet, Egypt, Eire, Est, Est5Edt, Gb, GbEire, Gmt,
GmtPlusZero, GmtMinusZero, GmtZero, Greenwich, Hongkong, Hst, Iceland, Iran, Israel, Jamaica,
Japan, Kwajalein, Libya, Met, Mst, Mst7Mdt, Navajo, Nz, NzChat, Poland, Portugal, Prc, Pst8Pdt,
Roc, Rok, Singapore, Turkey, Uct, Universal, Utc, WSu, Wet, Zulu);
ENUM(AnisotropyMode, Automatic, Default, X2, X4, X8, X16);
ENUM(AstcDecodeMode, Cpu, Gpu, CpuAsynchronous);
ENUM(AstcRecompression, Uncompressed, Bc1, Bc3);
ENUM(VSyncMode, Immediate, Mailbox, Fifo, FifoRelaxed);
ENUM(RendererBackend, OpenGL, Vulkan, Null);
ENUM(ShaderBackend, Glsl, Glasm, SpirV);
ENUM(GpuAccuracy, Normal, High, Extreme);
ENUM(CpuAccuracy, Auto, Accurate, Unsafe, Paranoid);
ENUM(MemoryLayout, Memory_4Gb, Memory_6Gb, Memory_8Gb);
ENUM(FullscreenMode, Borderless, Exclusive);
ENUM(NvdecEmulation, Off, Cpu, Gpu);
ENUM(ResolutionSetup, Res1_2X, Res3_4X, Res1X, Res3_2X, Res2X, Res3X, Res4X, Res5X, Res6X, Res7X,
Res8X);
ENUM(ScalingFilter, NearestNeighbor, Bilinear, Bicubic, Gaussian, ScaleForce, Fsr, MaxEnum);
ENUM(AntiAliasing, None, Fxaa, Smaa, MaxEnum);
ENUM(AspectRatio, R16_9, R4_3, R21_9, R16_10, Stretch);
ENUM(ConsoleMode, Handheld, Docked);
template <typename Type>
inline std::string CanonicalizeEnum(Type id) {
const auto group = EnumMetadata<Type>::Canonicalizations();
for (auto& [name, value] : group) {
if (value == id) {
return name;
}
}
return "unknown";
}
template <typename Type>
inline Type ToEnum(const std::string& canonicalization) {
const auto group = EnumMetadata<Type>::Canonicalizations();
for (auto& [name, value] : group) {
if (name == canonicalization) {
return value;
}
}
return {};
}
} // namespace Settings
#undef ENUM
#undef PAIR
#undef PAIR_1
#undef PAIR_2
#undef PAIR_3
#undef PAIR_4
#undef PAIR_5
#undef PAIR_6
#undef PAIR_7
#undef PAIR_8
#undef PAIR_9
#undef PAIR_10
#undef PAIR_12
#undef PAIR_13
#undef PAIR_14
#undef PAIR_15
#undef PAIR_16
#undef PAIR_17
#undef PAIR_18
#undef PAIR_19
#undef PAIR_20
#undef PAIR_22
#undef PAIR_23
#undef PAIR_24
#undef PAIR_25
#undef PAIR_26
#undef PAIR_27
#undef PAIR_28
#undef PAIR_29
#undef PAIR_30
#undef PAIR_32
#undef PAIR_33
#undef PAIR_34
#undef PAIR_35
#undef PAIR_36
#undef PAIR_37
#undef PAIR_38
#undef PAIR_39
#undef PAIR_40
#undef PAIR_42
#undef PAIR_43
#undef PAIR_44
#undef PAIR_45

View File

@ -1,394 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <limits>
#include <map>
#include <optional>
#include <stdexcept>
#include <string>
#include <typeindex>
#include <typeinfo>
#include "common/common_types.h"
#include "common/settings_common.h"
#include "common/settings_enums.h"
namespace Settings {
/** The Setting class is a simple resource manager. It defines a label and default value
* alongside the actual value of the setting for simpler and less-error prone use with frontend
* configurations. Specifying a default value and label is required. A minimum and maximum range
* can be specified for sanitization.
*/
template <typename Type, bool ranged = false>
class Setting : public BasicSetting {
protected:
Setting() = default;
public:
/**
* Sets a default value, label, and setting value.
*
* @param linkage Setting registry
* @param default_val Initial value of the setting, and default value of the setting
* @param name Label for the setting
* @param category_ Category of the setting AKA INI group
* @param specialization_ Suggestion for how frontend implementations represent this in a config
* @param save_ Suggests that this should or should not be saved to a frontend config file
* @param runtime_modifiable_ Suggests whether this is modifiable while a guest is loaded
* @param other_setting_ A second Setting to associate to this one in metadata
*/
explicit Setting(Linkage& linkage, const Type& default_val, const std::string& name,
Category category_, u32 specialization_ = Specialization::Default,
bool save_ = true, bool runtime_modifiable_ = false,
BasicSetting* other_setting_ = nullptr)
requires(!ranged)
: BasicSetting(linkage, name, category_, save_, runtime_modifiable_, specialization_,
other_setting_),
value{default_val}, default_value{default_val} {}
virtual ~Setting() = default;
/**
* Sets a default value, minimum value, maximum value, and label.
*
* @param linkage Setting registry
* @param default_val Initial value of the setting, and default value of the setting
* @param min_val Sets the minimum allowed value of the setting
* @param max_val Sets the maximum allowed value of the setting
* @param name Label for the setting
* @param category_ Category of the setting AKA INI group
* @param specialization_ Suggestion for how frontend implementations represent this in a config
* @param save_ Suggests that this should or should not be saved to a frontend config file
* @param runtime_modifiable_ Suggests whether this is modifiable while a guest is loaded
* @param other_setting_ A second Setting to associate to this one in metadata
*/
explicit Setting(Linkage& linkage, const Type& default_val, const Type& min_val,
const Type& max_val, const std::string& name, Category category_,
u32 specialization_ = Specialization::Default, bool save_ = true,
bool runtime_modifiable_ = false, BasicSetting* other_setting_ = nullptr)
requires(ranged)
: BasicSetting(linkage, name, category_, save_, runtime_modifiable_, specialization_,
other_setting_),
value{default_val}, default_value{default_val}, maximum{max_val}, minimum{min_val} {}
/**
* Returns a reference to the setting's value.
*
* @returns A reference to the setting
*/
[[nodiscard]] virtual const Type& GetValue() const {
return value;
}
/**
* Sets the setting to the given value.
*
* @param val The desired value
*/
virtual void SetValue(const Type& val) {
Type temp{ranged ? std::clamp(val, minimum, maximum) : val};
std::swap(value, temp);
}
/**
* Returns the value that this setting was created with.
*
* @returns A reference to the default value
*/
[[nodiscard]] const Type& GetDefault() const {
return default_value;
}
[[nodiscard]] constexpr bool IsEnum() const override {
return std::is_enum_v<Type>;
}
protected:
[[nodiscard]] std::string ToString(const Type& value_) const {
if constexpr (std::is_same_v<Type, std::string>) {
return value_;
} else if constexpr (std::is_same_v<Type, std::optional<u32>>) {
return value_.has_value() ? std::to_string(*value_) : "none";
} else if constexpr (std::is_same_v<Type, bool>) {
return value_ ? "true" : "false";
} else if constexpr (std::is_same_v<Type, AudioEngine>) {
// Compatibility with old AudioEngine setting being a string
return CanonicalizeEnum(value_);
} else {
return std::to_string(static_cast<u64>(value_));
}
}
public:
/**
* Converts the value of the setting to a std::string. Respects the global state if the setting
* has one.
*
* @returns The current setting as a std::string
*/
[[nodiscard]] std::string ToString() const override {
return ToString(this->GetValue());
}
/**
* Returns the default value of the setting as a std::string.
*
* @returns The default value as a string.
*/
[[nodiscard]] std::string DefaultToString() const override {
return ToString(default_value);
}
/**
* Assigns a value to the setting.
*
* @param val The desired setting value
*
* @returns A reference to the setting
*/
virtual const Type& operator=(const Type& val) {
Type temp{ranged ? std::clamp(val, minimum, maximum) : val};
std::swap(value, temp);
return value;
}
/**
* Returns a reference to the setting.
*
* @returns A reference to the setting
*/
explicit virtual operator const Type&() const {
return value;
}
/**
* Converts the given value to the Setting's type of value. Uses SetValue to enter the setting,
* thus respecting its constraints.
*
* @param input The desired value
*/
void LoadString(const std::string& input) override final {
if (input.empty()) {
this->SetValue(this->GetDefault());
return;
}
try {
if constexpr (std::is_same_v<Type, std::string>) {
this->SetValue(input);
} else if constexpr (std::is_same_v<Type, std::optional<u32>>) {
this->SetValue(static_cast<u32>(std::stoul(input)));
} else if constexpr (std::is_same_v<Type, bool>) {
this->SetValue(input == "true");
} else if constexpr (std::is_same_v<Type, AudioEngine>) {
this->SetValue(ToEnum<Type>(input));
} else {
this->SetValue(static_cast<Type>(std::stoll(input)));
}
} catch (std::invalid_argument&) {
this->SetValue(this->GetDefault());
}
}
[[nodiscard]] std::string Canonicalize() const override final {
if constexpr (std::is_enum_v<Type>) {
return CanonicalizeEnum(this->GetValue());
} else {
return ToString(this->GetValue());
}
}
/**
* Gives us another way to identify the setting without having to go through a string.
*
* @returns the type_index of the setting's type
*/
[[nodiscard]] std::type_index TypeId() const override final {
return std::type_index(typeid(Type));
}
[[nodiscard]] constexpr u32 EnumIndex() const override final {
if constexpr (std::is_enum_v<Type>) {
return EnumMetadata<Type>::Index();
} else {
return std::numeric_limits<u32>::max();
}
}
[[nodiscard]] std::string MinVal() const override final {
return this->ToString(minimum);
}
[[nodiscard]] std::string MaxVal() const override final {
return this->ToString(maximum);
}
[[nodiscard]] constexpr bool Ranged() const override {
return ranged;
}
protected:
Type value{}; ///< The setting
const Type default_value{}; ///< The default value
const Type maximum{}; ///< Maximum allowed value of the setting
const Type minimum{}; ///< Minimum allowed value of the setting
};
/**
* The SwitchableSetting class is a slightly more complex version of the Setting class. This adds a
* custom setting to switch to when a guest application specifically requires it. The effect is that
* other components of the emulator can access the setting's intended value without any need for the
* component to ask whether the custom or global setting is needed at the moment.
*
* By default, the global setting is used.
*/
template <typename Type, bool ranged = false>
class SwitchableSetting : virtual public Setting<Type, ranged> {
public:
/**
* Sets a default value, label, and setting value.
*
* @param linkage Setting registry
* @param default_val Initial value of the setting, and default value of the setting
* @param name Label for the setting
* @param category_ Category of the setting AKA INI group
* @param specialization_ Suggestion for how frontend implementations represent this in a config
* @param save_ Suggests that this should or should not be saved to a frontend config file
* @param runtime_modifiable_ Suggests whether this is modifiable while a guest is loaded
* @param other_setting_ A second Setting to associate to this one in metadata
*/
template <typename T = BasicSetting>
explicit SwitchableSetting(Linkage& linkage, const Type& default_val, const std::string& name,
Category category_, u32 specialization_ = Specialization::Default,
bool save_ = true, bool runtime_modifiable_ = false,
typename std::enable_if<!ranged, T*>::type other_setting_ = nullptr)
: Setting<Type, false>{
linkage, default_val, name, category_, specialization_,
save_, runtime_modifiable_, other_setting_} {
linkage.restore_functions.emplace_back([this]() { this->SetGlobal(true); });
}
virtual ~SwitchableSetting() = default;
/**
* Sets a default value, minimum value, maximum value, and label.
*
* @param linkage Setting registry
* @param default_val Initial value of the setting, and default value of the setting
* @param min_val Sets the minimum allowed value of the setting
* @param max_val Sets the maximum allowed value of the setting
* @param name Label for the setting
* @param category_ Category of the setting AKA INI group
* @param specialization_ Suggestion for how frontend implementations represent this in a config
* @param save_ Suggests that this should or should not be saved to a frontend config file
* @param runtime_modifiable_ Suggests whether this is modifiable while a guest is loaded
* @param other_setting_ A second Setting to associate to this one in metadata
*/
template <typename T = BasicSetting>
explicit SwitchableSetting(Linkage& linkage, const Type& default_val, const Type& min_val,
const Type& max_val, const std::string& name, Category category_,
u32 specialization_ = Specialization::Default, bool save_ = true,
bool runtime_modifiable_ = false,
typename std::enable_if<ranged, T*>::type other_setting_ = nullptr)
: Setting<Type, true>{linkage, default_val, min_val,
max_val, name, category_,
specialization_, save_, runtime_modifiable_,
other_setting_} {
linkage.restore_functions.emplace_back([this]() { this->SetGlobal(true); });
}
/**
* Tells this setting to represent either the global or custom setting when other member
* functions are used.
*
* @param to_global Whether to use the global or custom setting.
*/
void SetGlobal(bool to_global) override final {
use_global = to_global;
}
/**
* Returns whether this setting is using the global setting or not.
*
* @returns The global state
*/
[[nodiscard]] bool UsingGlobal() const override final {
return use_global;
}
/**
* Returns either the global or custom setting depending on the values of this setting's global
* state or if the global value was specifically requested.
*
* @param need_global Request global value regardless of setting's state; defaults to false
*
* @returns The required value of the setting
*/
[[nodiscard]] const Type& GetValue() const override final {
if (use_global) {
return this->value;
}
return custom;
}
[[nodiscard]] const Type& GetValue(bool need_global) const {
if (use_global || need_global) {
return this->value;
}
return custom;
}
/**
* Sets the current setting value depending on the global state.
*
* @param val The new value
*/
void SetValue(const Type& val) override final {
Type temp{ranged ? std::clamp(val, this->minimum, this->maximum) : val};
if (use_global) {
std::swap(this->value, temp);
} else {
std::swap(custom, temp);
}
}
[[nodiscard]] constexpr bool Switchable() const override final {
return true;
}
[[nodiscard]] std::string ToStringGlobal() const override final {
return this->ToString(this->value);
}
/**
* Assigns the current setting value depending on the global state.
*
* @param val The new value
*
* @returns A reference to the current setting value
*/
const Type& operator=(const Type& val) override final {
Type temp{ranged ? std::clamp(val, this->minimum, this->maximum) : val};
if (use_global) {
std::swap(this->value, temp);
return this->value;
}
std::swap(custom, temp);
return custom;
}
/**
* Returns the current setting value depending on the global state.
*
* @returns A reference to the current setting value
*/
explicit operator const Type&() const override final {
if (use_global) {
return this->value;
}
return custom;
}
protected:
bool use_global{true}; ///< The setting's global state
Type custom{}; ///< The custom value of the setting
};
} // namespace Settings

View File

@ -460,6 +460,11 @@ S operator&(const S& i, const swap_struct_t<T, F> v) {
return i & v.swap(); return i & v.swap();
} }
template <typename S, typename T, typename F>
S operator&(const swap_struct_t<T, F> v, const S& i) {
return static_cast<S>(v.swap() & i);
}
// Comparison // Comparison
template <typename S, typename T, typename F> template <typename S, typename T, typename F>
bool operator<(const S& p, const swap_struct_t<T, F> v) { bool operator<(const S& p, const swap_struct_t<T, F> v) {

View File

@ -37,49 +37,6 @@ add_library(core STATIC
debugger/gdbstub.h debugger/gdbstub.h
device_memory.cpp device_memory.cpp
device_memory.h device_memory.h
file_sys/fssystem/fs_i_storage.h
file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.cpp
file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.h
file_sys/fssystem/fssystem_aes_ctr_storage.cpp
file_sys/fssystem/fssystem_aes_ctr_storage.h
file_sys/fssystem/fssystem_aes_xts_storage.cpp
file_sys/fssystem/fssystem_aes_xts_storage.h
file_sys/fssystem/fssystem_alignment_matching_storage.h
file_sys/fssystem/fssystem_alignment_matching_storage_impl.cpp
file_sys/fssystem/fssystem_alignment_matching_storage_impl.h
file_sys/fssystem/fssystem_bucket_tree.cpp
file_sys/fssystem/fssystem_bucket_tree.h
file_sys/fssystem/fssystem_bucket_tree_utils.h
file_sys/fssystem/fssystem_compressed_storage.h
file_sys/fssystem/fssystem_compression_common.h
file_sys/fssystem/fssystem_compression_configuration.cpp
file_sys/fssystem/fssystem_compression_configuration.h
file_sys/fssystem/fssystem_crypto_configuration.cpp
file_sys/fssystem/fssystem_crypto_configuration.h
file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.cpp
file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h
file_sys/fssystem/fssystem_hierarchical_sha256_storage.cpp
file_sys/fssystem/fssystem_hierarchical_sha256_storage.h
file_sys/fssystem/fssystem_indirect_storage.cpp
file_sys/fssystem/fssystem_indirect_storage.h
file_sys/fssystem/fssystem_integrity_romfs_storage.cpp
file_sys/fssystem/fssystem_integrity_romfs_storage.h
file_sys/fssystem/fssystem_integrity_verification_storage.cpp
file_sys/fssystem/fssystem_integrity_verification_storage.h
file_sys/fssystem/fssystem_memory_resource_buffer_hold_storage.h
file_sys/fssystem/fssystem_nca_file_system_driver.cpp
file_sys/fssystem/fssystem_nca_file_system_driver.h
file_sys/fssystem/fssystem_nca_header.cpp
file_sys/fssystem/fssystem_nca_header.h
file_sys/fssystem/fssystem_nca_reader.cpp
file_sys/fssystem/fssystem_pooled_buffer.cpp
file_sys/fssystem/fssystem_pooled_buffer.h
file_sys/fssystem/fssystem_sparse_storage.cpp
file_sys/fssystem/fssystem_sparse_storage.h
file_sys/fssystem/fssystem_switch_storage.h
file_sys/fssystem/fssystem_utility.cpp
file_sys/fssystem/fssystem_utility.h
file_sys/fssystem/fs_types.h
file_sys/bis_factory.cpp file_sys/bis_factory.cpp
file_sys/bis_factory.h file_sys/bis_factory.h
file_sys/card_image.cpp file_sys/card_image.cpp
@ -100,6 +57,8 @@ add_library(core STATIC
file_sys/mode.h file_sys/mode.h
file_sys/nca_metadata.cpp file_sys/nca_metadata.cpp
file_sys/nca_metadata.h file_sys/nca_metadata.h
file_sys/nca_patch.cpp
file_sys/nca_patch.h
file_sys/partition_filesystem.cpp file_sys/partition_filesystem.cpp
file_sys/partition_filesystem.h file_sys/partition_filesystem.h
file_sys/patch_manager.cpp file_sys/patch_manager.cpp

View File

@ -287,7 +287,7 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable*
} }
} else { } else {
// Unsafe optimizations // Unsafe optimizations
if (Settings::values.cpu_accuracy.GetValue() == Settings::CpuAccuracy::Unsafe) { if (Settings::values.cpu_accuracy.GetValue() == Settings::CPUAccuracy::Unsafe) {
config.unsafe_optimizations = true; config.unsafe_optimizations = true;
if (Settings::values.cpuopt_unsafe_unfuse_fma) { if (Settings::values.cpuopt_unsafe_unfuse_fma) {
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA; config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA;
@ -307,7 +307,7 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable*
} }
// Curated optimizations // Curated optimizations
if (Settings::values.cpu_accuracy.GetValue() == Settings::CpuAccuracy::Auto) { if (Settings::values.cpu_accuracy.GetValue() == Settings::CPUAccuracy::Auto) {
config.unsafe_optimizations = true; config.unsafe_optimizations = true;
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA; config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA;
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_IgnoreStandardFPCRValue; config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_IgnoreStandardFPCRValue;
@ -316,7 +316,7 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable*
} }
// Paranoia mode for debugging optimizations // Paranoia mode for debugging optimizations
if (Settings::values.cpu_accuracy.GetValue() == Settings::CpuAccuracy::Paranoid) { if (Settings::values.cpu_accuracy.GetValue() == Settings::CPUAccuracy::Paranoid) {
config.unsafe_optimizations = false; config.unsafe_optimizations = false;
config.optimizations = Dynarmic::no_optimizations; config.optimizations = Dynarmic::no_optimizations;
} }

View File

@ -347,7 +347,7 @@ std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable*
} }
} else { } else {
// Unsafe optimizations // Unsafe optimizations
if (Settings::values.cpu_accuracy.GetValue() == Settings::CpuAccuracy::Unsafe) { if (Settings::values.cpu_accuracy.GetValue() == Settings::CPUAccuracy::Unsafe) {
config.unsafe_optimizations = true; config.unsafe_optimizations = true;
if (Settings::values.cpuopt_unsafe_unfuse_fma) { if (Settings::values.cpuopt_unsafe_unfuse_fma) {
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA; config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA;
@ -367,7 +367,7 @@ std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable*
} }
// Curated optimizations // Curated optimizations
if (Settings::values.cpu_accuracy.GetValue() == Settings::CpuAccuracy::Auto) { if (Settings::values.cpu_accuracy.GetValue() == Settings::CPUAccuracy::Auto) {
config.unsafe_optimizations = true; config.unsafe_optimizations = true;
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA; config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA;
config.fastmem_address_space_bits = 64; config.fastmem_address_space_bits = 64;
@ -375,7 +375,7 @@ std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable*
} }
// Paranoia mode for debugging optimizations // Paranoia mode for debugging optimizations
if (Settings::values.cpu_accuracy.GetValue() == Settings::CpuAccuracy::Paranoid) { if (Settings::values.cpu_accuracy.GetValue() == Settings::CPUAccuracy::Paranoid) {
config.unsafe_optimizations = false; config.unsafe_optimizations = false;
config.optimizations = Dynarmic::no_optimizations; config.optimizations = Dynarmic::no_optimizations;
} }

View File

@ -12,7 +12,6 @@
#include "common/logging/log.h" #include "common/logging/log.h"
#include "common/microprofile.h" #include "common/microprofile.h"
#include "common/settings.h" #include "common/settings.h"
#include "common/settings_enums.h"
#include "common/string_util.h" #include "common/string_util.h"
#include "core/arm/exclusive_monitor.h" #include "core/arm/exclusive_monitor.h"
#include "core/core.h" #include "core/core.h"
@ -141,13 +140,16 @@ struct System::Impl {
device_memory = std::make_unique<Core::DeviceMemory>(); device_memory = std::make_unique<Core::DeviceMemory>();
is_multicore = Settings::values.use_multi_core.GetValue(); is_multicore = Settings::values.use_multi_core.GetValue();
extended_memory_layout = extended_memory_layout = Settings::values.use_unsafe_extended_memory_layout.GetValue();
Settings::values.memory_layout_mode.GetValue() != Settings::MemoryLayout::Memory_4Gb;
core_timing.SetMulticore(is_multicore); core_timing.SetMulticore(is_multicore);
core_timing.Initialize([&system]() { system.RegisterHostThread(); }); core_timing.Initialize([&system]() { system.RegisterHostThread(); });
RefreshTime(); const auto posix_time = std::chrono::system_clock::now().time_since_epoch();
const auto current_time =
std::chrono::duration_cast<std::chrono::seconds>(posix_time).count();
Settings::values.custom_rtc_differential =
Settings::values.custom_rtc.value_or(current_time) - current_time;
// Create a default fs if one doesn't already exist. // Create a default fs if one doesn't already exist.
if (virtual_filesystem == nullptr) { if (virtual_filesystem == nullptr) {
@ -170,8 +172,7 @@ struct System::Impl {
void ReinitializeIfNecessary(System& system) { void ReinitializeIfNecessary(System& system) {
const bool must_reinitialize = const bool must_reinitialize =
is_multicore != Settings::values.use_multi_core.GetValue() || is_multicore != Settings::values.use_multi_core.GetValue() ||
extended_memory_layout != (Settings::values.memory_layout_mode.GetValue() != extended_memory_layout != Settings::values.use_unsafe_extended_memory_layout.GetValue();
Settings::MemoryLayout::Memory_4Gb);
if (!must_reinitialize) { if (!must_reinitialize) {
return; return;
@ -180,22 +181,11 @@ struct System::Impl {
LOG_DEBUG(Kernel, "Re-initializing"); LOG_DEBUG(Kernel, "Re-initializing");
is_multicore = Settings::values.use_multi_core.GetValue(); is_multicore = Settings::values.use_multi_core.GetValue();
extended_memory_layout = extended_memory_layout = Settings::values.use_unsafe_extended_memory_layout.GetValue();
Settings::values.memory_layout_mode.GetValue() != Settings::MemoryLayout::Memory_4Gb;
Initialize(system); Initialize(system);
} }
void RefreshTime() {
const auto posix_time = std::chrono::system_clock::now().time_since_epoch();
const auto current_time =
std::chrono::duration_cast<std::chrono::seconds>(posix_time).count();
Settings::values.custom_rtc_differential =
(Settings::values.custom_rtc_enabled ? Settings::values.custom_rtc.GetValue()
: current_time) -
current_time;
}
void Run() { void Run() {
std::unique_lock<std::mutex> lk(suspend_guard); std::unique_lock<std::mutex> lk(suspend_guard);
@ -1038,8 +1028,6 @@ void System::Exit() {
} }
void System::ApplySettings() { void System::ApplySettings() {
impl->RefreshTime();
if (IsPoweredOn()) { if (IsPoweredOn()) {
Renderer().RefreshBaseSettings(); Renderer().RefreshBaseSettings();
} }

View File

@ -263,23 +263,6 @@ void GDBStub::ExecuteCommand(std::string_view packet, std::vector<DebuggerAction
std::vector<u8> mem(size); std::vector<u8> mem(size);
if (system.ApplicationMemory().ReadBlock(addr, mem.data(), size)) { if (system.ApplicationMemory().ReadBlock(addr, mem.data(), size)) {
// Restore any bytes belonging to replaced instructions.
auto it = replaced_instructions.lower_bound(addr);
for (; it != replaced_instructions.end() && it->first < addr + size; it++) {
// Get the bytes of the instruction we previously replaced.
const u32 original_bytes = it->second;
// Calculate where to start writing to the output buffer.
const size_t output_offset = it->first - addr;
// Calculate how many bytes to write.
// The loop condition ensures output_offset < size.
const size_t n = std::min<size_t>(size - output_offset, sizeof(u32));
// Write the bytes to the output buffer.
std::memcpy(mem.data() + output_offset, &original_bytes, n);
}
SendReply(Common::HexToString(mem)); SendReply(Common::HexToString(mem));
} else { } else {
SendReply(GDB_STUB_REPLY_ERR); SendReply(GDB_STUB_REPLY_ERR);

View File

@ -31,9 +31,13 @@ XCI::XCI(VirtualFile file_, u64 program_id, size_t program_index)
: file(std::move(file_)), program_nca_status{Loader::ResultStatus::ErrorXCIMissingProgramNCA}, : file(std::move(file_)), program_nca_status{Loader::ResultStatus::ErrorXCIMissingProgramNCA},
partitions(partition_names.size()), partitions(partition_names.size()),
partitions_raw(partition_names.size()), keys{Core::Crypto::KeyManager::Instance()} { partitions_raw(partition_names.size()), keys{Core::Crypto::KeyManager::Instance()} {
const auto header_status = TryReadHeader(); if (file->ReadObject(&header) != sizeof(GamecardHeader)) {
if (header_status != Loader::ResultStatus::Success) { status = Loader::ResultStatus::ErrorBadXCIHeader;
status = header_status; return;
}
if (header.magic != Common::MakeMagic('H', 'E', 'A', 'D')) {
status = Loader::ResultStatus::ErrorBadXCIHeader;
return; return;
} }
@ -179,9 +183,9 @@ u32 XCI::GetSystemUpdateVersion() {
} }
for (const auto& update_file : update->GetFiles()) { for (const auto& update_file : update->GetFiles()) {
NCA nca{update_file}; NCA nca{update_file, nullptr, 0};
if (nca.GetStatus() != Loader::ResultStatus::Success || nca.GetSubdirectories().empty()) { if (nca.GetStatus() != Loader::ResultStatus::Success) {
continue; continue;
} }
@ -292,7 +296,7 @@ Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) {
continue; continue;
} }
auto nca = std::make_shared<NCA>(partition_file); auto nca = std::make_shared<NCA>(partition_file, nullptr, 0);
if (nca->IsUpdate()) { if (nca->IsUpdate()) {
continue; continue;
} }
@ -312,44 +316,6 @@ Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) {
return Loader::ResultStatus::Success; return Loader::ResultStatus::Success;
} }
Loader::ResultStatus XCI::TryReadHeader() {
constexpr size_t CardInitialDataRegionSize = 0x1000;
// Define the function we'll use to determine if we read a valid header.
const auto ReadCardHeader = [&]() {
// Ensure we can read the entire header. If we can't, we can't read the card image.
if (file->ReadObject(&header) != sizeof(GamecardHeader)) {
return Loader::ResultStatus::ErrorBadXCIHeader;
}
// Ensure the header magic matches. If it doesn't, this isn't a card image header.
if (header.magic != Common::MakeMagic('H', 'E', 'A', 'D')) {
return Loader::ResultStatus::ErrorBadXCIHeader;
}
// We read a card image header.
return Loader::ResultStatus::Success;
};
// Try to read the header directly.
if (ReadCardHeader() == Loader::ResultStatus::Success) {
return Loader::ResultStatus::Success;
}
// Get the size of the file.
const size_t card_image_size = file->GetSize();
// If we are large enough to have a key area, offset past the key area and retry.
if (card_image_size >= CardInitialDataRegionSize) {
file = std::make_shared<OffsetVfsFile>(file, card_image_size - CardInitialDataRegionSize,
CardInitialDataRegionSize);
return ReadCardHeader();
}
// We had no header and aren't large enough to have a key area, so this can't be parsed.
return Loader::ResultStatus::ErrorBadXCIHeader;
}
u8 XCI::GetFormatVersion() { u8 XCI::GetFormatVersion() {
return GetLogoPartition() == nullptr ? 0x1 : 0x2; return GetLogoPartition() == nullptr ? 0x1 : 0x2;
} }

View File

@ -128,7 +128,6 @@ public:
private: private:
Loader::ResultStatus AddNCAFromPartition(XCIPartition part); Loader::ResultStatus AddNCAFromPartition(XCIPartition part);
Loader::ResultStatus TryReadHeader();
VirtualFile file; VirtualFile file;
GamecardHeader header{}; GamecardHeader header{};

View File

@ -12,110 +12,546 @@
#include "core/crypto/ctr_encryption_layer.h" #include "core/crypto/ctr_encryption_layer.h"
#include "core/crypto/key_manager.h" #include "core/crypto/key_manager.h"
#include "core/file_sys/content_archive.h" #include "core/file_sys/content_archive.h"
#include "core/file_sys/nca_patch.h"
#include "core/file_sys/partition_filesystem.h" #include "core/file_sys/partition_filesystem.h"
#include "core/file_sys/vfs_offset.h" #include "core/file_sys/vfs_offset.h"
#include "core/loader/loader.h" #include "core/loader/loader.h"
#include "core/file_sys/fssystem/fssystem_compression_configuration.h"
#include "core/file_sys/fssystem/fssystem_crypto_configuration.h"
#include "core/file_sys/fssystem/fssystem_nca_file_system_driver.h"
namespace FileSys { namespace FileSys {
NCA::NCA(VirtualFile file_, const NCA* base_nca) // Media offsets in headers are stored divided by 512. Mult. by this to get real offset.
: file(std::move(file_)), keys{Core::Crypto::KeyManager::Instance()} { constexpr u64 MEDIA_OFFSET_MULTIPLIER = 0x200;
constexpr u64 SECTION_HEADER_SIZE = 0x200;
constexpr u64 SECTION_HEADER_OFFSET = 0x400;
constexpr u32 IVFC_MAX_LEVEL = 6;
enum class NCASectionFilesystemType : u8 {
PFS0 = 0x2,
ROMFS = 0x3,
};
struct IVFCLevel {
u64_le offset;
u64_le size;
u32_le block_size;
u32_le reserved;
};
static_assert(sizeof(IVFCLevel) == 0x18, "IVFCLevel has incorrect size.");
struct IVFCHeader {
u32_le magic;
u32_le magic_number;
INSERT_PADDING_BYTES_NOINIT(8);
std::array<IVFCLevel, 6> levels;
INSERT_PADDING_BYTES_NOINIT(64);
};
static_assert(sizeof(IVFCHeader) == 0xE0, "IVFCHeader has incorrect size.");
struct NCASectionHeaderBlock {
INSERT_PADDING_BYTES_NOINIT(3);
NCASectionFilesystemType filesystem_type;
NCASectionCryptoType crypto_type;
INSERT_PADDING_BYTES_NOINIT(3);
};
static_assert(sizeof(NCASectionHeaderBlock) == 0x8, "NCASectionHeaderBlock has incorrect size.");
struct NCABucketInfo {
u64 table_offset;
u64 table_size;
std::array<u8, 0x10> table_header;
};
static_assert(sizeof(NCABucketInfo) == 0x20, "NCABucketInfo has incorrect size.");
struct NCASparseInfo {
NCABucketInfo bucket;
u64 physical_offset;
u16 generation;
INSERT_PADDING_BYTES_NOINIT(0x6);
};
static_assert(sizeof(NCASparseInfo) == 0x30, "NCASparseInfo has incorrect size.");
struct NCACompressionInfo {
NCABucketInfo bucket;
INSERT_PADDING_BYTES_NOINIT(0x8);
};
static_assert(sizeof(NCACompressionInfo) == 0x28, "NCACompressionInfo has incorrect size.");
struct NCASectionRaw {
NCASectionHeaderBlock header;
std::array<u8, 0x138> block_data;
std::array<u8, 0x8> section_ctr;
NCASparseInfo sparse_info;
NCACompressionInfo compression_info;
INSERT_PADDING_BYTES_NOINIT(0x60);
};
static_assert(sizeof(NCASectionRaw) == 0x200, "NCASectionRaw has incorrect size.");
struct PFS0Superblock {
NCASectionHeaderBlock header_block;
std::array<u8, 0x20> hash;
u32_le size;
INSERT_PADDING_BYTES_NOINIT(4);
u64_le hash_table_offset;
u64_le hash_table_size;
u64_le pfs0_header_offset;
u64_le pfs0_size;
INSERT_PADDING_BYTES_NOINIT(0x1B0);
};
static_assert(sizeof(PFS0Superblock) == 0x200, "PFS0Superblock has incorrect size.");
struct RomFSSuperblock {
NCASectionHeaderBlock header_block;
IVFCHeader ivfc;
INSERT_PADDING_BYTES_NOINIT(0x118);
};
static_assert(sizeof(RomFSSuperblock) == 0x200, "RomFSSuperblock has incorrect size.");
struct BKTRHeader {
u64_le offset;
u64_le size;
u32_le magic;
INSERT_PADDING_BYTES_NOINIT(0x4);
u32_le number_entries;
INSERT_PADDING_BYTES_NOINIT(0x4);
};
static_assert(sizeof(BKTRHeader) == 0x20, "BKTRHeader has incorrect size.");
struct BKTRSuperblock {
NCASectionHeaderBlock header_block;
IVFCHeader ivfc;
INSERT_PADDING_BYTES_NOINIT(0x18);
BKTRHeader relocation;
BKTRHeader subsection;
INSERT_PADDING_BYTES_NOINIT(0xC0);
};
static_assert(sizeof(BKTRSuperblock) == 0x200, "BKTRSuperblock has incorrect size.");
union NCASectionHeader {
NCASectionRaw raw{};
PFS0Superblock pfs0;
RomFSSuperblock romfs;
BKTRSuperblock bktr;
};
static_assert(sizeof(NCASectionHeader) == 0x200, "NCASectionHeader has incorrect size.");
static bool IsValidNCA(const NCAHeader& header) {
// TODO(DarkLordZach): Add NCA2/NCA0 support.
return header.magic == Common::MakeMagic('N', 'C', 'A', '3');
}
NCA::NCA(VirtualFile file_, VirtualFile bktr_base_romfs_, u64 bktr_base_ivfc_offset)
: file(std::move(file_)),
bktr_base_romfs(std::move(bktr_base_romfs_)), keys{Core::Crypto::KeyManager::Instance()} {
if (file == nullptr) { if (file == nullptr) {
status = Loader::ResultStatus::ErrorNullFile; status = Loader::ResultStatus::ErrorNullFile;
return; return;
} }
reader = std::make_shared<NcaReader>(); if (sizeof(NCAHeader) != file->ReadObject(&header)) {
if (Result rc = LOG_ERROR(Loader, "File reader errored out during header read.");
reader->Initialize(file, GetCryptoConfiguration(), GetNcaCompressionConfiguration());
R_FAILED(rc)) {
if (rc != ResultInvalidNcaSignature) {
LOG_ERROR(Loader, "File reader errored out during header read: {:#x}",
rc.GetInnerValue());
}
status = Loader::ResultStatus::ErrorBadNCAHeader; status = Loader::ResultStatus::ErrorBadNCAHeader;
return; return;
} }
RightsId rights_id{}; if (!HandlePotentialHeaderDecryption()) {
reader->GetRightsId(rights_id.data(), rights_id.size());
if (rights_id != RightsId{}) {
// External decryption key required; provide it here.
const auto key_generation = std::max<s32>(reader->GetKeyGeneration(), 1) - 1;
u128 rights_id_u128;
std::memcpy(rights_id_u128.data(), rights_id.data(), sizeof(rights_id));
auto titlekey =
keys.GetKey(Core::Crypto::S128KeyType::Titlekey, rights_id_u128[1], rights_id_u128[0]);
if (titlekey == Core::Crypto::Key128{}) {
status = Loader::ResultStatus::ErrorMissingTitlekey;
return; return;
} }
if (!keys.HasKey(Core::Crypto::S128KeyType::Titlekek, key_generation)) { has_rights_id = std::ranges::any_of(header.rights_id, [](char c) { return c != '\0'; });
status = Loader::ResultStatus::ErrorMissingTitlekek;
const std::vector<NCASectionHeader> sections = ReadSectionHeaders();
is_update = std::ranges::any_of(sections, [](const NCASectionHeader& nca_header) {
return nca_header.raw.header.crypto_type == NCASectionCryptoType::BKTR;
});
if (!ReadSections(sections, bktr_base_ivfc_offset)) {
return; return;
} }
auto titlekek = keys.GetKey(Core::Crypto::S128KeyType::Titlekek, key_generation);
Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(titlekek, Core::Crypto::Mode::ECB);
cipher.Transcode(titlekey.data(), titlekey.size(), titlekey.data(),
Core::Crypto::Op::Decrypt);
reader->SetExternalDecryptionKey(titlekey.data(), titlekey.size());
}
const s32 fs_count = reader->GetFsCount();
NcaFileSystemDriver fs(base_nca ? base_nca->reader : nullptr, reader);
std::vector<VirtualFile> filesystems(fs_count);
for (s32 i = 0; i < fs_count; i++) {
NcaFsHeaderReader header_reader;
const Result rc = fs.OpenStorage(&filesystems[i], &header_reader, i);
if (R_FAILED(rc)) {
LOG_ERROR(Loader, "File reader errored out during read of section {}: {:#x}", i,
rc.GetInnerValue());
status = Loader::ResultStatus::ErrorBadNCAHeader;
return;
}
if (header_reader.GetFsType() == NcaFsHeader::FsType::RomFs) {
files.push_back(filesystems[i]);
romfs = files.back();
}
if (header_reader.GetFsType() == NcaFsHeader::FsType::PartitionFs) {
auto npfs = std::make_shared<PartitionFilesystem>(filesystems[i]);
if (npfs->GetStatus() == Loader::ResultStatus::Success) {
dirs.push_back(npfs);
if (IsDirectoryExeFS(npfs)) {
exefs = dirs.back();
} else if (IsDirectoryLogoPartition(npfs)) {
logo = dirs.back();
} else {
continue;
}
}
}
if (header_reader.GetEncryptionType() == NcaFsHeader::EncryptionType::AesCtrEx) {
is_update = true;
}
}
if (is_update && base_nca == nullptr) {
status = Loader::ResultStatus::ErrorMissingBKTRBaseRomFS;
} else {
status = Loader::ResultStatus::Success; status = Loader::ResultStatus::Success;
}
} }
NCA::~NCA() = default; NCA::~NCA() = default;
bool NCA::CheckSupportedNCA(const NCAHeader& nca_header) {
if (nca_header.magic == Common::MakeMagic('N', 'C', 'A', '2')) {
status = Loader::ResultStatus::ErrorNCA2;
return false;
}
if (nca_header.magic == Common::MakeMagic('N', 'C', 'A', '0')) {
status = Loader::ResultStatus::ErrorNCA0;
return false;
}
return true;
}
bool NCA::HandlePotentialHeaderDecryption() {
if (IsValidNCA(header)) {
return true;
}
if (!CheckSupportedNCA(header)) {
return false;
}
NCAHeader dec_header{};
Core::Crypto::AESCipher<Core::Crypto::Key256> cipher(
keys.GetKey(Core::Crypto::S256KeyType::Header), Core::Crypto::Mode::XTS);
cipher.XTSTranscode(&header, sizeof(NCAHeader), &dec_header, 0, 0x200,
Core::Crypto::Op::Decrypt);
if (IsValidNCA(dec_header)) {
header = dec_header;
encrypted = true;
} else {
if (!CheckSupportedNCA(dec_header)) {
return false;
}
if (keys.HasKey(Core::Crypto::S256KeyType::Header)) {
status = Loader::ResultStatus::ErrorIncorrectHeaderKey;
} else {
status = Loader::ResultStatus::ErrorMissingHeaderKey;
}
return false;
}
return true;
}
std::vector<NCASectionHeader> NCA::ReadSectionHeaders() const {
const std::ptrdiff_t number_sections =
std::ranges::count_if(header.section_tables, [](const NCASectionTableEntry& entry) {
return entry.media_offset > 0;
});
std::vector<NCASectionHeader> sections(number_sections);
const auto length_sections = SECTION_HEADER_SIZE * number_sections;
if (encrypted) {
auto raw = file->ReadBytes(length_sections, SECTION_HEADER_OFFSET);
Core::Crypto::AESCipher<Core::Crypto::Key256> cipher(
keys.GetKey(Core::Crypto::S256KeyType::Header), Core::Crypto::Mode::XTS);
cipher.XTSTranscode(raw.data(), length_sections, sections.data(), 2, SECTION_HEADER_SIZE,
Core::Crypto::Op::Decrypt);
} else {
file->ReadBytes(sections.data(), length_sections, SECTION_HEADER_OFFSET);
}
return sections;
}
bool NCA::ReadSections(const std::vector<NCASectionHeader>& sections, u64 bktr_base_ivfc_offset) {
for (std::size_t i = 0; i < sections.size(); ++i) {
const auto& section = sections[i];
if (section.raw.sparse_info.bucket.table_offset != 0 &&
section.raw.sparse_info.bucket.table_size != 0) {
LOG_ERROR(Loader, "Sparse NCAs are not supported.");
status = Loader::ResultStatus::ErrorSparseNCA;
return false;
}
if (section.raw.compression_info.bucket.table_offset != 0 &&
section.raw.compression_info.bucket.table_size != 0) {
LOG_ERROR(Loader, "Compressed NCAs are not supported.");
status = Loader::ResultStatus::ErrorCompressedNCA;
return false;
}
if (section.raw.header.filesystem_type == NCASectionFilesystemType::ROMFS) {
if (!ReadRomFSSection(section, header.section_tables[i], bktr_base_ivfc_offset)) {
return false;
}
} else if (section.raw.header.filesystem_type == NCASectionFilesystemType::PFS0) {
if (!ReadPFS0Section(section, header.section_tables[i])) {
return false;
}
}
}
return true;
}
bool NCA::ReadRomFSSection(const NCASectionHeader& section, const NCASectionTableEntry& entry,
u64 bktr_base_ivfc_offset) {
const std::size_t base_offset = entry.media_offset * MEDIA_OFFSET_MULTIPLIER;
ivfc_offset = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset;
const std::size_t romfs_offset = base_offset + ivfc_offset;
const std::size_t romfs_size = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].size;
auto raw = std::make_shared<OffsetVfsFile>(file, romfs_size, romfs_offset);
auto dec = Decrypt(section, raw, romfs_offset);
if (dec == nullptr) {
if (status != Loader::ResultStatus::Success)
return false;
if (has_rights_id)
status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek;
else
status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey;
return false;
}
if (section.raw.header.crypto_type == NCASectionCryptoType::BKTR) {
if (section.bktr.relocation.magic != Common::MakeMagic('B', 'K', 'T', 'R') ||
section.bktr.subsection.magic != Common::MakeMagic('B', 'K', 'T', 'R')) {
status = Loader::ResultStatus::ErrorBadBKTRHeader;
return false;
}
if (section.bktr.relocation.offset + section.bktr.relocation.size !=
section.bktr.subsection.offset) {
status = Loader::ResultStatus::ErrorBKTRSubsectionNotAfterRelocation;
return false;
}
const u64 size = MEDIA_OFFSET_MULTIPLIER * (entry.media_end_offset - entry.media_offset);
if (section.bktr.subsection.offset + section.bktr.subsection.size != size) {
status = Loader::ResultStatus::ErrorBKTRSubsectionNotAtEnd;
return false;
}
const u64 offset = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset;
RelocationBlock relocation_block{};
if (dec->ReadObject(&relocation_block, section.bktr.relocation.offset - offset) !=
sizeof(RelocationBlock)) {
status = Loader::ResultStatus::ErrorBadRelocationBlock;
return false;
}
SubsectionBlock subsection_block{};
if (dec->ReadObject(&subsection_block, section.bktr.subsection.offset - offset) !=
sizeof(RelocationBlock)) {
status = Loader::ResultStatus::ErrorBadSubsectionBlock;
return false;
}
std::vector<RelocationBucketRaw> relocation_buckets_raw(
(section.bktr.relocation.size - sizeof(RelocationBlock)) / sizeof(RelocationBucketRaw));
if (dec->ReadBytes(relocation_buckets_raw.data(),
section.bktr.relocation.size - sizeof(RelocationBlock),
section.bktr.relocation.offset + sizeof(RelocationBlock) - offset) !=
section.bktr.relocation.size - sizeof(RelocationBlock)) {
status = Loader::ResultStatus::ErrorBadRelocationBuckets;
return false;
}
std::vector<SubsectionBucketRaw> subsection_buckets_raw(
(section.bktr.subsection.size - sizeof(SubsectionBlock)) / sizeof(SubsectionBucketRaw));
if (dec->ReadBytes(subsection_buckets_raw.data(),
section.bktr.subsection.size - sizeof(SubsectionBlock),
section.bktr.subsection.offset + sizeof(SubsectionBlock) - offset) !=
section.bktr.subsection.size - sizeof(SubsectionBlock)) {
status = Loader::ResultStatus::ErrorBadSubsectionBuckets;
return false;
}
std::vector<RelocationBucket> relocation_buckets(relocation_buckets_raw.size());
std::ranges::transform(relocation_buckets_raw, relocation_buckets.begin(),
&ConvertRelocationBucketRaw);
std::vector<SubsectionBucket> subsection_buckets(subsection_buckets_raw.size());
std::ranges::transform(subsection_buckets_raw, subsection_buckets.begin(),
&ConvertSubsectionBucketRaw);
u32 ctr_low;
std::memcpy(&ctr_low, section.raw.section_ctr.data(), sizeof(ctr_low));
subsection_buckets.back().entries.push_back({section.bktr.relocation.offset, {0}, ctr_low});
subsection_buckets.back().entries.push_back({size, {0}, 0});
std::optional<Core::Crypto::Key128> key;
if (encrypted) {
if (has_rights_id) {
status = Loader::ResultStatus::Success;
key = GetTitlekey();
if (!key) {
status = Loader::ResultStatus::ErrorMissingTitlekey;
return false;
}
} else {
key = GetKeyAreaKey(NCASectionCryptoType::BKTR);
if (!key) {
status = Loader::ResultStatus::ErrorMissingKeyAreaKey;
return false;
}
}
}
if (bktr_base_romfs == nullptr) {
status = Loader::ResultStatus::ErrorMissingBKTRBaseRomFS;
return false;
}
auto bktr = std::make_shared<BKTR>(
bktr_base_romfs, std::make_shared<OffsetVfsFile>(file, romfs_size, base_offset),
relocation_block, relocation_buckets, subsection_block, subsection_buckets, encrypted,
encrypted ? *key : Core::Crypto::Key128{}, base_offset, bktr_base_ivfc_offset,
section.raw.section_ctr);
// BKTR applies to entire IVFC, so make an offset version to level 6
files.push_back(std::make_shared<OffsetVfsFile>(
bktr, romfs_size, section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset));
} else {
files.push_back(std::move(dec));
}
romfs = files.back();
return true;
}
bool NCA::ReadPFS0Section(const NCASectionHeader& section, const NCASectionTableEntry& entry) {
const u64 offset = (static_cast<u64>(entry.media_offset) * MEDIA_OFFSET_MULTIPLIER) +
section.pfs0.pfs0_header_offset;
const u64 size = MEDIA_OFFSET_MULTIPLIER * (entry.media_end_offset - entry.media_offset);
auto dec = Decrypt(section, std::make_shared<OffsetVfsFile>(file, size, offset), offset);
if (dec != nullptr) {
auto npfs = std::make_shared<PartitionFilesystem>(std::move(dec));
if (npfs->GetStatus() == Loader::ResultStatus::Success) {
dirs.push_back(std::move(npfs));
if (IsDirectoryExeFS(dirs.back()))
exefs = dirs.back();
else if (IsDirectoryLogoPartition(dirs.back()))
logo = dirs.back();
} else {
if (has_rights_id)
status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek;
else
status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey;
return false;
}
} else {
if (status != Loader::ResultStatus::Success)
return false;
if (has_rights_id)
status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek;
else
status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey;
return false;
}
return true;
}
u8 NCA::GetCryptoRevision() const {
u8 master_key_id = header.crypto_type;
if (header.crypto_type_2 > master_key_id)
master_key_id = header.crypto_type_2;
if (master_key_id > 0)
--master_key_id;
return master_key_id;
}
std::optional<Core::Crypto::Key128> NCA::GetKeyAreaKey(NCASectionCryptoType type) const {
const auto master_key_id = GetCryptoRevision();
if (!keys.HasKey(Core::Crypto::S128KeyType::KeyArea, master_key_id, header.key_index)) {
return std::nullopt;
}
std::vector<u8> key_area(header.key_area.begin(), header.key_area.end());
Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(
keys.GetKey(Core::Crypto::S128KeyType::KeyArea, master_key_id, header.key_index),
Core::Crypto::Mode::ECB);
cipher.Transcode(key_area.data(), key_area.size(), key_area.data(), Core::Crypto::Op::Decrypt);
Core::Crypto::Key128 out{};
if (type == NCASectionCryptoType::XTS) {
std::copy(key_area.begin(), key_area.begin() + 0x10, out.begin());
} else if (type == NCASectionCryptoType::CTR || type == NCASectionCryptoType::BKTR) {
std::copy(key_area.begin() + 0x20, key_area.begin() + 0x30, out.begin());
} else {
LOG_CRITICAL(Crypto, "Called GetKeyAreaKey on invalid NCASectionCryptoType type={:02X}",
type);
}
u128 out_128{};
std::memcpy(out_128.data(), out.data(), sizeof(u128));
LOG_TRACE(Crypto, "called with crypto_rev={:02X}, kak_index={:02X}, key={:016X}{:016X}",
master_key_id, header.key_index, out_128[1], out_128[0]);
return out;
}
std::optional<Core::Crypto::Key128> NCA::GetTitlekey() {
const auto master_key_id = GetCryptoRevision();
u128 rights_id{};
memcpy(rights_id.data(), header.rights_id.data(), 16);
if (rights_id == u128{}) {
status = Loader::ResultStatus::ErrorInvalidRightsID;
return std::nullopt;
}
auto titlekey = keys.GetKey(Core::Crypto::S128KeyType::Titlekey, rights_id[1], rights_id[0]);
if (titlekey == Core::Crypto::Key128{}) {
status = Loader::ResultStatus::ErrorMissingTitlekey;
return std::nullopt;
}
if (!keys.HasKey(Core::Crypto::S128KeyType::Titlekek, master_key_id)) {
status = Loader::ResultStatus::ErrorMissingTitlekek;
return std::nullopt;
}
Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(
keys.GetKey(Core::Crypto::S128KeyType::Titlekek, master_key_id), Core::Crypto::Mode::ECB);
cipher.Transcode(titlekey.data(), titlekey.size(), titlekey.data(), Core::Crypto::Op::Decrypt);
return titlekey;
}
VirtualFile NCA::Decrypt(const NCASectionHeader& s_header, VirtualFile in, u64 starting_offset) {
if (!encrypted)
return in;
switch (s_header.raw.header.crypto_type) {
case NCASectionCryptoType::NONE:
LOG_TRACE(Crypto, "called with mode=NONE");
return in;
case NCASectionCryptoType::CTR:
// During normal BKTR decryption, this entire function is skipped. This is for the metadata,
// which uses the same CTR as usual.
case NCASectionCryptoType::BKTR:
LOG_TRACE(Crypto, "called with mode=CTR, starting_offset={:016X}", starting_offset);
{
std::optional<Core::Crypto::Key128> key;
if (has_rights_id) {
status = Loader::ResultStatus::Success;
key = GetTitlekey();
if (!key) {
if (status == Loader::ResultStatus::Success)
status = Loader::ResultStatus::ErrorMissingTitlekey;
return nullptr;
}
} else {
key = GetKeyAreaKey(NCASectionCryptoType::CTR);
if (!key) {
status = Loader::ResultStatus::ErrorMissingKeyAreaKey;
return nullptr;
}
}
auto out = std::make_shared<Core::Crypto::CTREncryptionLayer>(std::move(in), *key,
starting_offset);
Core::Crypto::CTREncryptionLayer::IVData iv{};
for (std::size_t i = 0; i < 8; ++i) {
iv[i] = s_header.raw.section_ctr[8 - i - 1];
}
out->SetIV(iv);
return std::static_pointer_cast<VfsFile>(out);
}
case NCASectionCryptoType::XTS:
// TODO(DarkLordZach): Find a test case for XTS-encrypted NCAs
default:
LOG_ERROR(Crypto, "called with unhandled crypto type={:02X}",
s_header.raw.header.crypto_type);
return nullptr;
}
}
Loader::ResultStatus NCA::GetStatus() const { Loader::ResultStatus NCA::GetStatus() const {
return status; return status;
} }
@ -143,24 +579,21 @@ VirtualDir NCA::GetParentDirectory() const {
} }
NCAContentType NCA::GetType() const { NCAContentType NCA::GetType() const {
return static_cast<NCAContentType>(reader->GetContentType()); return header.content_type;
} }
u64 NCA::GetTitleId() const { u64 NCA::GetTitleId() const {
if (is_update) { if (is_update || status == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS)
return reader->GetProgramId() | 0x800; return header.title_id | 0x800;
} return header.title_id;
return reader->GetProgramId();
} }
RightsId NCA::GetRightsId() const { std::array<u8, 16> NCA::GetRightsId() const {
RightsId result; return header.rights_id;
reader->GetRightsId(result.data(), result.size());
return result;
} }
u32 NCA::GetSDKVersion() const { u32 NCA::GetSDKVersion() const {
return reader->GetSdkAddonVersion(); return header.sdk_version;
} }
bool NCA::IsUpdate() const { bool NCA::IsUpdate() const {
@ -179,6 +612,10 @@ VirtualFile NCA::GetBaseFile() const {
return file; return file;
} }
u64 NCA::GetBaseIVFCOffset() const {
return ivfc_offset;
}
VirtualDir NCA::GetLogoPartition() const { VirtualDir NCA::GetLogoPartition() const {
return logo; return logo;
} }

View File

@ -21,7 +21,7 @@ enum class ResultStatus : u16;
namespace FileSys { namespace FileSys {
class NcaReader; union NCASectionHeader;
/// Describes the type of content within an NCA archive. /// Describes the type of content within an NCA archive.
enum class NCAContentType : u8 { enum class NCAContentType : u8 {
@ -45,7 +45,41 @@ enum class NCAContentType : u8 {
PublicData = 5, PublicData = 5,
}; };
using RightsId = std::array<u8, 0x10>; enum class NCASectionCryptoType : u8 {
NONE = 1,
XTS = 2,
CTR = 3,
BKTR = 4,
};
struct NCASectionTableEntry {
u32_le media_offset;
u32_le media_end_offset;
INSERT_PADDING_BYTES(0x8);
};
static_assert(sizeof(NCASectionTableEntry) == 0x10, "NCASectionTableEntry has incorrect size.");
struct NCAHeader {
std::array<u8, 0x100> rsa_signature_1;
std::array<u8, 0x100> rsa_signature_2;
u32_le magic;
u8 is_system;
NCAContentType content_type;
u8 crypto_type;
u8 key_index;
u64_le size;
u64_le title_id;
INSERT_PADDING_BYTES(0x4);
u32_le sdk_version;
u8 crypto_type_2;
INSERT_PADDING_BYTES(15);
std::array<u8, 0x10> rights_id;
std::array<NCASectionTableEntry, 0x4> section_tables;
std::array<std::array<u8, 0x20>, 0x4> hash_tables;
std::array<u8, 0x40> key_area;
INSERT_PADDING_BYTES(0xC0);
};
static_assert(sizeof(NCAHeader) == 0x400, "NCAHeader has incorrect size.");
inline bool IsDirectoryExeFS(const VirtualDir& pfs) { inline bool IsDirectoryExeFS(const VirtualDir& pfs) {
// According to switchbrew, an exefs must only contain these two files: // According to switchbrew, an exefs must only contain these two files:
@ -63,7 +97,8 @@ inline bool IsDirectoryLogoPartition(const VirtualDir& pfs) {
// After construction, use GetStatus to determine if the file is valid and ready to be used. // After construction, use GetStatus to determine if the file is valid and ready to be used.
class NCA : public ReadOnlyVfsDirectory { class NCA : public ReadOnlyVfsDirectory {
public: public:
explicit NCA(VirtualFile file, const NCA* base_nca = nullptr); explicit NCA(VirtualFile file, VirtualFile bktr_base_romfs = nullptr,
u64 bktr_base_ivfc_offset = 0);
~NCA() override; ~NCA() override;
Loader::ResultStatus GetStatus() const; Loader::ResultStatus GetStatus() const;
@ -75,7 +110,7 @@ public:
NCAContentType GetType() const; NCAContentType GetType() const;
u64 GetTitleId() const; u64 GetTitleId() const;
RightsId GetRightsId() const; std::array<u8, 0x10> GetRightsId() const;
u32 GetSDKVersion() const; u32 GetSDKVersion() const;
bool IsUpdate() const; bool IsUpdate() const;
@ -84,9 +119,26 @@ public:
VirtualFile GetBaseFile() const; VirtualFile GetBaseFile() const;
// Returns the base ivfc offset used in BKTR patching.
u64 GetBaseIVFCOffset() const;
VirtualDir GetLogoPartition() const; VirtualDir GetLogoPartition() const;
private: private:
bool CheckSupportedNCA(const NCAHeader& header);
bool HandlePotentialHeaderDecryption();
std::vector<NCASectionHeader> ReadSectionHeaders() const;
bool ReadSections(const std::vector<NCASectionHeader>& sections, u64 bktr_base_ivfc_offset);
bool ReadRomFSSection(const NCASectionHeader& section, const NCASectionTableEntry& entry,
u64 bktr_base_ivfc_offset);
bool ReadPFS0Section(const NCASectionHeader& section, const NCASectionTableEntry& entry);
u8 GetCryptoRevision() const;
std::optional<Core::Crypto::Key128> GetKeyAreaKey(NCASectionCryptoType type) const;
std::optional<Core::Crypto::Key128> GetTitlekey();
VirtualFile Decrypt(const NCASectionHeader& header, VirtualFile in, u64 starting_offset);
std::vector<VirtualDir> dirs; std::vector<VirtualDir> dirs;
std::vector<VirtualFile> files; std::vector<VirtualFile> files;
@ -94,6 +146,11 @@ private:
VirtualDir exefs = nullptr; VirtualDir exefs = nullptr;
VirtualDir logo = nullptr; VirtualDir logo = nullptr;
VirtualFile file; VirtualFile file;
VirtualFile bktr_base_romfs;
u64 ivfc_offset = 0;
NCAHeader header{};
bool has_rights_id{};
Loader::ResultStatus status{}; Loader::ResultStatus status{};
@ -101,7 +158,6 @@ private:
bool is_update = false; bool is_update = false;
Core::Crypto::KeyManager& keys; Core::Crypto::KeyManager& keys;
std::shared_ptr<NcaReader> reader;
}; };
} // namespace FileSys } // namespace FileSys

View File

@ -68,8 +68,7 @@ NACP::NACP(VirtualFile file) {
NACP::~NACP() = default; NACP::~NACP() = default;
const LanguageEntry& NACP::GetLanguageEntry() const { const LanguageEntry& NACP::GetLanguageEntry() const {
Language language = Language language = language_to_codes[Settings::values.language_index.GetValue()];
language_to_codes[static_cast<s32>(Settings::values.language_index.GetValue())];
{ {
const auto& language_entry = raw.language_entries.at(static_cast<u8>(language)); const auto& language_entry = raw.language_entries.at(static_cast<u8>(language));

View File

@ -17,74 +17,4 @@ constexpr Result ERROR_INVALID_ARGUMENT{ErrorModule::FS, 6001};
constexpr Result ERROR_INVALID_OFFSET{ErrorModule::FS, 6061}; constexpr Result ERROR_INVALID_OFFSET{ErrorModule::FS, 6061};
constexpr Result ERROR_INVALID_SIZE{ErrorModule::FS, 6062}; constexpr Result ERROR_INVALID_SIZE{ErrorModule::FS, 6062};
constexpr Result ResultUnsupportedSdkVersion{ErrorModule::FS, 50};
constexpr Result ResultPartitionNotFound{ErrorModule::FS, 1001};
constexpr Result ResultUnsupportedVersion{ErrorModule::FS, 3002};
constexpr Result ResultOutOfRange{ErrorModule::FS, 3005};
constexpr Result ResultAllocationMemoryFailedInFileSystemBuddyHeapA{ErrorModule::FS, 3294};
constexpr Result ResultAllocationMemoryFailedInNcaFileSystemDriverI{ErrorModule::FS, 3341};
constexpr Result ResultAllocationMemoryFailedInNcaReaderA{ErrorModule::FS, 3363};
constexpr Result ResultAllocationMemoryFailedInAesCtrCounterExtendedStorageA{ErrorModule::FS, 3399};
constexpr Result ResultAllocationMemoryFailedInIntegrityRomFsStorageA{ErrorModule::FS, 3412};
constexpr Result ResultAllocationMemoryFailedMakeUnique{ErrorModule::FS, 3422};
constexpr Result ResultAllocationMemoryFailedAllocateShared{ErrorModule::FS, 3423};
constexpr Result ResultInvalidAesCtrCounterExtendedEntryOffset{ErrorModule::FS, 4012};
constexpr Result ResultIndirectStorageCorrupted{ErrorModule::FS, 4021};
constexpr Result ResultInvalidIndirectEntryOffset{ErrorModule::FS, 4022};
constexpr Result ResultInvalidIndirectEntryStorageIndex{ErrorModule::FS, 4023};
constexpr Result ResultInvalidIndirectStorageSize{ErrorModule::FS, 4024};
constexpr Result ResultInvalidBucketTreeSignature{ErrorModule::FS, 4032};
constexpr Result ResultInvalidBucketTreeEntryCount{ErrorModule::FS, 4033};
constexpr Result ResultInvalidBucketTreeNodeEntryCount{ErrorModule::FS, 4034};
constexpr Result ResultInvalidBucketTreeNodeOffset{ErrorModule::FS, 4035};
constexpr Result ResultInvalidBucketTreeEntryOffset{ErrorModule::FS, 4036};
constexpr Result ResultInvalidBucketTreeEntrySetOffset{ErrorModule::FS, 4037};
constexpr Result ResultInvalidBucketTreeNodeIndex{ErrorModule::FS, 4038};
constexpr Result ResultInvalidBucketTreeVirtualOffset{ErrorModule::FS, 4039};
constexpr Result ResultRomNcaInvalidPatchMetaDataHashType{ErrorModule::FS, 4084};
constexpr Result ResultRomNcaInvalidIntegrityLayerInfoOffset{ErrorModule::FS, 4085};
constexpr Result ResultRomNcaInvalidPatchMetaDataHashDataSize{ErrorModule::FS, 4086};
constexpr Result ResultRomNcaInvalidPatchMetaDataHashDataOffset{ErrorModule::FS, 4087};
constexpr Result ResultRomNcaInvalidPatchMetaDataHashDataHash{ErrorModule::FS, 4088};
constexpr Result ResultRomNcaInvalidSparseMetaDataHashType{ErrorModule::FS, 4089};
constexpr Result ResultRomNcaInvalidSparseMetaDataHashDataSize{ErrorModule::FS, 4090};
constexpr Result ResultRomNcaInvalidSparseMetaDataHashDataOffset{ErrorModule::FS, 4091};
constexpr Result ResultRomNcaInvalidSparseMetaDataHashDataHash{ErrorModule::FS, 4091};
constexpr Result ResultNcaBaseStorageOutOfRangeB{ErrorModule::FS, 4509};
constexpr Result ResultNcaBaseStorageOutOfRangeC{ErrorModule::FS, 4510};
constexpr Result ResultNcaBaseStorageOutOfRangeD{ErrorModule::FS, 4511};
constexpr Result ResultInvalidNcaSignature{ErrorModule::FS, 4517};
constexpr Result ResultNcaFsHeaderHashVerificationFailed{ErrorModule::FS, 4520};
constexpr Result ResultInvalidNcaKeyIndex{ErrorModule::FS, 4521};
constexpr Result ResultInvalidNcaFsHeaderHashType{ErrorModule::FS, 4522};
constexpr Result ResultInvalidNcaFsHeaderEncryptionType{ErrorModule::FS, 4523};
constexpr Result ResultInvalidNcaPatchInfoIndirectSize{ErrorModule::FS, 4524};
constexpr Result ResultInvalidNcaPatchInfoAesCtrExSize{ErrorModule::FS, 4525};
constexpr Result ResultInvalidNcaPatchInfoAesCtrExOffset{ErrorModule::FS, 4526};
constexpr Result ResultInvalidNcaHeader{ErrorModule::FS, 4528};
constexpr Result ResultInvalidNcaFsHeader{ErrorModule::FS, 4529};
constexpr Result ResultNcaBaseStorageOutOfRangeE{ErrorModule::FS, 4530};
constexpr Result ResultInvalidHierarchicalSha256BlockSize{ErrorModule::FS, 4532};
constexpr Result ResultInvalidHierarchicalSha256LayerCount{ErrorModule::FS, 4533};
constexpr Result ResultHierarchicalSha256BaseStorageTooLarge{ErrorModule::FS, 4534};
constexpr Result ResultHierarchicalSha256HashVerificationFailed{ErrorModule::FS, 4535};
constexpr Result ResultInvalidNcaHierarchicalIntegrityVerificationLayerCount{ErrorModule::FS, 4541};
constexpr Result ResultInvalidNcaIndirectStorageOutOfRange{ErrorModule::FS, 4542};
constexpr Result ResultInvalidNcaHeader1SignatureKeyGeneration{ErrorModule::FS, 4543};
constexpr Result ResultInvalidCompressedStorageSize{ErrorModule::FS, 4547};
constexpr Result ResultInvalidNcaMetaDataHashDataSize{ErrorModule::FS, 4548};
constexpr Result ResultInvalidNcaMetaDataHashDataHash{ErrorModule::FS, 4549};
constexpr Result ResultUnexpectedInCompressedStorageA{ErrorModule::FS, 5324};
constexpr Result ResultUnexpectedInCompressedStorageB{ErrorModule::FS, 5325};
constexpr Result ResultUnexpectedInCompressedStorageC{ErrorModule::FS, 5326};
constexpr Result ResultUnexpectedInCompressedStorageD{ErrorModule::FS, 5327};
constexpr Result ResultInvalidArgument{ErrorModule::FS, 6001};
constexpr Result ResultInvalidOffset{ErrorModule::FS, 6061};
constexpr Result ResultInvalidSize{ErrorModule::FS, 6062};
constexpr Result ResultNullptrArgument{ErrorModule::FS, 6063};
constexpr Result ResultUnsupportedSetSizeForIndirectStorage{ErrorModule::FS, 6325};
constexpr Result ResultUnsupportedWriteForCompressedStorage{ErrorModule::FS, 6387};
constexpr Result ResultUnsupportedOperateRangeForCompressedStorage{ErrorModule::FS, 6388};
constexpr Result ResultBufferAllocationFailed{ErrorModule::FS, 6705};
} // namespace FileSys } // namespace FileSys

View File

@ -1,58 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/overflow.h"
#include "core/file_sys/errors.h"
#include "core/file_sys/vfs.h"
namespace FileSys {
class IStorage : public VfsFile {
public:
virtual std::string GetName() const override {
return {};
}
virtual VirtualDir GetContainingDirectory() const override {
return {};
}
virtual bool IsWritable() const override {
return true;
}
virtual bool IsReadable() const override {
return true;
}
virtual bool Resize(size_t size) override {
return false;
}
virtual bool Rename(std::string_view name) override {
return false;
}
static inline Result CheckAccessRange(s64 offset, s64 size, s64 total_size) {
R_UNLESS(offset >= 0, ResultInvalidOffset);
R_UNLESS(size >= 0, ResultInvalidSize);
R_UNLESS(Common::WrappingAdd(offset, size) >= offset, ResultOutOfRange);
R_UNLESS(offset + size <= total_size, ResultOutOfRange);
R_SUCCEED();
}
};
class IReadOnlyStorage : public IStorage {
public:
virtual bool IsWritable() const override {
return false;
}
virtual size_t Write(const u8* buffer, size_t size, size_t offset) override {
return 0;
}
};
} // namespace FileSys

View File

@ -1,46 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/common_funcs.h"
namespace FileSys {
struct Int64 {
u32 low;
u32 high;
constexpr void Set(s64 v) {
this->low = static_cast<u32>((v & static_cast<u64>(0x00000000FFFFFFFFULL)) >> 0);
this->high = static_cast<u32>((v & static_cast<u64>(0xFFFFFFFF00000000ULL)) >> 32);
}
constexpr s64 Get() const {
return (static_cast<s64>(this->high) << 32) | (static_cast<s64>(this->low));
}
constexpr Int64& operator=(s64 v) {
this->Set(v);
return *this;
}
constexpr operator s64() const {
return this->Get();
}
};
struct HashSalt {
static constexpr size_t Size = 32;
std::array<u8, Size> value;
};
static_assert(std::is_trivial_v<HashSalt>);
static_assert(sizeof(HashSalt) == HashSalt::Size);
constexpr inline size_t IntegrityMinLayerCount = 2;
constexpr inline size_t IntegrityMaxLayerCount = 7;
constexpr inline size_t IntegrityLayerCountSave = 5;
constexpr inline size_t IntegrityLayerCountSaveDataMeta = 4;
} // namespace FileSys

View File

@ -1,251 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.h"
#include "core/file_sys/fssystem/fssystem_aes_ctr_storage.h"
#include "core/file_sys/fssystem/fssystem_nca_header.h"
#include "core/file_sys/vfs_offset.h"
namespace FileSys {
namespace {
class SoftwareDecryptor final : public AesCtrCounterExtendedStorage::IDecryptor {
public:
virtual void Decrypt(
u8* buf, size_t buf_size, const std::array<u8, AesCtrCounterExtendedStorage::KeySize>& key,
const std::array<u8, AesCtrCounterExtendedStorage::IvSize>& iv) override final;
};
} // namespace
Result AesCtrCounterExtendedStorage::CreateSoftwareDecryptor(std::unique_ptr<IDecryptor>* out) {
std::unique_ptr<IDecryptor> decryptor = std::make_unique<SoftwareDecryptor>();
R_UNLESS(decryptor != nullptr, ResultAllocationMemoryFailedInAesCtrCounterExtendedStorageA);
*out = std::move(decryptor);
R_SUCCEED();
}
Result AesCtrCounterExtendedStorage::Initialize(const void* key, size_t key_size, u32 secure_value,
VirtualFile data_storage,
VirtualFile table_storage) {
// Read and verify the bucket tree header.
BucketTree::Header header;
table_storage->ReadObject(std::addressof(header), 0);
R_TRY(header.Verify());
// Determine extents.
const auto node_storage_size = QueryNodeStorageSize(header.entry_count);
const auto entry_storage_size = QueryEntryStorageSize(header.entry_count);
const auto node_storage_offset = QueryHeaderStorageSize();
const auto entry_storage_offset = node_storage_offset + node_storage_size;
// Create a software decryptor.
std::unique_ptr<IDecryptor> sw_decryptor;
R_TRY(CreateSoftwareDecryptor(std::addressof(sw_decryptor)));
// Initialize.
R_RETURN(this->Initialize(
key, key_size, secure_value, 0, data_storage,
std::make_shared<OffsetVfsFile>(table_storage, node_storage_size, node_storage_offset),
std::make_shared<OffsetVfsFile>(table_storage, entry_storage_size, entry_storage_offset),
header.entry_count, std::move(sw_decryptor)));
}
Result AesCtrCounterExtendedStorage::Initialize(const void* key, size_t key_size, u32 secure_value,
s64 counter_offset, VirtualFile data_storage,
VirtualFile node_storage, VirtualFile entry_storage,
s32 entry_count,
std::unique_ptr<IDecryptor>&& decryptor) {
// Validate preconditions.
ASSERT(key != nullptr);
ASSERT(key_size == KeySize);
ASSERT(counter_offset >= 0);
ASSERT(decryptor != nullptr);
// Initialize the bucket tree table.
if (entry_count > 0) {
R_TRY(
m_table.Initialize(node_storage, entry_storage, NodeSize, sizeof(Entry), entry_count));
} else {
m_table.Initialize(NodeSize, 0);
}
// Set members.
m_data_storage = data_storage;
std::memcpy(m_key.data(), key, key_size);
m_secure_value = secure_value;
m_counter_offset = counter_offset;
m_decryptor = std::move(decryptor);
R_SUCCEED();
}
void AesCtrCounterExtendedStorage::Finalize() {
if (this->IsInitialized()) {
m_table.Finalize();
m_data_storage = VirtualFile();
}
}
Result AesCtrCounterExtendedStorage::GetEntryList(Entry* out_entries, s32* out_entry_count,
s32 entry_count, s64 offset, s64 size) {
// Validate pre-conditions.
ASSERT(offset >= 0);
ASSERT(size >= 0);
ASSERT(this->IsInitialized());
// Clear the out count.
R_UNLESS(out_entry_count != nullptr, ResultNullptrArgument);
*out_entry_count = 0;
// Succeed if there's no range.
R_SUCCEED_IF(size == 0);
// If we have an output array, we need it to be non-null.
R_UNLESS(out_entries != nullptr || entry_count == 0, ResultNullptrArgument);
// Check that our range is valid.
BucketTree::Offsets table_offsets;
R_TRY(m_table.GetOffsets(std::addressof(table_offsets)));
R_UNLESS(table_offsets.IsInclude(offset, size), ResultOutOfRange);
// Find the offset in our tree.
BucketTree::Visitor visitor;
R_TRY(m_table.Find(std::addressof(visitor), offset));
{
const auto entry_offset = visitor.Get<Entry>()->GetOffset();
R_UNLESS(0 <= entry_offset && table_offsets.IsInclude(entry_offset),
ResultInvalidAesCtrCounterExtendedEntryOffset);
}
// Prepare to loop over entries.
const auto end_offset = offset + static_cast<s64>(size);
s32 count = 0;
auto cur_entry = *visitor.Get<Entry>();
while (cur_entry.GetOffset() < end_offset) {
// Try to write the entry to the out list.
if (entry_count != 0) {
if (count >= entry_count) {
break;
}
std::memcpy(out_entries + count, std::addressof(cur_entry), sizeof(Entry));
}
count++;
// Advance.
if (visitor.CanMoveNext()) {
R_TRY(visitor.MoveNext());
cur_entry = *visitor.Get<Entry>();
} else {
break;
}
}
// Write the output count.
*out_entry_count = count;
R_SUCCEED();
}
size_t AesCtrCounterExtendedStorage::Read(u8* buffer, size_t size, size_t offset) const {
// Validate preconditions.
ASSERT(this->IsInitialized());
// Allow zero size.
if (size == 0) {
return size;
}
// Validate arguments.
ASSERT(buffer != nullptr);
ASSERT(Common::IsAligned(offset, BlockSize));
ASSERT(Common::IsAligned(size, BlockSize));
BucketTree::Offsets table_offsets;
ASSERT(R_SUCCEEDED(m_table.GetOffsets(std::addressof(table_offsets))));
ASSERT(table_offsets.IsInclude(offset, size));
// Read the data.
m_data_storage->Read(buffer, size, offset);
// Find the offset in our tree.
BucketTree::Visitor visitor;
ASSERT(R_SUCCEEDED(m_table.Find(std::addressof(visitor), offset)));
{
const auto entry_offset = visitor.Get<Entry>()->GetOffset();
ASSERT(Common::IsAligned(entry_offset, BlockSize));
ASSERT(0 <= entry_offset && table_offsets.IsInclude(entry_offset));
}
// Prepare to read in chunks.
u8* cur_data = static_cast<u8*>(buffer);
auto cur_offset = offset;
const auto end_offset = offset + static_cast<s64>(size);
while (cur_offset < end_offset) {
// Get the current entry.
const auto cur_entry = *visitor.Get<Entry>();
// Get and validate the entry's offset.
const auto cur_entry_offset = cur_entry.GetOffset();
ASSERT(static_cast<size_t>(cur_entry_offset) <= cur_offset);
// Get and validate the next entry offset.
s64 next_entry_offset;
if (visitor.CanMoveNext()) {
ASSERT(R_SUCCEEDED(visitor.MoveNext()));
next_entry_offset = visitor.Get<Entry>()->GetOffset();
ASSERT(table_offsets.IsInclude(next_entry_offset));
} else {
next_entry_offset = table_offsets.end_offset;
}
ASSERT(Common::IsAligned(next_entry_offset, BlockSize));
ASSERT(cur_offset < static_cast<size_t>(next_entry_offset));
// Get the offset of the entry in the data we read.
const auto data_offset = cur_offset - cur_entry_offset;
const auto data_size = (next_entry_offset - cur_entry_offset) - data_offset;
ASSERT(data_size > 0);
// Determine how much is left.
const auto remaining_size = end_offset - cur_offset;
const auto cur_size = static_cast<size_t>(std::min(remaining_size, data_size));
ASSERT(cur_size <= size);
// If necessary, perform decryption.
if (cur_entry.encryption_value == Entry::Encryption::Encrypted) {
// Make the CTR for the data we're decrypting.
const auto counter_offset = m_counter_offset + cur_entry_offset + data_offset;
NcaAesCtrUpperIv upper_iv = {
.part = {.generation = static_cast<u32>(cur_entry.generation),
.secure_value = m_secure_value}};
std::array<u8, IvSize> iv;
AesCtrStorage::MakeIv(iv.data(), IvSize, upper_iv.value, counter_offset);
// Decrypt.
m_decryptor->Decrypt(cur_data, cur_size, m_key, iv);
}
// Advance.
cur_data += cur_size;
cur_offset += cur_size;
}
return size;
}
void SoftwareDecryptor::Decrypt(u8* buf, size_t buf_size,
const std::array<u8, AesCtrCounterExtendedStorage::KeySize>& key,
const std::array<u8, AesCtrCounterExtendedStorage::IvSize>& iv) {
Core::Crypto::AESCipher<Core::Crypto::Key128, AesCtrCounterExtendedStorage::KeySize> cipher(
key, Core::Crypto::Mode::CTR);
cipher.SetIV(iv);
cipher.Transcode(buf, buf_size, buf, Core::Crypto::Op::Decrypt);
}
} // namespace FileSys

View File

@ -1,114 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <optional>
#include "common/literals.h"
#include "core/file_sys/fssystem/fs_i_storage.h"
#include "core/file_sys/fssystem/fssystem_bucket_tree.h"
namespace FileSys {
using namespace Common::Literals;
class AesCtrCounterExtendedStorage : public IReadOnlyStorage {
YUZU_NON_COPYABLE(AesCtrCounterExtendedStorage);
YUZU_NON_MOVEABLE(AesCtrCounterExtendedStorage);
public:
static constexpr size_t BlockSize = 0x10;
static constexpr size_t KeySize = 0x10;
static constexpr size_t IvSize = 0x10;
static constexpr size_t NodeSize = 16_KiB;
class IDecryptor {
public:
virtual ~IDecryptor() {}
virtual void Decrypt(u8* buf, size_t buf_size, const std::array<u8, KeySize>& key,
const std::array<u8, IvSize>& iv) = 0;
};
struct Entry {
enum class Encryption : u8 {
Encrypted = 0,
NotEncrypted = 1,
};
std::array<u8, sizeof(s64)> offset;
Encryption encryption_value;
std::array<u8, 3> reserved;
s32 generation;
void SetOffset(s64 value) {
std::memcpy(this->offset.data(), std::addressof(value), sizeof(s64));
}
s64 GetOffset() const {
s64 value;
std::memcpy(std::addressof(value), this->offset.data(), sizeof(s64));
return value;
}
};
static_assert(sizeof(Entry) == 0x10);
static_assert(alignof(Entry) == 4);
static_assert(std::is_trivial_v<Entry>);
public:
static constexpr s64 QueryHeaderStorageSize() {
return BucketTree::QueryHeaderStorageSize();
}
static constexpr s64 QueryNodeStorageSize(s32 entry_count) {
return BucketTree::QueryNodeStorageSize(NodeSize, sizeof(Entry), entry_count);
}
static constexpr s64 QueryEntryStorageSize(s32 entry_count) {
return BucketTree::QueryEntryStorageSize(NodeSize, sizeof(Entry), entry_count);
}
static Result CreateSoftwareDecryptor(std::unique_ptr<IDecryptor>* out);
public:
AesCtrCounterExtendedStorage()
: m_table(), m_data_storage(), m_secure_value(), m_counter_offset(), m_decryptor() {}
virtual ~AesCtrCounterExtendedStorage() {
this->Finalize();
}
Result Initialize(const void* key, size_t key_size, u32 secure_value, s64 counter_offset,
VirtualFile data_storage, VirtualFile node_storage, VirtualFile entry_storage,
s32 entry_count, std::unique_ptr<IDecryptor>&& decryptor);
void Finalize();
bool IsInitialized() const {
return m_table.IsInitialized();
}
virtual size_t Read(u8* buffer, size_t size, size_t offset) const override;
virtual size_t GetSize() const override {
BucketTree::Offsets offsets;
ASSERT(R_SUCCEEDED(m_table.GetOffsets(std::addressof(offsets))));
return offsets.end_offset;
}
Result GetEntryList(Entry* out_entries, s32* out_entry_count, s32 entry_count, s64 offset,
s64 size);
private:
Result Initialize(const void* key, size_t key_size, u32 secure_value, VirtualFile data_storage,
VirtualFile table_storage);
private:
mutable BucketTree m_table;
VirtualFile m_data_storage;
std::array<u8, KeySize> m_key;
u32 m_secure_value;
s64 m_counter_offset;
std::unique_ptr<IDecryptor> m_decryptor;
};
} // namespace FileSys

View File

@ -1,129 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/alignment.h"
#include "common/swap.h"
#include "core/file_sys/fssystem/fssystem_aes_ctr_storage.h"
#include "core/file_sys/fssystem/fssystem_pooled_buffer.h"
#include "core/file_sys/fssystem/fssystem_utility.h"
namespace FileSys {
void AesCtrStorage::MakeIv(void* dst, size_t dst_size, u64 upper, s64 offset) {
ASSERT(dst != nullptr);
ASSERT(dst_size == IvSize);
ASSERT(offset >= 0);
const uintptr_t out_addr = reinterpret_cast<uintptr_t>(dst);
*reinterpret_cast<u64_be*>(out_addr + 0) = upper;
*reinterpret_cast<s64_be*>(out_addr + sizeof(u64)) = static_cast<s64>(offset / BlockSize);
}
AesCtrStorage::AesCtrStorage(VirtualFile base, const void* key, size_t key_size, const void* iv,
size_t iv_size)
: m_base_storage(std::move(base)) {
ASSERT(m_base_storage != nullptr);
ASSERT(key != nullptr);
ASSERT(iv != nullptr);
ASSERT(key_size == KeySize);
ASSERT(iv_size == IvSize);
std::memcpy(m_key.data(), key, KeySize);
std::memcpy(m_iv.data(), iv, IvSize);
m_cipher.emplace(m_key, Core::Crypto::Mode::CTR);
}
size_t AesCtrStorage::Read(u8* buffer, size_t size, size_t offset) const {
// Allow zero-size reads.
if (size == 0) {
return size;
}
// Ensure buffer is valid.
ASSERT(buffer != nullptr);
// We can only read at block aligned offsets.
ASSERT(Common::IsAligned(offset, BlockSize));
ASSERT(Common::IsAligned(size, BlockSize));
// Read the data.
m_base_storage->Read(buffer, size, offset);
// Setup the counter.
std::array<u8, IvSize> ctr;
std::memcpy(ctr.data(), m_iv.data(), IvSize);
AddCounter(ctr.data(), IvSize, offset / BlockSize);
// Decrypt.
m_cipher->SetIV(ctr);
m_cipher->Transcode(buffer, size, buffer, Core::Crypto::Op::Decrypt);
return size;
}
size_t AesCtrStorage::Write(const u8* buffer, size_t size, size_t offset) {
// Allow zero-size writes.
if (size == 0) {
return size;
}
// Ensure buffer is valid.
ASSERT(buffer != nullptr);
// We can only write at block aligned offsets.
ASSERT(Common::IsAligned(offset, BlockSize));
ASSERT(Common::IsAligned(size, BlockSize));
// Get a pooled buffer.
PooledBuffer pooled_buffer;
const bool use_work_buffer = true;
if (use_work_buffer) {
pooled_buffer.Allocate(size, BlockSize);
}
// Setup the counter.
std::array<u8, IvSize> ctr;
std::memcpy(ctr.data(), m_iv.data(), IvSize);
AddCounter(ctr.data(), IvSize, offset / BlockSize);
// Loop until all data is written.
size_t remaining = size;
s64 cur_offset = 0;
while (remaining > 0) {
// Determine data we're writing and where.
const size_t write_size =
use_work_buffer ? std::min(pooled_buffer.GetSize(), remaining) : remaining;
void* write_buf;
if (use_work_buffer) {
write_buf = pooled_buffer.GetBuffer();
} else {
write_buf = const_cast<u8*>(buffer);
}
// Encrypt the data.
m_cipher->SetIV(ctr);
m_cipher->Transcode(buffer, write_size, reinterpret_cast<u8*>(write_buf),
Core::Crypto::Op::Encrypt);
// Write the encrypted data.
m_base_storage->Write(reinterpret_cast<u8*>(write_buf), write_size, offset + cur_offset);
// Advance.
cur_offset += write_size;
remaining -= write_size;
if (remaining > 0) {
AddCounter(ctr.data(), IvSize, write_size / BlockSize);
}
}
return size;
}
size_t AesCtrStorage::GetSize() const {
return m_base_storage->GetSize();
}
} // namespace FileSys

View File

@ -1,43 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <optional>
#include "core/crypto/aes_util.h"
#include "core/crypto/key_manager.h"
#include "core/file_sys/errors.h"
#include "core/file_sys/fssystem/fs_i_storage.h"
#include "core/file_sys/vfs.h"
namespace FileSys {
class AesCtrStorage : public IStorage {
YUZU_NON_COPYABLE(AesCtrStorage);
YUZU_NON_MOVEABLE(AesCtrStorage);
public:
static constexpr size_t BlockSize = 0x10;
static constexpr size_t KeySize = 0x10;
static constexpr size_t IvSize = 0x10;
public:
static void MakeIv(void* dst, size_t dst_size, u64 upper, s64 offset);
public:
AesCtrStorage(VirtualFile base, const void* key, size_t key_size, const void* iv,
size_t iv_size);
virtual size_t Read(u8* buffer, size_t size, size_t offset) const override;
virtual size_t Write(const u8* buffer, size_t size, size_t offset) override;
virtual size_t GetSize() const override;
private:
VirtualFile m_base_storage;
std::array<u8, KeySize> m_key;
std::array<u8, IvSize> m_iv;
mutable std::optional<Core::Crypto::AESCipher<Core::Crypto::Key128>> m_cipher;
};
} // namespace FileSys

View File

@ -1,112 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/alignment.h"
#include "common/swap.h"
#include "core/file_sys/errors.h"
#include "core/file_sys/fssystem/fssystem_aes_xts_storage.h"
#include "core/file_sys/fssystem/fssystem_pooled_buffer.h"
#include "core/file_sys/fssystem/fssystem_utility.h"
namespace FileSys {
void AesXtsStorage::MakeAesXtsIv(void* dst, size_t dst_size, s64 offset, size_t block_size) {
ASSERT(dst != nullptr);
ASSERT(dst_size == IvSize);
ASSERT(offset >= 0);
const uintptr_t out_addr = reinterpret_cast<uintptr_t>(dst);
*reinterpret_cast<s64_be*>(out_addr + sizeof(s64)) = offset / block_size;
}
AesXtsStorage::AesXtsStorage(VirtualFile base, const void* key1, const void* key2, size_t key_size,
const void* iv, size_t iv_size, size_t block_size)
: m_base_storage(std::move(base)), m_block_size(block_size), m_mutex() {
ASSERT(m_base_storage != nullptr);
ASSERT(key1 != nullptr);
ASSERT(key2 != nullptr);
ASSERT(iv != nullptr);
ASSERT(key_size == KeySize);
ASSERT(iv_size == IvSize);
ASSERT(Common::IsAligned(m_block_size, AesBlockSize));
std::memcpy(m_key.data() + 0, key1, KeySize);
std::memcpy(m_key.data() + 0x10, key2, KeySize);
std::memcpy(m_iv.data(), iv, IvSize);
m_cipher.emplace(m_key, Core::Crypto::Mode::XTS);
}
size_t AesXtsStorage::Read(u8* buffer, size_t size, size_t offset) const {
// Allow zero-size reads.
if (size == 0) {
return size;
}
// Ensure buffer is valid.
ASSERT(buffer != nullptr);
// We can only read at block aligned offsets.
ASSERT(Common::IsAligned(offset, AesBlockSize));
ASSERT(Common::IsAligned(size, AesBlockSize));
// Read the data.
m_base_storage->Read(buffer, size, offset);
// Setup the counter.
std::array<u8, IvSize> ctr;
std::memcpy(ctr.data(), m_iv.data(), IvSize);
AddCounter(ctr.data(), IvSize, offset / m_block_size);
// Handle any unaligned data before the start.
size_t processed_size = 0;
if ((offset % m_block_size) != 0) {
// Determine the size of the pre-data read.
const size_t skip_size =
static_cast<size_t>(offset - Common::AlignDown(offset, m_block_size));
const size_t data_size = std::min(size, m_block_size - skip_size);
// Decrypt into a pooled buffer.
{
PooledBuffer tmp_buf(m_block_size, m_block_size);
ASSERT(tmp_buf.GetSize() >= m_block_size);
std::memset(tmp_buf.GetBuffer(), 0, skip_size);
std::memcpy(tmp_buf.GetBuffer() + skip_size, buffer, data_size);
m_cipher->SetIV(ctr);
m_cipher->Transcode(tmp_buf.GetBuffer(), m_block_size, tmp_buf.GetBuffer(),
Core::Crypto::Op::Decrypt);
std::memcpy(buffer, tmp_buf.GetBuffer() + skip_size, data_size);
}
AddCounter(ctr.data(), IvSize, 1);
processed_size += data_size;
ASSERT(processed_size == std::min(size, m_block_size - skip_size));
}
// Decrypt aligned chunks.
char* cur = reinterpret_cast<char*>(buffer) + processed_size;
size_t remaining = size - processed_size;
while (remaining > 0) {
const size_t cur_size = std::min(m_block_size, remaining);
m_cipher->SetIV(ctr);
m_cipher->Transcode(cur, cur_size, cur, Core::Crypto::Op::Decrypt);
remaining -= cur_size;
cur += cur_size;
AddCounter(ctr.data(), IvSize, 1);
}
return size;
}
size_t AesXtsStorage::GetSize() const {
return m_base_storage->GetSize();
}
} // namespace FileSys

View File

@ -1,42 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <optional>
#include "core/crypto/aes_util.h"
#include "core/crypto/key_manager.h"
#include "core/file_sys/fssystem/fs_i_storage.h"
namespace FileSys {
class AesXtsStorage : public IReadOnlyStorage {
YUZU_NON_COPYABLE(AesXtsStorage);
YUZU_NON_MOVEABLE(AesXtsStorage);
public:
static constexpr size_t AesBlockSize = 0x10;
static constexpr size_t KeySize = 0x20;
static constexpr size_t IvSize = 0x10;
public:
static void MakeAesXtsIv(void* dst, size_t dst_size, s64 offset, size_t block_size);
public:
AesXtsStorage(VirtualFile base, const void* key1, const void* key2, size_t key_size,
const void* iv, size_t iv_size, size_t block_size);
virtual size_t Read(u8* buffer, size_t size, size_t offset) const override;
virtual size_t GetSize() const override;
private:
VirtualFile m_base_storage;
std::array<u8, KeySize> m_key;
std::array<u8, IvSize> m_iv;
const size_t m_block_size;
std::mutex m_mutex;
mutable std::optional<Core::Crypto::AESCipher<Core::Crypto::Key256>> m_cipher;
};
} // namespace FileSys

View File

@ -1,146 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/alignment.h"
#include "core/file_sys/errors.h"
#include "core/file_sys/fssystem/fs_i_storage.h"
#include "core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.h"
#include "core/file_sys/fssystem/fssystem_pooled_buffer.h"
namespace FileSys {
template <size_t DataAlign_, size_t BufferAlign_>
class AlignmentMatchingStorage : public IStorage {
YUZU_NON_COPYABLE(AlignmentMatchingStorage);
YUZU_NON_MOVEABLE(AlignmentMatchingStorage);
public:
static constexpr size_t DataAlign = DataAlign_;
static constexpr size_t BufferAlign = BufferAlign_;
static constexpr size_t DataAlignMax = 0x200;
static_assert(DataAlign <= DataAlignMax);
static_assert(Common::IsPowerOfTwo(DataAlign));
static_assert(Common::IsPowerOfTwo(BufferAlign));
private:
VirtualFile m_base_storage;
s64 m_base_storage_size;
public:
explicit AlignmentMatchingStorage(VirtualFile bs) : m_base_storage(std::move(bs)) {}
virtual size_t Read(u8* buffer, size_t size, size_t offset) const override {
// Allocate a work buffer on stack.
alignas(DataAlignMax) std::array<char, DataAlign> work_buf;
// Succeed if zero size.
if (size == 0) {
return size;
}
// Validate arguments.
ASSERT(buffer != nullptr);
s64 bs_size = this->GetSize();
ASSERT(R_SUCCEEDED(IStorage::CheckAccessRange(offset, size, bs_size)));
return AlignmentMatchingStorageImpl::Read(m_base_storage, work_buf.data(), work_buf.size(),
DataAlign, BufferAlign, offset, buffer, size);
}
virtual size_t Write(const u8* buffer, size_t size, size_t offset) override {
// Allocate a work buffer on stack.
alignas(DataAlignMax) std::array<char, DataAlign> work_buf;
// Succeed if zero size.
if (size == 0) {
return size;
}
// Validate arguments.
ASSERT(buffer != nullptr);
s64 bs_size = this->GetSize();
ASSERT(R_SUCCEEDED(IStorage::CheckAccessRange(offset, size, bs_size)));
return AlignmentMatchingStorageImpl::Write(m_base_storage, work_buf.data(), work_buf.size(),
DataAlign, BufferAlign, offset, buffer, size);
}
virtual size_t GetSize() const override {
return m_base_storage->GetSize();
}
};
template <size_t BufferAlign_>
class AlignmentMatchingStoragePooledBuffer : public IStorage {
YUZU_NON_COPYABLE(AlignmentMatchingStoragePooledBuffer);
YUZU_NON_MOVEABLE(AlignmentMatchingStoragePooledBuffer);
public:
static constexpr size_t BufferAlign = BufferAlign_;
static_assert(Common::IsPowerOfTwo(BufferAlign));
private:
VirtualFile m_base_storage;
s64 m_base_storage_size;
size_t m_data_align;
public:
explicit AlignmentMatchingStoragePooledBuffer(VirtualFile bs, size_t da)
: m_base_storage(std::move(bs)), m_data_align(da) {
ASSERT(Common::IsPowerOfTwo(da));
}
virtual size_t Read(u8* buffer, size_t size, size_t offset) const override {
// Succeed if zero size.
if (size == 0) {
return size;
}
// Validate arguments.
ASSERT(buffer != nullptr);
s64 bs_size = this->GetSize();
ASSERT(R_SUCCEEDED(IStorage::CheckAccessRange(offset, size, bs_size)));
// Allocate a pooled buffer.
PooledBuffer pooled_buffer;
pooled_buffer.AllocateParticularlyLarge(m_data_align, m_data_align);
return AlignmentMatchingStorageImpl::Read(m_base_storage, pooled_buffer.GetBuffer(),
pooled_buffer.GetSize(), m_data_align,
BufferAlign, offset, buffer, size);
}
virtual size_t Write(const u8* buffer, size_t size, size_t offset) override {
// Succeed if zero size.
if (size == 0) {
return size;
}
// Validate arguments.
ASSERT(buffer != nullptr);
s64 bs_size = this->GetSize();
ASSERT(R_SUCCEEDED(IStorage::CheckAccessRange(offset, size, bs_size)));
// Allocate a pooled buffer.
PooledBuffer pooled_buffer;
pooled_buffer.AllocateParticularlyLarge(m_data_align, m_data_align);
return AlignmentMatchingStorageImpl::Write(m_base_storage, pooled_buffer.GetBuffer(),
pooled_buffer.GetSize(), m_data_align,
BufferAlign, offset, buffer, size);
}
virtual size_t GetSize() const override {
return m_base_storage->GetSize();
}
};
} // namespace FileSys

View File

@ -1,204 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/alignment.h"
#include "core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.h"
namespace FileSys {
namespace {
template <typename T>
constexpr size_t GetRoundDownDifference(T x, size_t align) {
return static_cast<size_t>(x - Common::AlignDown(x, align));
}
template <typename T>
constexpr size_t GetRoundUpDifference(T x, size_t align) {
return static_cast<size_t>(Common::AlignUp(x, align) - x);
}
template <typename T>
size_t GetRoundUpDifference(T* x, size_t align) {
return GetRoundUpDifference(reinterpret_cast<uintptr_t>(x), align);
}
} // namespace
size_t AlignmentMatchingStorageImpl::Read(VirtualFile base_storage, char* work_buf,
size_t work_buf_size, size_t data_alignment,
size_t buffer_alignment, s64 offset, u8* buffer,
size_t size) {
// Check preconditions.
ASSERT(work_buf_size >= data_alignment);
// Succeed if zero size.
if (size == 0) {
return size;
}
// Validate arguments.
ASSERT(buffer != nullptr);
// Determine extents.
u8* aligned_core_buffer;
s64 core_offset;
size_t core_size;
size_t buffer_gap;
size_t offset_gap;
s64 covered_offset;
const size_t offset_round_up_difference = GetRoundUpDifference(offset, data_alignment);
if (Common::IsAligned(reinterpret_cast<uintptr_t>(buffer) + offset_round_up_difference,
buffer_alignment)) {
aligned_core_buffer = buffer + offset_round_up_difference;
core_offset = Common::AlignUp(offset, data_alignment);
core_size = (size < offset_round_up_difference)
? 0
: Common::AlignDown(size - offset_round_up_difference, data_alignment);
buffer_gap = 0;
offset_gap = 0;
covered_offset = core_size > 0 ? core_offset : offset;
} else {
const size_t buffer_round_up_difference = GetRoundUpDifference(buffer, buffer_alignment);
aligned_core_buffer = buffer + buffer_round_up_difference;
core_offset = Common::AlignDown(offset, data_alignment);
core_size = (size < buffer_round_up_difference)
? 0
: Common::AlignDown(size - buffer_round_up_difference, data_alignment);
buffer_gap = buffer_round_up_difference;
offset_gap = GetRoundDownDifference(offset, data_alignment);
covered_offset = offset;
}
// Read the core portion.
if (core_size > 0) {
base_storage->Read(aligned_core_buffer, core_size, core_offset);
if (offset_gap != 0 || buffer_gap != 0) {
std::memmove(aligned_core_buffer - buffer_gap, aligned_core_buffer + offset_gap,
core_size - offset_gap);
core_size -= offset_gap;
}
}
// Handle the head portion.
if (offset < covered_offset) {
const s64 head_offset = Common::AlignDown(offset, data_alignment);
const size_t head_size = static_cast<size_t>(covered_offset - offset);
ASSERT(GetRoundDownDifference(offset, data_alignment) + head_size <= work_buf_size);
base_storage->Read(reinterpret_cast<u8*>(work_buf), data_alignment, head_offset);
std::memcpy(buffer, work_buf + GetRoundDownDifference(offset, data_alignment), head_size);
}
// Handle the tail portion.
s64 tail_offset = covered_offset + core_size;
size_t remaining_tail_size = static_cast<size_t>((offset + size) - tail_offset);
while (remaining_tail_size > 0) {
const auto aligned_tail_offset = Common::AlignDown(tail_offset, data_alignment);
const auto cur_size =
std::min(static_cast<size_t>(aligned_tail_offset + data_alignment - tail_offset),
remaining_tail_size);
base_storage->Read(reinterpret_cast<u8*>(work_buf), data_alignment, aligned_tail_offset);
ASSERT((tail_offset - offset) + cur_size <= size);
ASSERT((tail_offset - aligned_tail_offset) + cur_size <= data_alignment);
std::memcpy(reinterpret_cast<char*>(buffer) + (tail_offset - offset),
work_buf + (tail_offset - aligned_tail_offset), cur_size);
remaining_tail_size -= cur_size;
tail_offset += cur_size;
}
return size;
}
size_t AlignmentMatchingStorageImpl::Write(VirtualFile base_storage, char* work_buf,
size_t work_buf_size, size_t data_alignment,
size_t buffer_alignment, s64 offset, const u8* buffer,
size_t size) {
// Check preconditions.
ASSERT(work_buf_size >= data_alignment);
// Succeed if zero size.
if (size == 0) {
return size;
}
// Validate arguments.
ASSERT(buffer != nullptr);
// Determine extents.
const u8* aligned_core_buffer;
s64 core_offset;
size_t core_size;
s64 covered_offset;
const size_t offset_round_up_difference = GetRoundUpDifference(offset, data_alignment);
if (Common::IsAligned(reinterpret_cast<uintptr_t>(buffer) + offset_round_up_difference,
buffer_alignment)) {
aligned_core_buffer = buffer + offset_round_up_difference;
core_offset = Common::AlignUp(offset, data_alignment);
core_size = (size < offset_round_up_difference)
? 0
: Common::AlignDown(size - offset_round_up_difference, data_alignment);
covered_offset = core_size > 0 ? core_offset : offset;
} else {
aligned_core_buffer = nullptr;
core_offset = Common::AlignDown(offset, data_alignment);
core_size = 0;
covered_offset = offset;
}
// Write the core portion.
if (core_size > 0) {
base_storage->Write(aligned_core_buffer, core_size, core_offset);
}
// Handle the head portion.
if (offset < covered_offset) {
const s64 head_offset = Common::AlignDown(offset, data_alignment);
const size_t head_size = static_cast<size_t>(covered_offset - offset);
ASSERT((offset - head_offset) + head_size <= data_alignment);
base_storage->Read(reinterpret_cast<u8*>(work_buf), data_alignment, head_offset);
std::memcpy(work_buf + (offset - head_offset), buffer, head_size);
base_storage->Write(reinterpret_cast<u8*>(work_buf), data_alignment, head_offset);
}
// Handle the tail portion.
s64 tail_offset = covered_offset + core_size;
size_t remaining_tail_size = static_cast<size_t>((offset + size) - tail_offset);
while (remaining_tail_size > 0) {
ASSERT(static_cast<size_t>(tail_offset - offset) < size);
const auto aligned_tail_offset = Common::AlignDown(tail_offset, data_alignment);
const auto cur_size =
std::min(static_cast<size_t>(aligned_tail_offset + data_alignment - tail_offset),
remaining_tail_size);
base_storage->Read(reinterpret_cast<u8*>(work_buf), data_alignment, aligned_tail_offset);
std::memcpy(work_buf + GetRoundDownDifference(tail_offset, data_alignment),
buffer + (tail_offset - offset), cur_size);
base_storage->Write(reinterpret_cast<u8*>(work_buf), data_alignment, aligned_tail_offset);
remaining_tail_size -= cur_size;
tail_offset += cur_size;
}
return size;
}
} // namespace FileSys

View File

@ -1,21 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "core/file_sys/errors.h"
#include "core/file_sys/fssystem/fs_i_storage.h"
namespace FileSys {
class AlignmentMatchingStorageImpl {
public:
static size_t Read(VirtualFile base_storage, char* work_buf, size_t work_buf_size,
size_t data_alignment, size_t buffer_alignment, s64 offset, u8* buffer,
size_t size);
static size_t Write(VirtualFile base_storage, char* work_buf, size_t work_buf_size,
size_t data_alignment, size_t buffer_alignment, s64 offset,
const u8* buffer, size_t size);
};
} // namespace FileSys

View File

@ -1,598 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/file_sys/errors.h"
#include "core/file_sys/fssystem/fssystem_bucket_tree.h"
#include "core/file_sys/fssystem/fssystem_bucket_tree_utils.h"
#include "core/file_sys/fssystem/fssystem_pooled_buffer.h"
namespace FileSys {
namespace {
using Node = impl::BucketTreeNode<const s64*>;
static_assert(sizeof(Node) == sizeof(BucketTree::NodeHeader));
static_assert(std::is_trivial_v<Node>);
constexpr inline s32 NodeHeaderSize = sizeof(BucketTree::NodeHeader);
class StorageNode {
private:
class Offset {
public:
using difference_type = s64;
private:
s64 m_offset;
s32 m_stride;
public:
constexpr Offset(s64 offset, s32 stride) : m_offset(offset), m_stride(stride) {}
constexpr Offset& operator++() {
m_offset += m_stride;
return *this;
}
constexpr Offset operator++(int) {
Offset ret(*this);
m_offset += m_stride;
return ret;
}
constexpr Offset& operator--() {
m_offset -= m_stride;
return *this;
}
constexpr Offset operator--(int) {
Offset ret(*this);
m_offset -= m_stride;
return ret;
}
constexpr difference_type operator-(const Offset& rhs) const {
return (m_offset - rhs.m_offset) / m_stride;
}
constexpr Offset operator+(difference_type ofs) const {
return Offset(m_offset + ofs * m_stride, m_stride);
}
constexpr Offset operator-(difference_type ofs) const {
return Offset(m_offset - ofs * m_stride, m_stride);
}
constexpr Offset& operator+=(difference_type ofs) {
m_offset += ofs * m_stride;
return *this;
}
constexpr Offset& operator-=(difference_type ofs) {
m_offset -= ofs * m_stride;
return *this;
}
constexpr bool operator==(const Offset& rhs) const {
return m_offset == rhs.m_offset;
}
constexpr bool operator!=(const Offset& rhs) const {
return m_offset != rhs.m_offset;
}
constexpr s64 Get() const {
return m_offset;
}
};
private:
const Offset m_start;
const s32 m_count;
s32 m_index;
public:
StorageNode(size_t size, s32 count)
: m_start(NodeHeaderSize, static_cast<s32>(size)), m_count(count), m_index(-1) {}
StorageNode(s64 ofs, size_t size, s32 count)
: m_start(NodeHeaderSize + ofs, static_cast<s32>(size)), m_count(count), m_index(-1) {}
s32 GetIndex() const {
return m_index;
}
void Find(const char* buffer, s64 virtual_address) {
s32 end = m_count;
auto pos = m_start;
while (end > 0) {
auto half = end / 2;
auto mid = pos + half;
s64 offset = 0;
std::memcpy(std::addressof(offset), buffer + mid.Get(), sizeof(s64));
if (offset <= virtual_address) {
pos = mid + 1;
end -= half + 1;
} else {
end = half;
}
}
m_index = static_cast<s32>(pos - m_start) - 1;
}
Result Find(VirtualFile storage, s64 virtual_address) {
s32 end = m_count;
auto pos = m_start;
while (end > 0) {
auto half = end / 2;
auto mid = pos + half;
s64 offset = 0;
storage->ReadObject(std::addressof(offset), mid.Get());
if (offset <= virtual_address) {
pos = mid + 1;
end -= half + 1;
} else {
end = half;
}
}
m_index = static_cast<s32>(pos - m_start) - 1;
R_SUCCEED();
}
};
} // namespace
void BucketTree::Header::Format(s32 entry_count_) {
ASSERT(entry_count_ >= 0);
this->magic = Magic;
this->version = Version;
this->entry_count = entry_count_;
this->reserved = 0;
}
Result BucketTree::Header::Verify() const {
R_UNLESS(this->magic == Magic, ResultInvalidBucketTreeSignature);
R_UNLESS(this->entry_count >= 0, ResultInvalidBucketTreeEntryCount);
R_UNLESS(this->version <= Version, ResultUnsupportedVersion);
R_SUCCEED();
}
Result BucketTree::NodeHeader::Verify(s32 node_index, size_t node_size, size_t entry_size) const {
R_UNLESS(this->index == node_index, ResultInvalidBucketTreeNodeIndex);
R_UNLESS(entry_size != 0 && node_size >= entry_size + NodeHeaderSize, ResultInvalidSize);
const size_t max_entry_count = (node_size - NodeHeaderSize) / entry_size;
R_UNLESS(this->count > 0 && static_cast<size_t>(this->count) <= max_entry_count,
ResultInvalidBucketTreeNodeEntryCount);
R_UNLESS(this->offset >= 0, ResultInvalidBucketTreeNodeOffset);
R_SUCCEED();
}
Result BucketTree::Initialize(VirtualFile node_storage, VirtualFile entry_storage, size_t node_size,
size_t entry_size, s32 entry_count) {
// Validate preconditions.
ASSERT(entry_size >= sizeof(s64));
ASSERT(node_size >= entry_size + sizeof(NodeHeader));
ASSERT(NodeSizeMin <= node_size && node_size <= NodeSizeMax);
ASSERT(Common::IsPowerOfTwo(node_size));
ASSERT(!this->IsInitialized());
// Ensure valid entry count.
R_UNLESS(entry_count > 0, ResultInvalidArgument);
// Allocate node.
R_UNLESS(m_node_l1.Allocate(node_size), ResultBufferAllocationFailed);
ON_RESULT_FAILURE {
m_node_l1.Free(node_size);
};
// Read node.
node_storage->Read(reinterpret_cast<u8*>(m_node_l1.Get()), node_size);
// Verify node.
R_TRY(m_node_l1->Verify(0, node_size, sizeof(s64)));
// Validate offsets.
const auto offset_count = GetOffsetCount(node_size);
const auto entry_set_count = GetEntrySetCount(node_size, entry_size, entry_count);
const auto* const node = m_node_l1.Get<Node>();
s64 start_offset;
if (offset_count < entry_set_count && node->GetCount() < offset_count) {
start_offset = *node->GetEnd();
} else {
start_offset = *node->GetBegin();
}
const auto end_offset = node->GetEndOffset();
R_UNLESS(0 <= start_offset && start_offset <= node->GetBeginOffset(),
ResultInvalidBucketTreeEntryOffset);
R_UNLESS(start_offset < end_offset, ResultInvalidBucketTreeEntryOffset);
// Set member variables.
m_node_storage = node_storage;
m_entry_storage = entry_storage;
m_node_size = node_size;
m_entry_size = entry_size;
m_entry_count = entry_count;
m_offset_count = offset_count;
m_entry_set_count = entry_set_count;
m_offset_cache.offsets.start_offset = start_offset;
m_offset_cache.offsets.end_offset = end_offset;
m_offset_cache.is_initialized = true;
// We succeeded.
R_SUCCEED();
}
void BucketTree::Initialize(size_t node_size, s64 end_offset) {
ASSERT(NodeSizeMin <= node_size && node_size <= NodeSizeMax);
ASSERT(Common::IsPowerOfTwo(node_size));
ASSERT(end_offset > 0);
ASSERT(!this->IsInitialized());
m_node_size = node_size;
m_offset_cache.offsets.start_offset = 0;
m_offset_cache.offsets.end_offset = end_offset;
m_offset_cache.is_initialized = true;
}
void BucketTree::Finalize() {
if (this->IsInitialized()) {
m_node_storage = VirtualFile();
m_entry_storage = VirtualFile();
m_node_l1.Free(m_node_size);
m_node_size = 0;
m_entry_size = 0;
m_entry_count = 0;
m_offset_count = 0;
m_entry_set_count = 0;
m_offset_cache.offsets.start_offset = 0;
m_offset_cache.offsets.end_offset = 0;
m_offset_cache.is_initialized = false;
}
}
Result BucketTree::Find(Visitor* visitor, s64 virtual_address) {
ASSERT(visitor != nullptr);
ASSERT(this->IsInitialized());
R_UNLESS(virtual_address >= 0, ResultInvalidOffset);
R_UNLESS(!this->IsEmpty(), ResultOutOfRange);
BucketTree::Offsets offsets;
R_TRY(this->GetOffsets(std::addressof(offsets)));
R_TRY(visitor->Initialize(this, offsets));
R_RETURN(visitor->Find(virtual_address));
}
Result BucketTree::InvalidateCache() {
// Reset our offsets.
m_offset_cache.is_initialized = false;
R_SUCCEED();
}
Result BucketTree::EnsureOffsetCache() {
// If we already have an offset cache, we're good.
R_SUCCEED_IF(m_offset_cache.is_initialized);
// Acquire exclusive right to edit the offset cache.
std::scoped_lock lk(m_offset_cache.mutex);
// Check again, to be sure.
R_SUCCEED_IF(m_offset_cache.is_initialized);
// Read/verify L1.
m_node_storage->Read(reinterpret_cast<u8*>(m_node_l1.Get()), m_node_size);
R_TRY(m_node_l1->Verify(0, m_node_size, sizeof(s64)));
// Get the node.
auto* const node = m_node_l1.Get<Node>();
s64 start_offset;
if (m_offset_count < m_entry_set_count && node->GetCount() < m_offset_count) {
start_offset = *node->GetEnd();
} else {
start_offset = *node->GetBegin();
}
const auto end_offset = node->GetEndOffset();
R_UNLESS(0 <= start_offset && start_offset <= node->GetBeginOffset(),
ResultInvalidBucketTreeEntryOffset);
R_UNLESS(start_offset < end_offset, ResultInvalidBucketTreeEntryOffset);
m_offset_cache.offsets.start_offset = start_offset;
m_offset_cache.offsets.end_offset = end_offset;
m_offset_cache.is_initialized = true;
R_SUCCEED();
}
Result BucketTree::Visitor::Initialize(const BucketTree* tree, const BucketTree::Offsets& offsets) {
ASSERT(tree != nullptr);
ASSERT(m_tree == nullptr || m_tree == tree);
if (m_entry == nullptr) {
m_entry = ::operator new(tree->m_entry_size);
R_UNLESS(m_entry != nullptr, ResultBufferAllocationFailed);
m_tree = tree;
m_offsets = offsets;
}
R_SUCCEED();
}
Result BucketTree::Visitor::MoveNext() {
R_UNLESS(this->IsValid(), ResultOutOfRange);
// Invalidate our index, and read the header for the next index.
auto entry_index = m_entry_index + 1;
if (entry_index == m_entry_set.info.count) {
const auto entry_set_index = m_entry_set.info.index + 1;
R_UNLESS(entry_set_index < m_entry_set_count, ResultOutOfRange);
m_entry_index = -1;
const auto end = m_entry_set.info.end;
const auto entry_set_size = m_tree->m_node_size;
const auto entry_set_offset = entry_set_index * static_cast<s64>(entry_set_size);
m_tree->m_entry_storage->ReadObject(std::addressof(m_entry_set), entry_set_offset);
R_TRY(m_entry_set.header.Verify(entry_set_index, entry_set_size, m_tree->m_entry_size));
R_UNLESS(m_entry_set.info.start == end && m_entry_set.info.start < m_entry_set.info.end,
ResultInvalidBucketTreeEntrySetOffset);
entry_index = 0;
} else {
m_entry_index = -1;
}
// Read the new entry.
const auto entry_size = m_tree->m_entry_size;
const auto entry_offset = impl::GetBucketTreeEntryOffset(
m_entry_set.info.index, m_tree->m_node_size, entry_size, entry_index);
m_tree->m_entry_storage->Read(reinterpret_cast<u8*>(m_entry), entry_size, entry_offset);
// Note that we changed index.
m_entry_index = entry_index;
R_SUCCEED();
}
Result BucketTree::Visitor::MovePrevious() {
R_UNLESS(this->IsValid(), ResultOutOfRange);
// Invalidate our index, and read the header for the previous index.
auto entry_index = m_entry_index;
if (entry_index == 0) {
R_UNLESS(m_entry_set.info.index > 0, ResultOutOfRange);
m_entry_index = -1;
const auto start = m_entry_set.info.start;
const auto entry_set_size = m_tree->m_node_size;
const auto entry_set_index = m_entry_set.info.index - 1;
const auto entry_set_offset = entry_set_index * static_cast<s64>(entry_set_size);
m_tree->m_entry_storage->ReadObject(std::addressof(m_entry_set), entry_set_offset);
R_TRY(m_entry_set.header.Verify(entry_set_index, entry_set_size, m_tree->m_entry_size));
R_UNLESS(m_entry_set.info.end == start && m_entry_set.info.start < m_entry_set.info.end,
ResultInvalidBucketTreeEntrySetOffset);
entry_index = m_entry_set.info.count;
} else {
m_entry_index = -1;
}
--entry_index;
// Read the new entry.
const auto entry_size = m_tree->m_entry_size;
const auto entry_offset = impl::GetBucketTreeEntryOffset(
m_entry_set.info.index, m_tree->m_node_size, entry_size, entry_index);
m_tree->m_entry_storage->Read(reinterpret_cast<u8*>(m_entry), entry_size, entry_offset);
// Note that we changed index.
m_entry_index = entry_index;
R_SUCCEED();
}
Result BucketTree::Visitor::Find(s64 virtual_address) {
ASSERT(m_tree != nullptr);
// Get the node.
const auto* const node = m_tree->m_node_l1.Get<Node>();
R_UNLESS(virtual_address < node->GetEndOffset(), ResultOutOfRange);
// Get the entry set index.
s32 entry_set_index = -1;
if (m_tree->IsExistOffsetL2OnL1() && virtual_address < node->GetBeginOffset()) {
const auto start = node->GetEnd();
const auto end = node->GetBegin() + m_tree->m_offset_count;
auto pos = std::upper_bound(start, end, virtual_address);
R_UNLESS(start < pos, ResultOutOfRange);
--pos;
entry_set_index = static_cast<s32>(pos - start);
} else {
const auto start = node->GetBegin();
const auto end = node->GetEnd();
auto pos = std::upper_bound(start, end, virtual_address);
R_UNLESS(start < pos, ResultOutOfRange);
--pos;
if (m_tree->IsExistL2()) {
const auto node_index = static_cast<s32>(pos - start);
R_UNLESS(0 <= node_index && node_index < m_tree->m_offset_count,
ResultInvalidBucketTreeNodeOffset);
R_TRY(this->FindEntrySet(std::addressof(entry_set_index), virtual_address, node_index));
} else {
entry_set_index = static_cast<s32>(pos - start);
}
}
// Validate the entry set index.
R_UNLESS(0 <= entry_set_index && entry_set_index < m_tree->m_entry_set_count,
ResultInvalidBucketTreeNodeOffset);
// Find the entry.
R_TRY(this->FindEntry(virtual_address, entry_set_index));
// Set count.
m_entry_set_count = m_tree->m_entry_set_count;
R_SUCCEED();
}
Result BucketTree::Visitor::FindEntrySet(s32* out_index, s64 virtual_address, s32 node_index) {
const auto node_size = m_tree->m_node_size;
PooledBuffer pool(node_size, 1);
if (node_size <= pool.GetSize()) {
R_RETURN(
this->FindEntrySetWithBuffer(out_index, virtual_address, node_index, pool.GetBuffer()));
} else {
pool.Deallocate();
R_RETURN(this->FindEntrySetWithoutBuffer(out_index, virtual_address, node_index));
}
}
Result BucketTree::Visitor::FindEntrySetWithBuffer(s32* out_index, s64 virtual_address,
s32 node_index, char* buffer) {
// Calculate node extents.
const auto node_size = m_tree->m_node_size;
const auto node_offset = (node_index + 1) * static_cast<s64>(node_size);
VirtualFile storage = m_tree->m_node_storage;
// Read the node.
storage->Read(reinterpret_cast<u8*>(buffer), node_size, node_offset);
// Validate the header.
NodeHeader header;
std::memcpy(std::addressof(header), buffer, NodeHeaderSize);
R_TRY(header.Verify(node_index, node_size, sizeof(s64)));
// Create the node, and find.
StorageNode node(sizeof(s64), header.count);
node.Find(buffer, virtual_address);
R_UNLESS(node.GetIndex() >= 0, ResultInvalidBucketTreeVirtualOffset);
// Return the index.
*out_index = static_cast<s32>(m_tree->GetEntrySetIndex(header.index, node.GetIndex()));
R_SUCCEED();
}
Result BucketTree::Visitor::FindEntrySetWithoutBuffer(s32* out_index, s64 virtual_address,
s32 node_index) {
// Calculate node extents.
const auto node_size = m_tree->m_node_size;
const auto node_offset = (node_index + 1) * static_cast<s64>(node_size);
VirtualFile storage = m_tree->m_node_storage;
// Read and validate the header.
NodeHeader header;
storage->ReadObject(std::addressof(header), node_offset);
R_TRY(header.Verify(node_index, node_size, sizeof(s64)));
// Create the node, and find.
StorageNode node(node_offset, sizeof(s64), header.count);
R_TRY(node.Find(storage, virtual_address));
R_UNLESS(node.GetIndex() >= 0, ResultOutOfRange);
// Return the index.
*out_index = static_cast<s32>(m_tree->GetEntrySetIndex(header.index, node.GetIndex()));
R_SUCCEED();
}
Result BucketTree::Visitor::FindEntry(s64 virtual_address, s32 entry_set_index) {
const auto entry_set_size = m_tree->m_node_size;
PooledBuffer pool(entry_set_size, 1);
if (entry_set_size <= pool.GetSize()) {
R_RETURN(this->FindEntryWithBuffer(virtual_address, entry_set_index, pool.GetBuffer()));
} else {
pool.Deallocate();
R_RETURN(this->FindEntryWithoutBuffer(virtual_address, entry_set_index));
}
}
Result BucketTree::Visitor::FindEntryWithBuffer(s64 virtual_address, s32 entry_set_index,
char* buffer) {
// Calculate entry set extents.
const auto entry_size = m_tree->m_entry_size;
const auto entry_set_size = m_tree->m_node_size;
const auto entry_set_offset = entry_set_index * static_cast<s64>(entry_set_size);
VirtualFile storage = m_tree->m_entry_storage;
// Read the entry set.
storage->Read(reinterpret_cast<u8*>(buffer), entry_set_size, entry_set_offset);
// Validate the entry_set.
EntrySetHeader entry_set;
std::memcpy(std::addressof(entry_set), buffer, sizeof(EntrySetHeader));
R_TRY(entry_set.header.Verify(entry_set_index, entry_set_size, entry_size));
// Create the node, and find.
StorageNode node(entry_size, entry_set.info.count);
node.Find(buffer, virtual_address);
R_UNLESS(node.GetIndex() >= 0, ResultOutOfRange);
// Copy the data into entry.
const auto entry_index = node.GetIndex();
const auto entry_offset = impl::GetBucketTreeEntryOffset(0, entry_size, entry_index);
std::memcpy(m_entry, buffer + entry_offset, entry_size);
// Set our entry set/index.
m_entry_set = entry_set;
m_entry_index = entry_index;
R_SUCCEED();
}
Result BucketTree::Visitor::FindEntryWithoutBuffer(s64 virtual_address, s32 entry_set_index) {
// Calculate entry set extents.
const auto entry_size = m_tree->m_entry_size;
const auto entry_set_size = m_tree->m_node_size;
const auto entry_set_offset = entry_set_index * static_cast<s64>(entry_set_size);
VirtualFile storage = m_tree->m_entry_storage;
// Read and validate the entry_set.
EntrySetHeader entry_set;
storage->ReadObject(std::addressof(entry_set), entry_set_offset);
R_TRY(entry_set.header.Verify(entry_set_index, entry_set_size, entry_size));
// Create the node, and find.
StorageNode node(entry_set_offset, entry_size, entry_set.info.count);
R_TRY(node.Find(storage, virtual_address));
R_UNLESS(node.GetIndex() >= 0, ResultOutOfRange);
// Copy the data into entry.
const auto entry_index = node.GetIndex();
const auto entry_offset =
impl::GetBucketTreeEntryOffset(entry_set_offset, entry_size, entry_index);
storage->Read(reinterpret_cast<u8*>(m_entry), entry_size, entry_offset);
// Set our entry set/index.
m_entry_set = entry_set;
m_entry_index = entry_index;
R_SUCCEED();
}
} // namespace FileSys

View File

@ -1,416 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <mutex>
#include "common/alignment.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/literals.h"
#include "core/file_sys/vfs.h"
#include "core/hle/result.h"
namespace FileSys {
using namespace Common::Literals;
class BucketTree {
YUZU_NON_COPYABLE(BucketTree);
YUZU_NON_MOVEABLE(BucketTree);
public:
static constexpr u32 Magic = Common::MakeMagic('B', 'K', 'T', 'R');
static constexpr u32 Version = 1;
static constexpr size_t NodeSizeMin = 1_KiB;
static constexpr size_t NodeSizeMax = 512_KiB;
public:
class Visitor;
struct Header {
u32 magic;
u32 version;
s32 entry_count;
s32 reserved;
void Format(s32 entry_count);
Result Verify() const;
};
static_assert(std::is_trivial_v<Header>);
static_assert(sizeof(Header) == 0x10);
struct NodeHeader {
s32 index;
s32 count;
s64 offset;
Result Verify(s32 node_index, size_t node_size, size_t entry_size) const;
};
static_assert(std::is_trivial_v<NodeHeader>);
static_assert(sizeof(NodeHeader) == 0x10);
struct Offsets {
s64 start_offset;
s64 end_offset;
constexpr bool IsInclude(s64 offset) const {
return this->start_offset <= offset && offset < this->end_offset;
}
constexpr bool IsInclude(s64 offset, s64 size) const {
return size > 0 && this->start_offset <= offset && size <= (this->end_offset - offset);
}
};
static_assert(std::is_trivial_v<Offsets>);
static_assert(sizeof(Offsets) == 0x10);
struct OffsetCache {
Offsets offsets;
std::mutex mutex;
bool is_initialized;
OffsetCache() : offsets{-1, -1}, mutex(), is_initialized(false) {}
};
class ContinuousReadingInfo {
public:
constexpr ContinuousReadingInfo() : m_read_size(), m_skip_count(), m_done() {}
constexpr void Reset() {
m_read_size = 0;
m_skip_count = 0;
m_done = false;
}
constexpr void SetSkipCount(s32 count) {
ASSERT(count >= 0);
m_skip_count = count;
}
constexpr s32 GetSkipCount() const {
return m_skip_count;
}
constexpr bool CheckNeedScan() {
return (--m_skip_count) <= 0;
}
constexpr void Done() {
m_read_size = 0;
m_done = true;
}
constexpr bool IsDone() const {
return m_done;
}
constexpr void SetReadSize(size_t size) {
m_read_size = size;
}
constexpr size_t GetReadSize() const {
return m_read_size;
}
constexpr bool CanDo() const {
return m_read_size > 0;
}
private:
size_t m_read_size;
s32 m_skip_count;
bool m_done;
};
private:
class NodeBuffer {
YUZU_NON_COPYABLE(NodeBuffer);
public:
NodeBuffer() : m_header() {}
~NodeBuffer() {
ASSERT(m_header == nullptr);
}
NodeBuffer(NodeBuffer&& rhs) : m_header(rhs.m_header) {
rhs.m_header = nullptr;
}
NodeBuffer& operator=(NodeBuffer&& rhs) {
if (this != std::addressof(rhs)) {
ASSERT(m_header == nullptr);
m_header = rhs.m_header;
rhs.m_header = nullptr;
}
return *this;
}
bool Allocate(size_t node_size) {
ASSERT(m_header == nullptr);
m_header = ::operator new(node_size, std::align_val_t{sizeof(s64)});
// ASSERT(Common::IsAligned(m_header, sizeof(s64)));
return m_header != nullptr;
}
void Free(size_t node_size) {
if (m_header) {
::operator delete(m_header, std::align_val_t{sizeof(s64)});
m_header = nullptr;
}
}
void FillZero(size_t node_size) const {
if (m_header) {
std::memset(m_header, 0, node_size);
}
}
NodeHeader* Get() const {
return reinterpret_cast<NodeHeader*>(m_header);
}
NodeHeader* operator->() const {
return this->Get();
}
template <typename T>
T* Get() const {
static_assert(std::is_trivial_v<T>);
static_assert(sizeof(T) == sizeof(NodeHeader));
return reinterpret_cast<T*>(m_header);
}
private:
void* m_header;
};
private:
static constexpr s32 GetEntryCount(size_t node_size, size_t entry_size) {
return static_cast<s32>((node_size - sizeof(NodeHeader)) / entry_size);
}
static constexpr s32 GetOffsetCount(size_t node_size) {
return static_cast<s32>((node_size - sizeof(NodeHeader)) / sizeof(s64));
}
static constexpr s32 GetEntrySetCount(size_t node_size, size_t entry_size, s32 entry_count) {
const s32 entry_count_per_node = GetEntryCount(node_size, entry_size);
return Common::DivideUp(entry_count, entry_count_per_node);
}
static constexpr s32 GetNodeL2Count(size_t node_size, size_t entry_size, s32 entry_count) {
const s32 offset_count_per_node = GetOffsetCount(node_size);
const s32 entry_set_count = GetEntrySetCount(node_size, entry_size, entry_count);
if (entry_set_count <= offset_count_per_node) {
return 0;
}
const s32 node_l2_count = Common::DivideUp(entry_set_count, offset_count_per_node);
ASSERT(node_l2_count <= offset_count_per_node);
return Common::DivideUp(entry_set_count - (offset_count_per_node - (node_l2_count - 1)),
offset_count_per_node);
}
public:
BucketTree()
: m_node_storage(), m_entry_storage(), m_node_l1(), m_node_size(), m_entry_size(),
m_entry_count(), m_offset_count(), m_entry_set_count(), m_offset_cache() {}
~BucketTree() {
this->Finalize();
}
Result Initialize(VirtualFile node_storage, VirtualFile entry_storage, size_t node_size,
size_t entry_size, s32 entry_count);
void Initialize(size_t node_size, s64 end_offset);
void Finalize();
bool IsInitialized() const {
return m_node_size > 0;
}
bool IsEmpty() const {
return m_entry_size == 0;
}
Result Find(Visitor* visitor, s64 virtual_address);
Result InvalidateCache();
s32 GetEntryCount() const {
return m_entry_count;
}
Result GetOffsets(Offsets* out) {
// Ensure we have an offset cache.
R_TRY(this->EnsureOffsetCache());
// Set the output.
*out = m_offset_cache.offsets;
R_SUCCEED();
}
public:
static constexpr s64 QueryHeaderStorageSize() {
return sizeof(Header);
}
static constexpr s64 QueryNodeStorageSize(size_t node_size, size_t entry_size,
s32 entry_count) {
ASSERT(entry_size >= sizeof(s64));
ASSERT(node_size >= entry_size + sizeof(NodeHeader));
ASSERT(NodeSizeMin <= node_size && node_size <= NodeSizeMax);
ASSERT(Common::IsPowerOfTwo(node_size));
ASSERT(entry_count >= 0);
if (entry_count <= 0) {
return 0;
}
return (1 + GetNodeL2Count(node_size, entry_size, entry_count)) *
static_cast<s64>(node_size);
}
static constexpr s64 QueryEntryStorageSize(size_t node_size, size_t entry_size,
s32 entry_count) {
ASSERT(entry_size >= sizeof(s64));
ASSERT(node_size >= entry_size + sizeof(NodeHeader));
ASSERT(NodeSizeMin <= node_size && node_size <= NodeSizeMax);
ASSERT(Common::IsPowerOfTwo(node_size));
ASSERT(entry_count >= 0);
if (entry_count <= 0) {
return 0;
}
return GetEntrySetCount(node_size, entry_size, entry_count) * static_cast<s64>(node_size);
}
private:
template <typename EntryType>
struct ContinuousReadingParam {
s64 offset;
size_t size;
NodeHeader entry_set;
s32 entry_index;
Offsets offsets;
EntryType entry;
};
private:
template <typename EntryType>
Result ScanContinuousReading(ContinuousReadingInfo* out_info,
const ContinuousReadingParam<EntryType>& param) const;
bool IsExistL2() const {
return m_offset_count < m_entry_set_count;
}
bool IsExistOffsetL2OnL1() const {
return this->IsExistL2() && m_node_l1->count < m_offset_count;
}
s64 GetEntrySetIndex(s32 node_index, s32 offset_index) const {
return (m_offset_count - m_node_l1->count) + (m_offset_count * node_index) + offset_index;
}
Result EnsureOffsetCache();
private:
mutable VirtualFile m_node_storage;
mutable VirtualFile m_entry_storage;
NodeBuffer m_node_l1;
size_t m_node_size;
size_t m_entry_size;
s32 m_entry_count;
s32 m_offset_count;
s32 m_entry_set_count;
OffsetCache m_offset_cache;
};
class BucketTree::Visitor {
YUZU_NON_COPYABLE(Visitor);
YUZU_NON_MOVEABLE(Visitor);
public:
constexpr Visitor()
: m_tree(), m_entry(), m_entry_index(-1), m_entry_set_count(), m_entry_set{} {}
~Visitor() {
if (m_entry != nullptr) {
::operator delete(m_entry, m_tree->m_entry_size);
m_tree = nullptr;
m_entry = nullptr;
}
}
bool IsValid() const {
return m_entry_index >= 0;
}
bool CanMoveNext() const {
return this->IsValid() && (m_entry_index + 1 < m_entry_set.info.count ||
m_entry_set.info.index + 1 < m_entry_set_count);
}
bool CanMovePrevious() const {
return this->IsValid() && (m_entry_index > 0 || m_entry_set.info.index > 0);
}
Result MoveNext();
Result MovePrevious();
template <typename EntryType>
Result ScanContinuousReading(ContinuousReadingInfo* out_info, s64 offset, size_t size) const;
const void* Get() const {
ASSERT(this->IsValid());
return m_entry;
}
template <typename T>
const T* Get() const {
ASSERT(this->IsValid());
return reinterpret_cast<const T*>(m_entry);
}
const BucketTree* GetTree() const {
return m_tree;
}
private:
Result Initialize(const BucketTree* tree, const BucketTree::Offsets& offsets);
Result Find(s64 virtual_address);
Result FindEntrySet(s32* out_index, s64 virtual_address, s32 node_index);
Result FindEntrySetWithBuffer(s32* out_index, s64 virtual_address, s32 node_index,
char* buffer);
Result FindEntrySetWithoutBuffer(s32* out_index, s64 virtual_address, s32 node_index);
Result FindEntry(s64 virtual_address, s32 entry_set_index);
Result FindEntryWithBuffer(s64 virtual_address, s32 entry_set_index, char* buffer);
Result FindEntryWithoutBuffer(s64 virtual_address, s32 entry_set_index);
private:
friend class BucketTree;
union EntrySetHeader {
NodeHeader header;
struct Info {
s32 index;
s32 count;
s64 end;
s64 start;
} info;
static_assert(std::is_trivial_v<Info>);
};
static_assert(std::is_trivial_v<EntrySetHeader>);
const BucketTree* m_tree;
BucketTree::Offsets m_offsets;
void* m_entry;
s32 m_entry_index;
s32 m_entry_set_count;
EntrySetHeader m_entry_set;
};
} // namespace FileSys

View File

@ -1,170 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "core/file_sys/errors.h"
#include "core/file_sys/fssystem/fssystem_bucket_tree.h"
#include "core/file_sys/fssystem/fssystem_bucket_tree_utils.h"
#include "core/file_sys/fssystem/fssystem_pooled_buffer.h"
namespace FileSys {
template <typename EntryType>
Result BucketTree::ScanContinuousReading(ContinuousReadingInfo* out_info,
const ContinuousReadingParam<EntryType>& param) const {
static_assert(std::is_trivial_v<ContinuousReadingParam<EntryType>>);
// Validate our preconditions.
ASSERT(this->IsInitialized());
ASSERT(out_info != nullptr);
ASSERT(m_entry_size == sizeof(EntryType));
// Reset the output.
out_info->Reset();
// If there's nothing to read, we're done.
R_SUCCEED_IF(param.size == 0);
// If we're reading a fragment, we're done.
R_SUCCEED_IF(param.entry.IsFragment());
// Validate the first entry.
auto entry = param.entry;
auto cur_offset = param.offset;
R_UNLESS(entry.GetVirtualOffset() <= cur_offset, ResultOutOfRange);
// Create a pooled buffer for our scan.
PooledBuffer pool(m_node_size, 1);
char* buffer = nullptr;
s64 entry_storage_size = m_entry_storage->GetSize();
// Read the node.
if (m_node_size <= pool.GetSize()) {
buffer = pool.GetBuffer();
const auto ofs = param.entry_set.index * static_cast<s64>(m_node_size);
R_UNLESS(m_node_size + ofs <= static_cast<size_t>(entry_storage_size),
ResultInvalidBucketTreeNodeEntryCount);
m_entry_storage->Read(reinterpret_cast<u8*>(buffer), m_node_size, ofs);
}
// Calculate extents.
const auto end_offset = cur_offset + static_cast<s64>(param.size);
s64 phys_offset = entry.GetPhysicalOffset();
// Start merge tracking.
s64 merge_size = 0;
s64 readable_size = 0;
bool merged = false;
// Iterate.
auto entry_index = param.entry_index;
for (const auto entry_count = param.entry_set.count; entry_index < entry_count; ++entry_index) {
// If we're past the end, we're done.
if (end_offset <= cur_offset) {
break;
}
// Validate the entry offset.
const auto entry_offset = entry.GetVirtualOffset();
R_UNLESS(entry_offset <= cur_offset, ResultInvalidIndirectEntryOffset);
// Get the next entry.
EntryType next_entry = {};
s64 next_entry_offset;
if (entry_index + 1 < entry_count) {
if (buffer != nullptr) {
const auto ofs = impl::GetBucketTreeEntryOffset(0, m_entry_size, entry_index + 1);
std::memcpy(std::addressof(next_entry), buffer + ofs, m_entry_size);
} else {
const auto ofs = impl::GetBucketTreeEntryOffset(param.entry_set.index, m_node_size,
m_entry_size, entry_index + 1);
m_entry_storage->ReadObject(std::addressof(next_entry), ofs);
}
next_entry_offset = next_entry.GetVirtualOffset();
R_UNLESS(param.offsets.IsInclude(next_entry_offset), ResultInvalidIndirectEntryOffset);
} else {
next_entry_offset = param.entry_set.offset;
}
// Validate the next entry offset.
R_UNLESS(cur_offset < next_entry_offset, ResultInvalidIndirectEntryOffset);
// Determine the much data there is.
const auto data_size = next_entry_offset - cur_offset;
ASSERT(data_size > 0);
// Determine how much data we should read.
const auto remaining_size = end_offset - cur_offset;
const size_t read_size = static_cast<size_t>(std::min(data_size, remaining_size));
ASSERT(read_size <= param.size);
// Update our merge tracking.
if (entry.IsFragment()) {
// If we can't merge, stop looping.
if (EntryType::FragmentSizeMax <= read_size || remaining_size <= data_size) {
break;
}
// Otherwise, add the current size to the merge size.
merge_size += read_size;
} else {
// If we can't merge, stop looping.
if (phys_offset != entry.GetPhysicalOffset()) {
break;
}
// Add the size to the readable amount.
readable_size += merge_size + read_size;
ASSERT(readable_size <= static_cast<s64>(param.size));
// Update whether we've merged.
merged |= merge_size > 0;
merge_size = 0;
}
// Advance.
cur_offset += read_size;
ASSERT(cur_offset <= end_offset);
phys_offset += next_entry_offset - entry_offset;
entry = next_entry;
}
// If we merged, set our readable size.
if (merged) {
out_info->SetReadSize(static_cast<size_t>(readable_size));
}
out_info->SetSkipCount(entry_index - param.entry_index);
R_SUCCEED();
}
template <typename EntryType>
Result BucketTree::Visitor::ScanContinuousReading(ContinuousReadingInfo* out_info, s64 offset,
size_t size) const {
static_assert(std::is_trivial_v<EntryType>);
ASSERT(this->IsValid());
// Create our parameters.
ContinuousReadingParam<EntryType> param = {
.offset = offset,
.size = size,
.entry_set = m_entry_set.header,
.entry_index = m_entry_index,
.offsets{},
.entry{},
};
std::memcpy(std::addressof(param.offsets), std::addressof(m_offsets),
sizeof(BucketTree::Offsets));
std::memcpy(std::addressof(param.entry), m_entry, sizeof(EntryType));
// Scan.
R_RETURN(m_tree->ScanContinuousReading<EntryType>(out_info, param));
}
} // namespace FileSys

View File

@ -1,110 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "core/file_sys/fssystem/fssystem_bucket_tree.h"
namespace FileSys::impl {
class SafeValue {
public:
static s64 GetInt64(const void* ptr) {
s64 value;
std::memcpy(std::addressof(value), ptr, sizeof(s64));
return value;
}
static s64 GetInt64(const s64* ptr) {
return GetInt64(static_cast<const void*>(ptr));
}
static s64 GetInt64(const s64& v) {
return GetInt64(std::addressof(v));
}
static void SetInt64(void* dst, const void* src) {
std::memcpy(dst, src, sizeof(s64));
}
static void SetInt64(void* dst, const s64* src) {
return SetInt64(dst, static_cast<const void*>(src));
}
static void SetInt64(void* dst, const s64& v) {
return SetInt64(dst, std::addressof(v));
}
};
template <typename IteratorType>
struct BucketTreeNode {
using Header = BucketTree::NodeHeader;
Header header;
s32 GetCount() const {
return this->header.count;
}
void* GetArray() {
return std::addressof(this->header) + 1;
}
template <typename T>
T* GetArray() {
return reinterpret_cast<T*>(this->GetArray());
}
const void* GetArray() const {
return std::addressof(this->header) + 1;
}
template <typename T>
const T* GetArray() const {
return reinterpret_cast<const T*>(this->GetArray());
}
s64 GetBeginOffset() const {
return *this->GetArray<s64>();
}
s64 GetEndOffset() const {
return this->header.offset;
}
IteratorType GetBegin() {
return IteratorType(this->GetArray<s64>());
}
IteratorType GetEnd() {
return IteratorType(this->GetArray<s64>()) + this->header.count;
}
IteratorType GetBegin() const {
return IteratorType(this->GetArray<s64>());
}
IteratorType GetEnd() const {
return IteratorType(this->GetArray<s64>()) + this->header.count;
}
IteratorType GetBegin(size_t entry_size) {
return IteratorType(this->GetArray(), entry_size);
}
IteratorType GetEnd(size_t entry_size) {
return IteratorType(this->GetArray(), entry_size) + this->header.count;
}
IteratorType GetBegin(size_t entry_size) const {
return IteratorType(this->GetArray(), entry_size);
}
IteratorType GetEnd(size_t entry_size) const {
return IteratorType(this->GetArray(), entry_size) + this->header.count;
}
};
constexpr inline s64 GetBucketTreeEntryOffset(s64 entry_set_offset, size_t entry_size,
s32 entry_index) {
return entry_set_offset + sizeof(BucketTree::NodeHeader) +
entry_index * static_cast<s64>(entry_size);
}
constexpr inline s64 GetBucketTreeEntryOffset(s32 entry_set_index, size_t node_size,
size_t entry_size, s32 entry_index) {
return GetBucketTreeEntryOffset(entry_set_index * static_cast<s64>(node_size), entry_size,
entry_index);
}
} // namespace FileSys::impl

View File

@ -1,963 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/literals.h"
#include "core/file_sys/errors.h"
#include "core/file_sys/fssystem/fs_i_storage.h"
#include "core/file_sys/fssystem/fssystem_bucket_tree.h"
#include "core/file_sys/fssystem/fssystem_compression_common.h"
#include "core/file_sys/fssystem/fssystem_pooled_buffer.h"
#include "core/file_sys/vfs.h"
namespace FileSys {
using namespace Common::Literals;
class CompressedStorage : public IReadOnlyStorage {
YUZU_NON_COPYABLE(CompressedStorage);
YUZU_NON_MOVEABLE(CompressedStorage);
public:
static constexpr size_t NodeSize = 16_KiB;
struct Entry {
s64 virt_offset;
s64 phys_offset;
CompressionType compression_type;
s32 phys_size;
s64 GetPhysicalSize() const {
return this->phys_size;
}
};
static_assert(std::is_trivial_v<Entry>);
static_assert(sizeof(Entry) == 0x18);
public:
static constexpr s64 QueryNodeStorageSize(s32 entry_count) {
return BucketTree::QueryNodeStorageSize(NodeSize, sizeof(Entry), entry_count);
}
static constexpr s64 QueryEntryStorageSize(s32 entry_count) {
return BucketTree::QueryEntryStorageSize(NodeSize, sizeof(Entry), entry_count);
}
private:
class CompressedStorageCore {
YUZU_NON_COPYABLE(CompressedStorageCore);
YUZU_NON_MOVEABLE(CompressedStorageCore);
public:
CompressedStorageCore() : m_table(), m_data_storage() {}
~CompressedStorageCore() {
this->Finalize();
}
public:
Result Initialize(VirtualFile data_storage, VirtualFile node_storage,
VirtualFile entry_storage, s32 bktr_entry_count, size_t block_size_max,
size_t continuous_reading_size_max,
GetDecompressorFunction get_decompressor) {
// Check pre-conditions.
ASSERT(0 < block_size_max);
ASSERT(block_size_max <= continuous_reading_size_max);
ASSERT(get_decompressor != nullptr);
// Initialize our entry table.
R_TRY(m_table.Initialize(node_storage, entry_storage, NodeSize, sizeof(Entry),
bktr_entry_count));
// Set our other fields.
m_block_size_max = block_size_max;
m_continuous_reading_size_max = continuous_reading_size_max;
m_data_storage = data_storage;
m_get_decompressor_function = get_decompressor;
R_SUCCEED();
}
void Finalize() {
if (this->IsInitialized()) {
m_table.Finalize();
m_data_storage = VirtualFile();
}
}
VirtualFile GetDataStorage() {
return m_data_storage;
}
Result GetDataStorageSize(s64* out) {
// Check pre-conditions.
ASSERT(out != nullptr);
// Get size.
*out = m_data_storage->GetSize();
R_SUCCEED();
}
BucketTree& GetEntryTable() {
return m_table;
}
Result GetEntryList(Entry* out_entries, s32* out_read_count, s32 max_entry_count,
s64 offset, s64 size) {
// Check pre-conditions.
ASSERT(offset >= 0);
ASSERT(size >= 0);
ASSERT(this->IsInitialized());
// Check that we can output the count.
R_UNLESS(out_read_count != nullptr, ResultNullptrArgument);
// Check that we have anything to read at all.
R_SUCCEED_IF(size == 0);
// Check that either we have a buffer, or this is to determine how many we need.
if (max_entry_count != 0) {
R_UNLESS(out_entries != nullptr, ResultNullptrArgument);
}
// Get the table offsets.
BucketTree::Offsets table_offsets;
R_TRY(m_table.GetOffsets(std::addressof(table_offsets)));
// Validate arguments.
R_UNLESS(table_offsets.IsInclude(offset, size), ResultOutOfRange);
// Find the offset in our tree.
BucketTree::Visitor visitor;
R_TRY(m_table.Find(std::addressof(visitor), offset));
{
const auto entry_offset = visitor.Get<Entry>()->virt_offset;
R_UNLESS(0 <= entry_offset && table_offsets.IsInclude(entry_offset),
ResultUnexpectedInCompressedStorageA);
}
// Get the entries.
const auto end_offset = offset + size;
s32 read_count = 0;
while (visitor.Get<Entry>()->virt_offset < end_offset) {
// If we should be setting the output, do so.
if (max_entry_count != 0) {
// Ensure we only read as many entries as we can.
if (read_count >= max_entry_count) {
break;
}
// Set the current output entry.
out_entries[read_count] = *visitor.Get<Entry>();
}
// Increase the read count.
++read_count;
// If we're at the end, we're done.
if (!visitor.CanMoveNext()) {
break;
}
// Move to the next entry.
R_TRY(visitor.MoveNext());
}
// Set the output read count.
*out_read_count = read_count;
R_SUCCEED();
}
Result GetSize(s64* out) {
// Check pre-conditions.
ASSERT(out != nullptr);
// Get our table offsets.
BucketTree::Offsets offsets;
R_TRY(m_table.GetOffsets(std::addressof(offsets)));
// Set the output.
*out = offsets.end_offset;
R_SUCCEED();
}
Result OperatePerEntry(s64 offset, s64 size, auto f) {
// Check pre-conditions.
ASSERT(offset >= 0);
ASSERT(size >= 0);
ASSERT(this->IsInitialized());
// Succeed if there's nothing to operate on.
R_SUCCEED_IF(size == 0);
// Get the table offsets.
BucketTree::Offsets table_offsets;
R_TRY(m_table.GetOffsets(std::addressof(table_offsets)));
// Validate arguments.
R_UNLESS(table_offsets.IsInclude(offset, size), ResultOutOfRange);
// Find the offset in our tree.
BucketTree::Visitor visitor;
R_TRY(m_table.Find(std::addressof(visitor), offset));
{
const auto entry_offset = visitor.Get<Entry>()->virt_offset;
R_UNLESS(0 <= entry_offset && table_offsets.IsInclude(entry_offset),
ResultUnexpectedInCompressedStorageA);
}
// Prepare to operate in chunks.
auto cur_offset = offset;
const auto end_offset = offset + static_cast<s64>(size);
while (cur_offset < end_offset) {
// Get the current entry.
const auto cur_entry = *visitor.Get<Entry>();
// Get and validate the entry's offset.
const auto cur_entry_offset = cur_entry.virt_offset;
R_UNLESS(cur_entry_offset <= cur_offset, ResultUnexpectedInCompressedStorageA);
// Get and validate the next entry offset.
s64 next_entry_offset;
if (visitor.CanMoveNext()) {
R_TRY(visitor.MoveNext());
next_entry_offset = visitor.Get<Entry>()->virt_offset;
R_UNLESS(table_offsets.IsInclude(next_entry_offset),
ResultUnexpectedInCompressedStorageA);
} else {
next_entry_offset = table_offsets.end_offset;
}
R_UNLESS(cur_offset < next_entry_offset, ResultUnexpectedInCompressedStorageA);
// Get the offset of the entry in the data we read.
const auto data_offset = cur_offset - cur_entry_offset;
const auto data_size = (next_entry_offset - cur_entry_offset);
ASSERT(data_size > 0);
// Determine how much is left.
const auto remaining_size = end_offset - cur_offset;
const auto cur_size = std::min<s64>(remaining_size, data_size - data_offset);
ASSERT(cur_size <= size);
// Get the data storage size.
s64 storage_size = m_data_storage->GetSize();
// Check that our read remains naively physically in bounds.
R_UNLESS(0 <= cur_entry.phys_offset && cur_entry.phys_offset <= storage_size,
ResultUnexpectedInCompressedStorageC);
// If we have any compression, verify that we remain physically in bounds.
if (cur_entry.compression_type != CompressionType::None) {
R_UNLESS(cur_entry.phys_offset + cur_entry.GetPhysicalSize() <= storage_size,
ResultUnexpectedInCompressedStorageC);
}
// Check that block alignment requirements are met.
if (CompressionTypeUtility::IsBlockAlignmentRequired(cur_entry.compression_type)) {
R_UNLESS(Common::IsAligned(cur_entry.phys_offset, CompressionBlockAlignment),
ResultUnexpectedInCompressedStorageA);
}
// Invoke the operator.
bool is_continuous = true;
R_TRY(
f(std::addressof(is_continuous), cur_entry, data_size, data_offset, cur_size));
// If not continuous, we're done.
if (!is_continuous) {
break;
}
// Advance.
cur_offset += cur_size;
}
R_SUCCEED();
}
public:
using ReadImplFunction = std::function<Result(void*, size_t)>;
using ReadFunction = std::function<Result(size_t, const ReadImplFunction&)>;
public:
Result Read(s64 offset, s64 size, const ReadFunction& read_func) {
// Check pre-conditions.
ASSERT(offset >= 0);
ASSERT(this->IsInitialized());
// Succeed immediately, if we have nothing to read.
R_SUCCEED_IF(size == 0);
// Declare read lambda.
constexpr int EntriesCountMax = 0x80;
struct Entries {
CompressionType compression_type;
u32 gap_from_prev;
u32 physical_size;
u32 virtual_size;
};
std::array<Entries, EntriesCountMax> entries;
s32 entry_count = 0;
Entry prev_entry = {
.virt_offset = -1,
.phys_offset{},
.compression_type{},
.phys_size{},
};
bool will_allocate_pooled_buffer = false;
s64 required_access_physical_offset = 0;
s64 required_access_physical_size = 0;
auto PerformRequiredRead = [&]() -> Result {
// If there are no entries, we have nothing to do.
R_SUCCEED_IF(entry_count == 0);
// Get the remaining size in a convenient form.
const size_t total_required_size =
static_cast<size_t>(required_access_physical_size);
// Perform the read based on whether we need to allocate a buffer.
if (will_allocate_pooled_buffer) {
// Allocate a pooled buffer.
PooledBuffer pooled_buffer;
if (pooled_buffer.GetAllocatableSizeMax() >= total_required_size) {
pooled_buffer.Allocate(total_required_size, m_block_size_max);
} else {
pooled_buffer.AllocateParticularlyLarge(
std::min<size_t>(
total_required_size,
PooledBuffer::GetAllocatableParticularlyLargeSizeMax()),
m_block_size_max);
}
// Read each of the entries.
for (s32 entry_idx = 0; entry_idx < entry_count; ++entry_idx) {
// Determine the current read size.
bool will_use_pooled_buffer = false;
const size_t cur_read_size = [&]() -> size_t {
if (const size_t target_entry_size =
static_cast<size_t>(entries[entry_idx].physical_size) +
static_cast<size_t>(entries[entry_idx].gap_from_prev);
target_entry_size <= pooled_buffer.GetSize()) {
// We'll be using the pooled buffer.
will_use_pooled_buffer = true;
// Determine how much we can read.
const size_t max_size = std::min<size_t>(
required_access_physical_size, pooled_buffer.GetSize());
size_t read_size = 0;
for (auto n = entry_idx; n < entry_count; ++n) {
const size_t cur_entry_size =
static_cast<size_t>(entries[n].physical_size) +
static_cast<size_t>(entries[n].gap_from_prev);
if (read_size + cur_entry_size > max_size) {
break;
}
read_size += cur_entry_size;
}
return read_size;
} else {
// If we don't fit, we must be uncompressed.
ASSERT(entries[entry_idx].compression_type ==
CompressionType::None);
// We can perform the whole of an uncompressed read directly.
return entries[entry_idx].virtual_size;
}
}();
// Perform the read based on whether or not we'll use the pooled buffer.
if (will_use_pooled_buffer) {
// Read the compressed data into the pooled buffer.
auto* const buffer = pooled_buffer.GetBuffer();
m_data_storage->Read(reinterpret_cast<u8*>(buffer), cur_read_size,
required_access_physical_offset);
// Decompress the data.
size_t buffer_offset;
for (buffer_offset = 0;
entry_idx < entry_count &&
((static_cast<size_t>(entries[entry_idx].physical_size) +
static_cast<size_t>(entries[entry_idx].gap_from_prev)) == 0 ||
buffer_offset < cur_read_size);
buffer_offset += entries[entry_idx++].physical_size) {
// Advance by the relevant gap.
buffer_offset += entries[entry_idx].gap_from_prev;
const auto compression_type = entries[entry_idx].compression_type;
switch (compression_type) {
case CompressionType::None: {
// Check that we can remain within bounds.
ASSERT(buffer_offset + entries[entry_idx].virtual_size <=
cur_read_size);
// Perform no decompression.
R_TRY(read_func(
entries[entry_idx].virtual_size,
[&](void* dst, size_t dst_size) -> Result {
// Check that the size is valid.
ASSERT(dst_size == entries[entry_idx].virtual_size);
// We have no compression, so just copy the data
// out.
std::memcpy(dst, buffer + buffer_offset,
entries[entry_idx].virtual_size);
R_SUCCEED();
}));
break;
}
case CompressionType::Zeros: {
// Check that we can remain within bounds.
ASSERT(buffer_offset <= cur_read_size);
// Zero the memory.
R_TRY(read_func(
entries[entry_idx].virtual_size,
[&](void* dst, size_t dst_size) -> Result {
// Check that the size is valid.
ASSERT(dst_size == entries[entry_idx].virtual_size);
// The data is zeroes, so zero the buffer.
std::memset(dst, 0, entries[entry_idx].virtual_size);
R_SUCCEED();
}));
break;
}
default: {
// Check that we can remain within bounds.
ASSERT(buffer_offset + entries[entry_idx].physical_size <=
cur_read_size);
// Get the decompressor.
const auto decompressor =
this->GetDecompressor(compression_type);
R_UNLESS(decompressor != nullptr,
ResultUnexpectedInCompressedStorageB);
// Decompress the data.
R_TRY(read_func(entries[entry_idx].virtual_size,
[&](void* dst, size_t dst_size) -> Result {
// Check that the size is valid.
ASSERT(dst_size ==
entries[entry_idx].virtual_size);
// Perform the decompression.
R_RETURN(decompressor(
dst, entries[entry_idx].virtual_size,
buffer + buffer_offset,
entries[entry_idx].physical_size));
}));
break;
}
}
}
// Check that we processed the correct amount of data.
ASSERT(buffer_offset == cur_read_size);
} else {
// Account for the gap from the previous entry.
required_access_physical_offset += entries[entry_idx].gap_from_prev;
required_access_physical_size -= entries[entry_idx].gap_from_prev;
// We don't need the buffer (as the data is uncompressed), so just
// execute the read.
R_TRY(
read_func(cur_read_size, [&](void* dst, size_t dst_size) -> Result {
// Check that the size is valid.
ASSERT(dst_size == cur_read_size);
// Perform the read.
m_data_storage->Read(reinterpret_cast<u8*>(dst), cur_read_size,
required_access_physical_offset);
R_SUCCEED();
}));
}
// Advance on.
required_access_physical_offset += cur_read_size;
required_access_physical_size -= cur_read_size;
}
// Verify that we have nothing remaining to read.
ASSERT(required_access_physical_size == 0);
R_SUCCEED();
} else {
// We don't need a buffer, so just execute the read.
R_TRY(read_func(total_required_size, [&](void* dst, size_t dst_size) -> Result {
// Check that the size is valid.
ASSERT(dst_size == total_required_size);
// Perform the read.
m_data_storage->Read(reinterpret_cast<u8*>(dst), total_required_size,
required_access_physical_offset);
R_SUCCEED();
}));
}
R_SUCCEED();
};
R_TRY(this->OperatePerEntry(
offset, size,
[&](bool* out_continuous, const Entry& entry, s64 virtual_data_size,
s64 data_offset, s64 read_size) -> Result {
// Determine the physical extents.
s64 physical_offset, physical_size;
if (CompressionTypeUtility::IsRandomAccessible(entry.compression_type)) {
physical_offset = entry.phys_offset + data_offset;
physical_size = read_size;
} else {
physical_offset = entry.phys_offset;
physical_size = entry.GetPhysicalSize();
}
// If we have a pending data storage operation, perform it if we have to.
const s64 required_access_physical_end =
required_access_physical_offset + required_access_physical_size;
if (required_access_physical_size > 0) {
const bool required_by_gap =
!(required_access_physical_end <= physical_offset &&
physical_offset <= Common::AlignUp(required_access_physical_end,
CompressionBlockAlignment));
const bool required_by_continuous_size =
((physical_size + physical_offset) - required_access_physical_end) +
required_access_physical_size >
static_cast<s64>(m_continuous_reading_size_max);
const bool required_by_entry_count = entry_count == EntriesCountMax;
if (required_by_gap || required_by_continuous_size ||
required_by_entry_count) {
// Check that our planned access is sane.
ASSERT(!will_allocate_pooled_buffer ||
required_access_physical_size <=
static_cast<s64>(m_continuous_reading_size_max));
// Perform the required read.
const Result rc = PerformRequiredRead();
if (R_FAILED(rc)) {
R_THROW(rc);
}
// Reset our requirements.
prev_entry.virt_offset = -1;
required_access_physical_size = 0;
entry_count = 0;
will_allocate_pooled_buffer = false;
}
}
// Sanity check that we're within bounds on entries.
ASSERT(entry_count < EntriesCountMax);
// Determine if a buffer allocation is needed.
if (entry.compression_type != CompressionType::None ||
(prev_entry.virt_offset >= 0 &&
entry.virt_offset - prev_entry.virt_offset !=
entry.phys_offset - prev_entry.phys_offset)) {
will_allocate_pooled_buffer = true;
}
// If we need to access the data storage, update our required access parameters.
if (CompressionTypeUtility::IsDataStorageAccessRequired(
entry.compression_type)) {
// If the data is compressed, ensure the access is sane.
if (entry.compression_type != CompressionType::None) {
R_UNLESS(data_offset == 0, ResultInvalidOffset);
R_UNLESS(virtual_data_size == read_size, ResultInvalidSize);
R_UNLESS(entry.GetPhysicalSize() <= static_cast<s64>(m_block_size_max),
ResultUnexpectedInCompressedStorageD);
}
// Update the required access parameters.
s64 gap_from_prev;
if (required_access_physical_size > 0) {
gap_from_prev = physical_offset - required_access_physical_end;
} else {
gap_from_prev = 0;
required_access_physical_offset = physical_offset;
}
required_access_physical_size += physical_size + gap_from_prev;
// Create an entry to access the data storage.
entries[entry_count++] = {
.compression_type = entry.compression_type,
.gap_from_prev = static_cast<u32>(gap_from_prev),
.physical_size = static_cast<u32>(physical_size),
.virtual_size = static_cast<u32>(read_size),
};
} else {
// Verify that we're allowed to be operating on the non-data-storage-access
// type.
R_UNLESS(entry.compression_type == CompressionType::Zeros,
ResultUnexpectedInCompressedStorageB);
// If we have entries, create a fake entry for the zero region.
if (entry_count != 0) {
// We need to have a physical size.
R_UNLESS(entry.GetPhysicalSize() != 0,
ResultUnexpectedInCompressedStorageD);
// Create a fake entry.
entries[entry_count++] = {
.compression_type = CompressionType::Zeros,
.gap_from_prev = 0,
.physical_size = 0,
.virtual_size = static_cast<u32>(read_size),
};
} else {
// We have no entries, so we can just perform the read.
const Result rc =
read_func(static_cast<size_t>(read_size),
[&](void* dst, size_t dst_size) -> Result {
// Check the space we should zero is correct.
ASSERT(dst_size == static_cast<size_t>(read_size));
// Zero the memory.
std::memset(dst, 0, read_size);
R_SUCCEED();
});
if (R_FAILED(rc)) {
R_THROW(rc);
}
}
}
// Set the previous entry.
prev_entry = entry;
// We're continuous.
*out_continuous = true;
R_SUCCEED();
}));
// If we still have a pending access, perform it.
if (required_access_physical_size != 0) {
R_TRY(PerformRequiredRead());
}
R_SUCCEED();
}
private:
DecompressorFunction GetDecompressor(CompressionType type) const {
// Check that we can get a decompressor for the type.
if (CompressionTypeUtility::IsUnknownType(type)) {
return nullptr;
}
// Get the decompressor.
return m_get_decompressor_function(type);
}
bool IsInitialized() const {
return m_table.IsInitialized();
}
private:
size_t m_block_size_max;
size_t m_continuous_reading_size_max;
BucketTree m_table;
VirtualFile m_data_storage;
GetDecompressorFunction m_get_decompressor_function;
};
class CacheManager {
YUZU_NON_COPYABLE(CacheManager);
YUZU_NON_MOVEABLE(CacheManager);
private:
struct AccessRange {
s64 virtual_offset;
s64 virtual_size;
u32 physical_size;
bool is_block_alignment_required;
s64 GetEndVirtualOffset() const {
return this->virtual_offset + this->virtual_size;
}
};
static_assert(std::is_trivial_v<AccessRange>);
public:
CacheManager() = default;
public:
Result Initialize(s64 storage_size, size_t cache_size_0, size_t cache_size_1,
size_t max_cache_entries) {
// Set our fields.
m_storage_size = storage_size;
R_SUCCEED();
}
Result Read(CompressedStorageCore& core, s64 offset, void* buffer, size_t size) {
// If we have nothing to read, succeed.
R_SUCCEED_IF(size == 0);
// Check that we have a buffer to read into.
R_UNLESS(buffer != nullptr, ResultNullptrArgument);
// Check that the read is in bounds.
R_UNLESS(offset <= m_storage_size, ResultInvalidOffset);
// Determine how much we can read.
const size_t read_size = std::min<size_t>(size, m_storage_size - offset);
// Create head/tail ranges.
AccessRange head_range = {};
AccessRange tail_range = {};
bool is_tail_set = false;
// Operate to determine the head range.
R_TRY(core.OperatePerEntry(
offset, 1,
[&](bool* out_continuous, const Entry& entry, s64 virtual_data_size,
s64 data_offset, s64 data_read_size) -> Result {
// Set the head range.
head_range = {
.virtual_offset = entry.virt_offset,
.virtual_size = virtual_data_size,
.physical_size = static_cast<u32>(entry.phys_size),
.is_block_alignment_required =
CompressionTypeUtility::IsBlockAlignmentRequired(
entry.compression_type),
};
// If required, set the tail range.
if (static_cast<s64>(offset + read_size) <=
entry.virt_offset + virtual_data_size) {
tail_range = {
.virtual_offset = entry.virt_offset,
.virtual_size = virtual_data_size,
.physical_size = static_cast<u32>(entry.phys_size),
.is_block_alignment_required =
CompressionTypeUtility::IsBlockAlignmentRequired(
entry.compression_type),
};
is_tail_set = true;
}
// We only want to determine the head range, so we're not continuous.
*out_continuous = false;
R_SUCCEED();
}));
// If necessary, determine the tail range.
if (!is_tail_set) {
R_TRY(core.OperatePerEntry(
offset + read_size - 1, 1,
[&](bool* out_continuous, const Entry& entry, s64 virtual_data_size,
s64 data_offset, s64 data_read_size) -> Result {
// Set the tail range.
tail_range = {
.virtual_offset = entry.virt_offset,
.virtual_size = virtual_data_size,
.physical_size = static_cast<u32>(entry.phys_size),
.is_block_alignment_required =
CompressionTypeUtility::IsBlockAlignmentRequired(
entry.compression_type),
};
// We only want to determine the tail range, so we're not continuous.
*out_continuous = false;
R_SUCCEED();
}));
}
// Begin performing the accesses.
s64 cur_offset = offset;
size_t cur_size = read_size;
char* cur_dst = static_cast<char*>(buffer);
// Determine our alignment.
const bool head_unaligned = head_range.is_block_alignment_required &&
(cur_offset != head_range.virtual_offset ||
static_cast<s64>(cur_size) < head_range.virtual_size);
const bool tail_unaligned = [&]() -> bool {
if (tail_range.is_block_alignment_required) {
if (static_cast<s64>(cur_size + cur_offset) ==
tail_range.GetEndVirtualOffset()) {
return false;
} else if (!head_unaligned) {
return true;
} else {
return head_range.GetEndVirtualOffset() <
static_cast<s64>(cur_size + cur_offset);
}
} else {
return false;
}
}();
// Determine start/end offsets.
const s64 start_offset =
head_range.is_block_alignment_required ? head_range.virtual_offset : cur_offset;
const s64 end_offset = tail_range.is_block_alignment_required
? tail_range.GetEndVirtualOffset()
: cur_offset + cur_size;
// Perform the read.
bool is_burst_reading = false;
R_TRY(core.Read(
start_offset, end_offset - start_offset,
[&](size_t size_buffer_required,
const CompressedStorageCore::ReadImplFunction& read_impl) -> Result {
// Determine whether we're burst reading.
const AccessRange* unaligned_range = nullptr;
if (!is_burst_reading) {
// Check whether we're using head, tail, or none as unaligned.
if (head_unaligned && head_range.virtual_offset <= cur_offset &&
cur_offset < head_range.GetEndVirtualOffset()) {
unaligned_range = std::addressof(head_range);
} else if (tail_unaligned && tail_range.virtual_offset <= cur_offset &&
cur_offset < tail_range.GetEndVirtualOffset()) {
unaligned_range = std::addressof(tail_range);
} else {
is_burst_reading = true;
}
}
ASSERT((is_burst_reading ^ (unaligned_range != nullptr)));
// Perform reading by burst, or not.
if (is_burst_reading) {
// Check that the access is valid for burst reading.
ASSERT(size_buffer_required <= cur_size);
// Perform the read.
Result rc = read_impl(cur_dst, size_buffer_required);
if (R_FAILED(rc)) {
R_THROW(rc);
}
// Advance.
cur_dst += size_buffer_required;
cur_offset += size_buffer_required;
cur_size -= size_buffer_required;
// Determine whether we're going to continue burst reading.
const s64 offset_aligned =
tail_unaligned ? tail_range.virtual_offset : end_offset;
ASSERT(cur_offset <= offset_aligned);
if (offset_aligned <= cur_offset) {
is_burst_reading = false;
}
} else {
// We're not burst reading, so we have some unaligned range.
ASSERT(unaligned_range != nullptr);
// Check that the size is correct.
ASSERT(size_buffer_required ==
static_cast<size_t>(unaligned_range->virtual_size));
// Get a pooled buffer for our read.
PooledBuffer pooled_buffer;
pooled_buffer.Allocate(size_buffer_required, size_buffer_required);
// Perform read.
Result rc = read_impl(pooled_buffer.GetBuffer(), size_buffer_required);
if (R_FAILED(rc)) {
R_THROW(rc);
}
// Copy the data we read to the destination.
const size_t skip_size = cur_offset - unaligned_range->virtual_offset;
const size_t copy_size = std::min<size_t>(
cur_size, unaligned_range->GetEndVirtualOffset() - cur_offset);
std::memcpy(cur_dst, pooled_buffer.GetBuffer() + skip_size, copy_size);
// Advance.
cur_dst += copy_size;
cur_offset += copy_size;
cur_size -= copy_size;
}
R_SUCCEED();
}));
R_SUCCEED();
}
private:
s64 m_storage_size = 0;
};
public:
CompressedStorage() = default;
virtual ~CompressedStorage() {
this->Finalize();
}
Result Initialize(VirtualFile data_storage, VirtualFile node_storage, VirtualFile entry_storage,
s32 bktr_entry_count, size_t block_size_max,
size_t continuous_reading_size_max, GetDecompressorFunction get_decompressor,
size_t cache_size_0, size_t cache_size_1, s32 max_cache_entries) {
// Initialize our core.
R_TRY(m_core.Initialize(data_storage, node_storage, entry_storage, bktr_entry_count,
block_size_max, continuous_reading_size_max, get_decompressor));
// Get our core size.
s64 core_size = 0;
R_TRY(m_core.GetSize(std::addressof(core_size)));
// Initialize our cache manager.
R_TRY(m_cache_manager.Initialize(core_size, cache_size_0, cache_size_1, max_cache_entries));
R_SUCCEED();
}
void Finalize() {
m_core.Finalize();
}
VirtualFile GetDataStorage() {
return m_core.GetDataStorage();
}
Result GetDataStorageSize(s64* out) {
R_RETURN(m_core.GetDataStorageSize(out));
}
Result GetEntryList(Entry* out_entries, s32* out_read_count, s32 max_entry_count, s64 offset,
s64 size) {
R_RETURN(m_core.GetEntryList(out_entries, out_read_count, max_entry_count, offset, size));
}
BucketTree& GetEntryTable() {
return m_core.GetEntryTable();
}
public:
virtual size_t GetSize() const override {
s64 ret{};
m_core.GetSize(&ret);
return ret;
}
virtual size_t Read(u8* buffer, size_t size, size_t offset) const override {
if (R_SUCCEEDED(m_cache_manager.Read(m_core, offset, buffer, size))) {
return size;
} else {
return 0;
}
}
private:
mutable CompressedStorageCore m_core;
mutable CacheManager m_cache_manager;
};
} // namespace FileSys

View File

@ -1,43 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "core/hle/result.h"
namespace FileSys {
enum class CompressionType : u8 {
None = 0,
Zeros = 1,
Two = 2,
Lz4 = 3,
Unknown = 4,
};
using DecompressorFunction = Result (*)(void*, size_t, const void*, size_t);
using GetDecompressorFunction = DecompressorFunction (*)(CompressionType);
constexpr s64 CompressionBlockAlignment = 0x10;
namespace CompressionTypeUtility {
constexpr bool IsBlockAlignmentRequired(CompressionType type) {
return type != CompressionType::None && type != CompressionType::Zeros;
}
constexpr bool IsDataStorageAccessRequired(CompressionType type) {
return type != CompressionType::Zeros;
}
constexpr bool IsRandomAccessible(CompressionType type) {
return type == CompressionType::None;
}
constexpr bool IsUnknownType(CompressionType type) {
return type >= CompressionType::Unknown;
}
} // namespace CompressionTypeUtility
} // namespace FileSys

View File

@ -1,36 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/lz4_compression.h"
#include "core/file_sys/fssystem/fssystem_compression_configuration.h"
namespace FileSys {
namespace {
Result DecompressLz4(void* dst, size_t dst_size, const void* src, size_t src_size) {
auto result = Common::Compression::DecompressDataLZ4(dst, dst_size, src, src_size);
R_UNLESS(static_cast<size_t>(result) == dst_size, ResultUnexpectedInCompressedStorageC);
R_SUCCEED();
}
constexpr DecompressorFunction GetNcaDecompressorFunction(CompressionType type) {
switch (type) {
case CompressionType::Lz4:
return DecompressLz4;
default:
return nullptr;
}
}
} // namespace
const NcaCompressionConfiguration& GetNcaCompressionConfiguration() {
static const NcaCompressionConfiguration configuration = {
.get_decompressor = GetNcaDecompressorFunction,
};
return configuration;
}
} // namespace FileSys

View File

@ -1,12 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "core/file_sys/fssystem/fssystem_nca_file_system_driver.h"
namespace FileSys {
const NcaCompressionConfiguration& GetNcaCompressionConfiguration();
}

View File

@ -1,65 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/crypto/aes_util.h"
#include "core/crypto/key_manager.h"
#include "core/file_sys/fssystem/fssystem_crypto_configuration.h"
namespace FileSys {
namespace {
void GenerateKey(void* dst_key, size_t dst_key_size, const void* src_key, size_t src_key_size,
s32 key_type) {
if (key_type == static_cast<s32>(KeyType::ZeroKey)) {
std::memset(dst_key, 0, dst_key_size);
return;
}
if (key_type == static_cast<s32>(KeyType::InvalidKey) ||
key_type < static_cast<s32>(KeyType::ZeroKey) ||
key_type >= static_cast<s32>(KeyType::NcaExternalKey)) {
std::memset(dst_key, 0xFF, dst_key_size);
return;
}
const auto& instance = Core::Crypto::KeyManager::Instance();
if (key_type == static_cast<s32>(KeyType::NcaHeaderKey1) ||
key_type == static_cast<s32>(KeyType::NcaHeaderKey2)) {
const s32 key_index = static_cast<s32>(KeyType::NcaHeaderKey2) == key_type;
const auto key = instance.GetKey(Core::Crypto::S256KeyType::Header);
std::memcpy(dst_key, key.data() + key_index * 0x10, std::min(dst_key_size, key.size() / 2));
return;
}
const s32 key_generation =
std::max(key_type / NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount, 1) - 1;
const s32 key_index = key_type % NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount;
Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(
instance.GetKey(Core::Crypto::S128KeyType::KeyArea, key_generation, key_index),
Core::Crypto::Mode::ECB);
cipher.Transcode(reinterpret_cast<const u8*>(src_key), src_key_size,
reinterpret_cast<u8*>(dst_key), Core::Crypto::Op::Decrypt);
}
} // namespace
const NcaCryptoConfiguration& GetCryptoConfiguration() {
static const NcaCryptoConfiguration configuration = {
.header_1_sign_key_moduli{},
.header_1_sign_key_public_exponent{},
.key_area_encryption_key_source{},
.header_encryption_key_source{},
.header_encrypted_encryption_keys{},
.generate_key = GenerateKey,
.verify_sign1{},
.is_plaintext_header_available{},
.is_available_sw_key{},
};
return configuration;
}
} // namespace FileSys

View File

@ -1,12 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "core/file_sys/fssystem/fssystem_nca_file_system_driver.h"
namespace FileSys {
const NcaCryptoConfiguration& GetCryptoConfiguration();
}

View File

@ -1,127 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h"
#include "core/file_sys/vfs_offset.h"
namespace FileSys {
HierarchicalIntegrityVerificationStorage::HierarchicalIntegrityVerificationStorage()
: m_data_size(-1) {
for (size_t i = 0; i < MaxLayers - 1; i++) {
m_verify_storages[i] = std::make_shared<IntegrityVerificationStorage>();
}
}
Result HierarchicalIntegrityVerificationStorage::Initialize(
const HierarchicalIntegrityVerificationInformation& info,
HierarchicalStorageInformation storage, int max_data_cache_entries, int max_hash_cache_entries,
s8 buffer_level) {
// Validate preconditions.
ASSERT(IntegrityMinLayerCount <= info.max_layers && info.max_layers <= IntegrityMaxLayerCount);
// Set member variables.
m_max_layers = info.max_layers;
// Initialize the top level verification storage.
m_verify_storages[0]->Initialize(storage[HierarchicalStorageInformation::MasterStorage],
storage[HierarchicalStorageInformation::Layer1Storage],
static_cast<s64>(1) << info.info[0].block_order, HashSize,
false);
// Ensure we don't leak state if further initialization goes wrong.
ON_RESULT_FAILURE {
m_verify_storages[0]->Finalize();
m_data_size = -1;
};
// Initialize the top level buffer storage.
m_buffer_storages[0] = m_verify_storages[0];
R_UNLESS(m_buffer_storages[0] != nullptr, ResultAllocationMemoryFailedAllocateShared);
// Prepare to initialize the level storages.
s32 level = 0;
// Ensure we don't leak state if further initialization goes wrong.
ON_RESULT_FAILURE_2 {
m_verify_storages[level + 1]->Finalize();
for (; level > 0; --level) {
m_buffer_storages[level].reset();
m_verify_storages[level]->Finalize();
}
};
// Initialize the level storages.
for (; level < m_max_layers - 3; ++level) {
// Initialize the verification storage.
auto buffer_storage =
std::make_shared<OffsetVfsFile>(m_buffer_storages[level], info.info[level].size, 0);
m_verify_storages[level + 1]->Initialize(
std::move(buffer_storage), storage[level + 2],
static_cast<s64>(1) << info.info[level + 1].block_order,
static_cast<s64>(1) << info.info[level].block_order, false);
// Initialize the buffer storage.
m_buffer_storages[level + 1] = m_verify_storages[level + 1];
R_UNLESS(m_buffer_storages[level + 1] != nullptr,
ResultAllocationMemoryFailedAllocateShared);
}
// Initialize the final level storage.
{
// Initialize the verification storage.
auto buffer_storage =
std::make_shared<OffsetVfsFile>(m_buffer_storages[level], info.info[level].size, 0);
m_verify_storages[level + 1]->Initialize(
std::move(buffer_storage), storage[level + 2],
static_cast<s64>(1) << info.info[level + 1].block_order,
static_cast<s64>(1) << info.info[level].block_order, true);
// Initialize the buffer storage.
m_buffer_storages[level + 1] = m_verify_storages[level + 1];
R_UNLESS(m_buffer_storages[level + 1] != nullptr,
ResultAllocationMemoryFailedAllocateShared);
}
// Set the data size.
m_data_size = info.info[level + 1].size;
// We succeeded.
R_SUCCEED();
}
void HierarchicalIntegrityVerificationStorage::Finalize() {
if (m_data_size >= 0) {
m_data_size = 0;
for (s32 level = m_max_layers - 2; level >= 0; --level) {
m_buffer_storages[level].reset();
m_verify_storages[level]->Finalize();
}
m_data_size = -1;
}
}
size_t HierarchicalIntegrityVerificationStorage::Read(u8* buffer, size_t size,
size_t offset) const {
// Validate preconditions.
ASSERT(m_data_size >= 0);
// Succeed if zero-size.
if (size == 0) {
return size;
}
// Validate arguments.
ASSERT(buffer != nullptr);
// Read the data.
return m_buffer_storages[m_max_layers - 2]->Read(buffer, size, offset);
}
size_t HierarchicalIntegrityVerificationStorage::GetSize() const {
return m_data_size;
}
} // namespace FileSys

View File

@ -1,164 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/alignment.h"
#include "core/file_sys/fssystem/fs_i_storage.h"
#include "core/file_sys/fssystem/fs_types.h"
#include "core/file_sys/fssystem/fssystem_alignment_matching_storage.h"
#include "core/file_sys/fssystem/fssystem_integrity_verification_storage.h"
#include "core/file_sys/vfs_offset.h"
namespace FileSys {
struct HierarchicalIntegrityVerificationLevelInformation {
Int64 offset;
Int64 size;
s32 block_order;
std::array<u8, 4> reserved;
};
static_assert(std::is_trivial_v<HierarchicalIntegrityVerificationLevelInformation>);
static_assert(sizeof(HierarchicalIntegrityVerificationLevelInformation) == 0x18);
static_assert(alignof(HierarchicalIntegrityVerificationLevelInformation) == 0x4);
struct HierarchicalIntegrityVerificationInformation {
u32 max_layers;
std::array<HierarchicalIntegrityVerificationLevelInformation, IntegrityMaxLayerCount - 1> info;
HashSalt seed;
s64 GetLayeredHashSize() const {
return this->info[this->max_layers - 2].offset;
}
s64 GetDataOffset() const {
return this->info[this->max_layers - 2].offset;
}
s64 GetDataSize() const {
return this->info[this->max_layers - 2].size;
}
};
static_assert(std::is_trivial_v<HierarchicalIntegrityVerificationInformation>);
struct HierarchicalIntegrityVerificationMetaInformation {
u32 magic;
u32 version;
u32 master_hash_size;
HierarchicalIntegrityVerificationInformation level_hash_info;
};
static_assert(std::is_trivial_v<HierarchicalIntegrityVerificationMetaInformation>);
struct HierarchicalIntegrityVerificationSizeSet {
s64 control_size;
s64 master_hash_size;
std::array<s64, IntegrityMaxLayerCount - 2> layered_hash_sizes;
};
static_assert(std::is_trivial_v<HierarchicalIntegrityVerificationSizeSet>);
class HierarchicalIntegrityVerificationStorage : public IReadOnlyStorage {
YUZU_NON_COPYABLE(HierarchicalIntegrityVerificationStorage);
YUZU_NON_MOVEABLE(HierarchicalIntegrityVerificationStorage);
public:
using GenerateRandomFunction = void (*)(void* dst, size_t size);
class HierarchicalStorageInformation {
public:
enum {
MasterStorage = 0,
Layer1Storage = 1,
Layer2Storage = 2,
Layer3Storage = 3,
Layer4Storage = 4,
Layer5Storage = 5,
DataStorage = 6,
};
private:
std::array<VirtualFile, DataStorage + 1> m_storages;
public:
void SetMasterHashStorage(VirtualFile s) {
m_storages[MasterStorage] = s;
}
void SetLayer1HashStorage(VirtualFile s) {
m_storages[Layer1Storage] = s;
}
void SetLayer2HashStorage(VirtualFile s) {
m_storages[Layer2Storage] = s;
}
void SetLayer3HashStorage(VirtualFile s) {
m_storages[Layer3Storage] = s;
}
void SetLayer4HashStorage(VirtualFile s) {
m_storages[Layer4Storage] = s;
}
void SetLayer5HashStorage(VirtualFile s) {
m_storages[Layer5Storage] = s;
}
void SetDataStorage(VirtualFile s) {
m_storages[DataStorage] = s;
}
VirtualFile& operator[](s32 index) {
ASSERT(MasterStorage <= index && index <= DataStorage);
return m_storages[index];
}
};
public:
HierarchicalIntegrityVerificationStorage();
virtual ~HierarchicalIntegrityVerificationStorage() override {
this->Finalize();
}
Result Initialize(const HierarchicalIntegrityVerificationInformation& info,
HierarchicalStorageInformation storage, int max_data_cache_entries,
int max_hash_cache_entries, s8 buffer_level);
void Finalize();
virtual size_t Read(u8* buffer, size_t size, size_t offset) const override;
virtual size_t GetSize() const override;
bool IsInitialized() const {
return m_data_size >= 0;
}
s64 GetL1HashVerificationBlockSize() const {
return m_verify_storages[m_max_layers - 2]->GetBlockSize();
}
VirtualFile GetL1HashStorage() {
return std::make_shared<OffsetVfsFile>(
m_buffer_storages[m_max_layers - 3],
Common::DivideUp(m_data_size, this->GetL1HashVerificationBlockSize()), 0);
}
public:
static constexpr s8 GetDefaultDataCacheBufferLevel(u32 max_layers) {
return static_cast<s8>(16 + max_layers - 2);
}
protected:
static constexpr s64 HashSize = 256 / 8;
static constexpr size_t MaxLayers = IntegrityMaxLayerCount;
private:
static GenerateRandomFunction s_generate_random;
static void SetGenerateRandomFunction(GenerateRandomFunction func) {
s_generate_random = func;
}
private:
friend struct HierarchicalIntegrityVerificationMetaInformation;
private:
std::array<std::shared_ptr<IntegrityVerificationStorage>, MaxLayers - 1> m_verify_storages;
std::array<VirtualFile, MaxLayers - 1> m_buffer_storages;
s64 m_data_size;
s32 m_max_layers;
};
} // namespace FileSys

View File

@ -1,80 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/alignment.h"
#include "common/scope_exit.h"
#include "core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.h"
namespace FileSys {
namespace {
s32 Log2(s32 value) {
ASSERT(value > 0);
ASSERT(Common::IsPowerOfTwo(value));
s32 log = 0;
while ((value >>= 1) > 0) {
++log;
}
return log;
}
} // namespace
Result HierarchicalSha256Storage::Initialize(VirtualFile* base_storages, s32 layer_count,
size_t htbs, void* hash_buf, size_t hash_buf_size) {
// Validate preconditions.
ASSERT(layer_count == LayerCount);
ASSERT(Common::IsPowerOfTwo(htbs));
ASSERT(hash_buf != nullptr);
// Set size tracking members.
m_hash_target_block_size = static_cast<s32>(htbs);
m_log_size_ratio = Log2(m_hash_target_block_size / HashSize);
// Get the base storage size.
m_base_storage_size = base_storages[2]->GetSize();
{
auto size_guard = SCOPE_GUARD({ m_base_storage_size = 0; });
R_UNLESS(m_base_storage_size <= static_cast<s64>(HashSize)
<< m_log_size_ratio << m_log_size_ratio,
ResultHierarchicalSha256BaseStorageTooLarge);
size_guard.Cancel();
}
// Set hash buffer tracking members.
m_base_storage = base_storages[2];
m_hash_buffer = static_cast<char*>(hash_buf);
m_hash_buffer_size = hash_buf_size;
// Read the master hash.
std::array<u8, HashSize> master_hash{};
base_storages[0]->ReadObject(std::addressof(master_hash));
// Read and validate the data being hashed.
s64 hash_storage_size = base_storages[1]->GetSize();
ASSERT(Common::IsAligned(hash_storage_size, HashSize));
ASSERT(hash_storage_size <= m_hash_target_block_size);
ASSERT(hash_storage_size <= static_cast<s64>(m_hash_buffer_size));
base_storages[1]->Read(reinterpret_cast<u8*>(m_hash_buffer),
static_cast<size_t>(hash_storage_size), 0);
R_SUCCEED();
}
size_t HierarchicalSha256Storage::Read(u8* buffer, size_t size, size_t offset) const {
// Succeed if zero-size.
if (size == 0) {
return size;
}
// Validate that we have a buffer to read into.
ASSERT(buffer != nullptr);
// Read the data.
return m_base_storage->Read(buffer, size, offset);
}
} // namespace FileSys

View File

@ -1,44 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <mutex>
#include "core/file_sys/errors.h"
#include "core/file_sys/fssystem/fs_i_storage.h"
#include "core/file_sys/vfs.h"
namespace FileSys {
class HierarchicalSha256Storage : public IReadOnlyStorage {
YUZU_NON_COPYABLE(HierarchicalSha256Storage);
YUZU_NON_MOVEABLE(HierarchicalSha256Storage);
public:
static constexpr s32 LayerCount = 3;
static constexpr size_t HashSize = 256 / 8;
public:
HierarchicalSha256Storage() : m_mutex() {}
Result Initialize(VirtualFile* base_storages, s32 layer_count, size_t htbs, void* hash_buf,
size_t hash_buf_size);
virtual size_t GetSize() const override {
return m_base_storage->GetSize();
}
virtual size_t Read(u8* buffer, size_t length, size_t offset) const override;
private:
VirtualFile m_base_storage;
s64 m_base_storage_size;
char* m_hash_buffer;
size_t m_hash_buffer_size;
s32 m_hash_target_block_size;
s32 m_log_size_ratio;
std::mutex m_mutex;
};
} // namespace FileSys

View File

@ -1,119 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/file_sys/errors.h"
#include "core/file_sys/fssystem/fssystem_indirect_storage.h"
namespace FileSys {
Result IndirectStorage::Initialize(VirtualFile table_storage) {
// Read and verify the bucket tree header.
BucketTree::Header header;
table_storage->ReadObject(std::addressof(header));
R_TRY(header.Verify());
// Determine extents.
const auto node_storage_size = QueryNodeStorageSize(header.entry_count);
const auto entry_storage_size = QueryEntryStorageSize(header.entry_count);
const auto node_storage_offset = QueryHeaderStorageSize();
const auto entry_storage_offset = node_storage_offset + node_storage_size;
// Initialize.
R_RETURN(this->Initialize(
std::make_shared<OffsetVfsFile>(table_storage, node_storage_size, node_storage_offset),
std::make_shared<OffsetVfsFile>(table_storage, entry_storage_size, entry_storage_offset),
header.entry_count));
}
void IndirectStorage::Finalize() {
if (this->IsInitialized()) {
m_table.Finalize();
for (auto i = 0; i < StorageCount; i++) {
m_data_storage[i] = VirtualFile();
}
}
}
Result IndirectStorage::GetEntryList(Entry* out_entries, s32* out_entry_count, s32 entry_count,
s64 offset, s64 size) {
// Validate pre-conditions.
ASSERT(offset >= 0);
ASSERT(size >= 0);
ASSERT(this->IsInitialized());
// Clear the out count.
R_UNLESS(out_entry_count != nullptr, ResultNullptrArgument);
*out_entry_count = 0;
// Succeed if there's no range.
R_SUCCEED_IF(size == 0);
// If we have an output array, we need it to be non-null.
R_UNLESS(out_entries != nullptr || entry_count == 0, ResultNullptrArgument);
// Check that our range is valid.
BucketTree::Offsets table_offsets;
R_TRY(m_table.GetOffsets(std::addressof(table_offsets)));
R_UNLESS(table_offsets.IsInclude(offset, size), ResultOutOfRange);
// Find the offset in our tree.
BucketTree::Visitor visitor;
R_TRY(m_table.Find(std::addressof(visitor), offset));
{
const auto entry_offset = visitor.Get<Entry>()->GetVirtualOffset();
R_UNLESS(0 <= entry_offset && table_offsets.IsInclude(entry_offset),
ResultInvalidIndirectEntryOffset);
}
// Prepare to loop over entries.
const auto end_offset = offset + static_cast<s64>(size);
s32 count = 0;
auto cur_entry = *visitor.Get<Entry>();
while (cur_entry.GetVirtualOffset() < end_offset) {
// Try to write the entry to the out list.
if (entry_count != 0) {
if (count >= entry_count) {
break;
}
std::memcpy(out_entries + count, std::addressof(cur_entry), sizeof(Entry));
}
count++;
// Advance.
if (visitor.CanMoveNext()) {
R_TRY(visitor.MoveNext());
cur_entry = *visitor.Get<Entry>();
} else {
break;
}
}
// Write the output count.
*out_entry_count = count;
R_SUCCEED();
}
size_t IndirectStorage::Read(u8* buffer, size_t size, size_t offset) const {
// Validate pre-conditions.
ASSERT(this->IsInitialized());
ASSERT(buffer != nullptr);
// Succeed if there's nothing to read.
if (size == 0) {
return 0;
}
const_cast<IndirectStorage*>(this)->OperatePerEntry<true, true>(
offset, size,
[=](VirtualFile storage, s64 data_offset, s64 cur_offset, s64 cur_size) -> Result {
storage->Read(reinterpret_cast<u8*>(buffer) + (cur_offset - offset),
static_cast<size_t>(cur_size), data_offset);
R_SUCCEED();
});
return size;
}
} // namespace FileSys

View File

@ -1,294 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "core/file_sys/errors.h"
#include "core/file_sys/fssystem/fs_i_storage.h"
#include "core/file_sys/fssystem/fssystem_bucket_tree.h"
#include "core/file_sys/fssystem/fssystem_bucket_tree_template_impl.h"
#include "core/file_sys/vfs.h"
#include "core/file_sys/vfs_offset.h"
namespace FileSys {
class IndirectStorage : public IReadOnlyStorage {
YUZU_NON_COPYABLE(IndirectStorage);
YUZU_NON_MOVEABLE(IndirectStorage);
public:
static constexpr s32 StorageCount = 2;
static constexpr size_t NodeSize = 16_KiB;
struct Entry {
std::array<u8, sizeof(s64)> virt_offset;
std::array<u8, sizeof(s64)> phys_offset;
s32 storage_index;
void SetVirtualOffset(const s64& ofs) {
std::memcpy(this->virt_offset.data(), std::addressof(ofs), sizeof(s64));
}
s64 GetVirtualOffset() const {
s64 offset;
std::memcpy(std::addressof(offset), this->virt_offset.data(), sizeof(s64));
return offset;
}
void SetPhysicalOffset(const s64& ofs) {
std::memcpy(this->phys_offset.data(), std::addressof(ofs), sizeof(s64));
}
s64 GetPhysicalOffset() const {
s64 offset;
std::memcpy(std::addressof(offset), this->phys_offset.data(), sizeof(s64));
return offset;
}
};
static_assert(std::is_trivial_v<Entry>);
static_assert(sizeof(Entry) == 0x14);
struct EntryData {
s64 virt_offset;
s64 phys_offset;
s32 storage_index;
void Set(const Entry& entry) {
this->virt_offset = entry.GetVirtualOffset();
this->phys_offset = entry.GetPhysicalOffset();
this->storage_index = entry.storage_index;
}
};
static_assert(std::is_trivial_v<EntryData>);
public:
IndirectStorage() : m_table(), m_data_storage() {}
virtual ~IndirectStorage() {
this->Finalize();
}
Result Initialize(VirtualFile table_storage);
void Finalize();
bool IsInitialized() const {
return m_table.IsInitialized();
}
Result Initialize(VirtualFile node_storage, VirtualFile entry_storage, s32 entry_count) {
R_RETURN(
m_table.Initialize(node_storage, entry_storage, NodeSize, sizeof(Entry), entry_count));
}
void SetStorage(s32 idx, VirtualFile storage) {
ASSERT(0 <= idx && idx < StorageCount);
m_data_storage[idx] = storage;
}
template <typename T>
void SetStorage(s32 idx, T storage, s64 offset, s64 size) {
ASSERT(0 <= idx && idx < StorageCount);
m_data_storage[idx] = std::make_shared<OffsetVfsFile>(storage, size, offset);
}
Result GetEntryList(Entry* out_entries, s32* out_entry_count, s32 entry_count, s64 offset,
s64 size);
virtual size_t GetSize() const override {
BucketTree::Offsets offsets{};
m_table.GetOffsets(std::addressof(offsets));
return offsets.end_offset;
}
virtual size_t Read(u8* buffer, size_t size, size_t offset) const override;
public:
static constexpr s64 QueryHeaderStorageSize() {
return BucketTree::QueryHeaderStorageSize();
}
static constexpr s64 QueryNodeStorageSize(s32 entry_count) {
return BucketTree::QueryNodeStorageSize(NodeSize, sizeof(Entry), entry_count);
}
static constexpr s64 QueryEntryStorageSize(s32 entry_count) {
return BucketTree::QueryEntryStorageSize(NodeSize, sizeof(Entry), entry_count);
}
protected:
BucketTree& GetEntryTable() {
return m_table;
}
VirtualFile& GetDataStorage(s32 index) {
ASSERT(0 <= index && index < StorageCount);
return m_data_storage[index];
}
template <bool ContinuousCheck, bool RangeCheck, typename F>
Result OperatePerEntry(s64 offset, s64 size, F func);
private:
struct ContinuousReadingEntry {
static constexpr size_t FragmentSizeMax = 4_KiB;
IndirectStorage::Entry entry;
s64 GetVirtualOffset() const {
return this->entry.GetVirtualOffset();
}
s64 GetPhysicalOffset() const {
return this->entry.GetPhysicalOffset();
}
bool IsFragment() const {
return this->entry.storage_index != 0;
}
};
static_assert(std::is_trivial_v<ContinuousReadingEntry>);
private:
mutable BucketTree m_table;
std::array<VirtualFile, StorageCount> m_data_storage;
};
template <bool ContinuousCheck, bool RangeCheck, typename F>
Result IndirectStorage::OperatePerEntry(s64 offset, s64 size, F func) {
// Validate preconditions.
ASSERT(offset >= 0);
ASSERT(size >= 0);
ASSERT(this->IsInitialized());
// Succeed if there's nothing to operate on.
R_SUCCEED_IF(size == 0);
// Get the table offsets.
BucketTree::Offsets table_offsets;
R_TRY(m_table.GetOffsets(std::addressof(table_offsets)));
// Validate arguments.
R_UNLESS(table_offsets.IsInclude(offset, size), ResultOutOfRange);
// Find the offset in our tree.
BucketTree::Visitor visitor;
R_TRY(m_table.Find(std::addressof(visitor), offset));
{
const auto entry_offset = visitor.Get<Entry>()->GetVirtualOffset();
R_UNLESS(0 <= entry_offset && table_offsets.IsInclude(entry_offset),
ResultInvalidIndirectEntryOffset);
}
// Prepare to operate in chunks.
auto cur_offset = offset;
const auto end_offset = offset + static_cast<s64>(size);
BucketTree::ContinuousReadingInfo cr_info;
while (cur_offset < end_offset) {
// Get the current entry.
const auto cur_entry = *visitor.Get<Entry>();
// Get and validate the entry's offset.
const auto cur_entry_offset = cur_entry.GetVirtualOffset();
R_UNLESS(cur_entry_offset <= cur_offset, ResultInvalidIndirectEntryOffset);
// Validate the storage index.
R_UNLESS(0 <= cur_entry.storage_index && cur_entry.storage_index < StorageCount,
ResultInvalidIndirectEntryStorageIndex);
// If we need to check the continuous info, do so.
if constexpr (ContinuousCheck) {
// Scan, if we need to.
if (cr_info.CheckNeedScan()) {
R_TRY(visitor.ScanContinuousReading<ContinuousReadingEntry>(
std::addressof(cr_info), cur_offset,
static_cast<size_t>(end_offset - cur_offset)));
}
// Process a base storage entry.
if (cr_info.CanDo()) {
// Ensure that we can process.
R_UNLESS(cur_entry.storage_index == 0, ResultInvalidIndirectEntryStorageIndex);
// Ensure that we remain within range.
const auto data_offset = cur_offset - cur_entry_offset;
const auto cur_entry_phys_offset = cur_entry.GetPhysicalOffset();
const auto cur_size = static_cast<s64>(cr_info.GetReadSize());
// If we should, verify the range.
if constexpr (RangeCheck) {
// Get the current data storage's size.
s64 cur_data_storage_size = m_data_storage[0]->GetSize();
R_UNLESS(0 <= cur_entry_phys_offset &&
cur_entry_phys_offset <= cur_data_storage_size,
ResultInvalidIndirectEntryOffset);
R_UNLESS(cur_entry_phys_offset + data_offset + cur_size <=
cur_data_storage_size,
ResultInvalidIndirectStorageSize);
}
// Operate.
R_TRY(func(m_data_storage[0], cur_entry_phys_offset + data_offset, cur_offset,
cur_size));
// Mark as done.
cr_info.Done();
}
}
// Get and validate the next entry offset.
s64 next_entry_offset;
if (visitor.CanMoveNext()) {
R_TRY(visitor.MoveNext());
next_entry_offset = visitor.Get<Entry>()->GetVirtualOffset();
R_UNLESS(table_offsets.IsInclude(next_entry_offset), ResultInvalidIndirectEntryOffset);
} else {
next_entry_offset = table_offsets.end_offset;
}
R_UNLESS(cur_offset < next_entry_offset, ResultInvalidIndirectEntryOffset);
// Get the offset of the entry in the data we read.
const auto data_offset = cur_offset - cur_entry_offset;
const auto data_size = (next_entry_offset - cur_entry_offset);
ASSERT(data_size > 0);
// Determine how much is left.
const auto remaining_size = end_offset - cur_offset;
const auto cur_size = std::min<s64>(remaining_size, data_size - data_offset);
ASSERT(cur_size <= size);
// Operate, if we need to.
bool needs_operate;
if constexpr (!ContinuousCheck) {
needs_operate = true;
} else {
needs_operate = !cr_info.IsDone() || cur_entry.storage_index != 0;
}
if (needs_operate) {
const auto cur_entry_phys_offset = cur_entry.GetPhysicalOffset();
if constexpr (RangeCheck) {
// Get the current data storage's size.
s64 cur_data_storage_size = m_data_storage[cur_entry.storage_index]->GetSize();
// Ensure that we remain within range.
R_UNLESS(0 <= cur_entry_phys_offset &&
cur_entry_phys_offset <= cur_data_storage_size,
ResultIndirectStorageCorrupted);
R_UNLESS(cur_entry_phys_offset + data_offset + cur_size <= cur_data_storage_size,
ResultIndirectStorageCorrupted);
}
R_TRY(func(m_data_storage[cur_entry.storage_index], cur_entry_phys_offset + data_offset,
cur_offset, cur_size));
}
cur_offset += cur_size;
}
R_SUCCEED();
}
} // namespace FileSys

View File

@ -1,30 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/file_sys/fssystem/fssystem_integrity_romfs_storage.h"
namespace FileSys {
Result IntegrityRomFsStorage::Initialize(
HierarchicalIntegrityVerificationInformation level_hash_info, Hash master_hash,
HierarchicalIntegrityVerificationStorage::HierarchicalStorageInformation storage_info,
int max_data_cache_entries, int max_hash_cache_entries, s8 buffer_level) {
// Set master hash.
m_master_hash = master_hash;
m_master_hash_storage = std::make_shared<ArrayVfsFile<sizeof(Hash)>>(m_master_hash.value);
R_UNLESS(m_master_hash_storage != nullptr,
ResultAllocationMemoryFailedInIntegrityRomFsStorageA);
// Set the master hash storage.
storage_info[0] = m_master_hash_storage;
// Initialize our integrity storage.
R_RETURN(m_integrity_storage.Initialize(level_hash_info, storage_info, max_data_cache_entries,
max_hash_cache_entries, buffer_level));
}
void IntegrityRomFsStorage::Finalize() {
m_integrity_storage.Finalize();
}
} // namespace FileSys

View File

@ -1,42 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h"
#include "core/file_sys/fssystem/fssystem_nca_header.h"
#include "core/file_sys/vfs_vector.h"
namespace FileSys {
constexpr inline size_t IntegrityLayerCountRomFs = 7;
constexpr inline size_t IntegrityHashLayerBlockSize = 16_KiB;
class IntegrityRomFsStorage : public IReadOnlyStorage {
public:
IntegrityRomFsStorage() {}
virtual ~IntegrityRomFsStorage() override {
this->Finalize();
}
Result Initialize(
HierarchicalIntegrityVerificationInformation level_hash_info, Hash master_hash,
HierarchicalIntegrityVerificationStorage::HierarchicalStorageInformation storage_info,
int max_data_cache_entries, int max_hash_cache_entries, s8 buffer_level);
void Finalize();
virtual size_t Read(u8* buffer, size_t size, size_t offset) const override {
return m_integrity_storage.Read(buffer, size, offset);
}
virtual size_t GetSize() const override {
return m_integrity_storage.GetSize();
}
private:
HierarchicalIntegrityVerificationStorage m_integrity_storage;
Hash m_master_hash;
std::shared_ptr<ArrayVfsFile<sizeof(Hash)>> m_master_hash_storage;
};
} // namespace FileSys

View File

@ -1,91 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/alignment.h"
#include "core/file_sys/fssystem/fssystem_integrity_verification_storage.h"
namespace FileSys {
constexpr inline u32 ILog2(u32 val) {
ASSERT(val > 0);
return static_cast<u32>((sizeof(u32) * 8) - 1 - std::countl_zero<u32>(val));
}
void IntegrityVerificationStorage::Initialize(VirtualFile hs, VirtualFile ds, s64 verif_block_size,
s64 upper_layer_verif_block_size, bool is_real_data) {
// Validate preconditions.
ASSERT(verif_block_size >= HashSize);
// Set storages.
m_hash_storage = hs;
m_data_storage = ds;
// Set verification block sizes.
m_verification_block_size = verif_block_size;
m_verification_block_order = ILog2(static_cast<u32>(verif_block_size));
ASSERT(m_verification_block_size == 1ll << m_verification_block_order);
// Set upper layer block sizes.
upper_layer_verif_block_size = std::max(upper_layer_verif_block_size, HashSize);
m_upper_layer_verification_block_size = upper_layer_verif_block_size;
m_upper_layer_verification_block_order = ILog2(static_cast<u32>(upper_layer_verif_block_size));
ASSERT(m_upper_layer_verification_block_size == 1ll << m_upper_layer_verification_block_order);
// Validate sizes.
{
s64 hash_size = m_hash_storage->GetSize();
s64 data_size = m_data_storage->GetSize();
ASSERT(((hash_size / HashSize) * m_verification_block_size) >= data_size);
}
// Set data.
m_is_real_data = is_real_data;
}
void IntegrityVerificationStorage::Finalize() {
m_hash_storage = VirtualFile();
m_data_storage = VirtualFile();
}
size_t IntegrityVerificationStorage::Read(u8* buffer, size_t size, size_t offset) const {
// Succeed if zero size.
if (size == 0) {
return size;
}
// Validate arguments.
ASSERT(buffer != nullptr);
// Validate the offset.
s64 data_size = m_data_storage->GetSize();
ASSERT(offset <= static_cast<size_t>(data_size));
// Validate the access range.
ASSERT(R_SUCCEEDED(IStorage::CheckAccessRange(
offset, size, Common::AlignUp(data_size, static_cast<size_t>(m_verification_block_size)))));
// Determine the read extents.
size_t read_size = size;
if (static_cast<s64>(offset + read_size) > data_size) {
// Determine the padding sizes.
s64 padding_offset = data_size - offset;
size_t padding_size = static_cast<size_t>(
m_verification_block_size - (padding_offset & (m_verification_block_size - 1)));
ASSERT(static_cast<s64>(padding_size) < m_verification_block_size);
// Clear the padding.
std::memset(static_cast<u8*>(buffer) + padding_offset, 0, padding_size);
// Set the new in-bounds size.
read_size = static_cast<size_t>(data_size - offset);
}
// Perform the read.
return m_data_storage->Read(buffer, read_size, offset);
}
size_t IntegrityVerificationStorage::GetSize() const {
return m_data_storage->GetSize();
}
} // namespace FileSys

View File

@ -1,65 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <optional>
#include "core/file_sys/fssystem/fs_i_storage.h"
#include "core/file_sys/fssystem/fs_types.h"
namespace FileSys {
class IntegrityVerificationStorage : public IReadOnlyStorage {
YUZU_NON_COPYABLE(IntegrityVerificationStorage);
YUZU_NON_MOVEABLE(IntegrityVerificationStorage);
public:
static constexpr s64 HashSize = 256 / 8;
struct BlockHash {
std::array<u8, HashSize> hash;
};
static_assert(std::is_trivial_v<BlockHash>);
public:
IntegrityVerificationStorage()
: m_verification_block_size(0), m_verification_block_order(0),
m_upper_layer_verification_block_size(0), m_upper_layer_verification_block_order(0) {}
virtual ~IntegrityVerificationStorage() override {
this->Finalize();
}
void Initialize(VirtualFile hs, VirtualFile ds, s64 verif_block_size,
s64 upper_layer_verif_block_size, bool is_real_data);
void Finalize();
virtual size_t Read(u8* buffer, size_t size, size_t offset) const override;
virtual size_t GetSize() const override;
s64 GetBlockSize() const {
return m_verification_block_size;
}
private:
static void SetValidationBit(BlockHash* hash) {
ASSERT(hash != nullptr);
hash->hash[HashSize - 1] |= 0x80;
}
static bool IsValidationBit(const BlockHash* hash) {
ASSERT(hash != nullptr);
return (hash->hash[HashSize - 1] & 0x80) != 0;
}
private:
VirtualFile m_hash_storage;
VirtualFile m_data_storage;
s64 m_verification_block_size;
s64 m_verification_block_order;
s64 m_upper_layer_verification_block_size;
s64 m_upper_layer_verification_block_order;
bool m_is_real_data;
};
} // namespace FileSys

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