diff options
Diffstat (limited to '')
178 files changed, 9983 insertions, 2140 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2da983cad..7bb88c8ea 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -134,7 +134,7 @@ else() endif() # GCC bugs - if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "12" AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "11" AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") # These diagnostics would be great if they worked, but are just completely broken # and produce bogus errors on external libraries like fmt. add_compile_options( diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/LicenseAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/LicenseAdapter.kt index 7006651d0..bc6ff1364 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/LicenseAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/LicenseAdapter.kt @@ -49,6 +49,7 @@ class LicenseAdapter(private val activity: AppCompatActivity, var licenses: List val context = YuzuApplication.appContext binding.textSettingName.text = context.getString(license.titleId) binding.textSettingDescription.text = context.getString(license.descriptionId) + binding.textSettingValue.visibility = View.GONE } } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/SetupAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/SetupAdapter.kt index 481ddd5a5..6b46d359e 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/SetupAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/SetupAdapter.kt @@ -5,13 +5,19 @@ package org.yuzu.yuzu_emu.adapters import android.text.Html import android.view.LayoutInflater +import android.view.View import android.view.ViewGroup import androidx.appcompat.app.AppCompatActivity import androidx.core.content.res.ResourcesCompat +import androidx.lifecycle.ViewModelProvider import androidx.recyclerview.widget.RecyclerView import com.google.android.material.button.MaterialButton 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.StepState +import org.yuzu.yuzu_emu.utils.ViewUtils class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>) : RecyclerView.Adapter<SetupAdapter.SetupPageViewHolder>() { @@ -26,7 +32,7 @@ class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>) holder.bind(pages[position]) inner class SetupPageViewHolder(val binding: PageSetupBinding) : - RecyclerView.ViewHolder(binding.root) { + RecyclerView.ViewHolder(binding.root), SetupCallback { lateinit var page: SetupPage init { @@ -35,6 +41,12 @@ class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>) fun bind(page: SetupPage) { this.page = page + + if (page.stepCompleted.invoke() == StepState.COMPLETE) { + binding.buttonAction.visibility = View.INVISIBLE + binding.textConfirmation.visibility = View.VISIBLE + } + binding.icon.setImageDrawable( ResourcesCompat.getDrawable( activity.resources, @@ -62,9 +74,15 @@ class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>) MaterialButton.ICON_GRAVITY_END } setOnClickListener { - page.buttonAction.invoke() + page.buttonAction.invoke(this@SetupPageViewHolder) } } } + + override fun onStepCompleted() { + ViewUtils.hideView(binding.buttonAction, 200) + ViewUtils.showView(binding.textConfirmation, 200) + ViewModelProvider(activity)[HomeViewModel::class.java].setShouldPageForward(true) + } } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt index ce0b92c90..9711e2c51 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt @@ -207,8 +207,11 @@ class SettingsAdapter( val sliderBinding = DialogSliderBinding.inflate(inflater) textSliderValue = sliderBinding.textValue - textSliderValue!!.text = sliderProgress.toString() - sliderBinding.textUnits.text = item.units + textSliderValue!!.text = String.format( + context.getString(R.string.value_with_units), + sliderProgress.toString(), + item.units + ) sliderBinding.slider.apply { valueFrom = item.min.toFloat() @@ -216,7 +219,11 @@ class SettingsAdapter( value = sliderProgress.toFloat() addOnChangeListener { _: Slider, value: Float, _: Boolean -> sliderProgress = value.toInt() - textSliderValue!!.text = sliderProgress.toString() + textSliderValue!!.text = String.format( + context.getString(R.string.value_with_units), + sliderProgress.toString(), + item.units + ) } } @@ -225,10 +232,6 @@ class SettingsAdapter( .setView(sliderBinding.root) .setPositiveButton(android.R.string.ok, this) .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() } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt index 7955532ee..79572fc06 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt @@ -25,12 +25,17 @@ class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA binding.textSettingDescription.setText(item.descriptionId) binding.textSettingDescription.visibility = View.VISIBLE } else { - val epochTime = setting.value.toLong() - val instant = Instant.ofEpochMilli(epochTime * 1000) - val zonedTime = ZonedDateTime.ofInstant(instant, ZoneId.of("UTC")) - val dateFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM) - binding.textSettingDescription.text = dateFormatter.format(zonedTime) + binding.textSettingDescription.visibility = View.GONE } + + binding.textSettingValue.visibility = View.VISIBLE + val epochTime = setting.value.toLong() + val instant = Instant.ofEpochMilli(epochTime * 1000) + val zonedTime = ZonedDateTime.ofInstant(instant, ZoneId.of("UTC")) + val dateFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM) + binding.textSettingValue.text = dateFormatter.format(zonedTime) + + setStyle(setting.isEditable, binding) } override fun onClick(clicked: View) { diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/RunnableViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/RunnableViewHolder.kt index 5dad5945f..83a2e94f1 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/RunnableViewHolder.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/RunnableViewHolder.kt @@ -23,6 +23,9 @@ class RunnableViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA } else { binding.textSettingDescription.visibility = View.GONE } + binding.textSettingValue.visibility = View.GONE + + setStyle(setting.isEditable, binding) } override fun onClick(clicked: View) { diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SettingViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SettingViewHolder.kt index f56460893..0fd1d2eaa 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SettingViewHolder.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SettingViewHolder.kt @@ -5,6 +5,8 @@ package org.yuzu.yuzu_emu.features.settings.ui.viewholder import android.view.View 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.ui.SettingsAdapter @@ -33,4 +35,18 @@ abstract class SettingViewHolder(itemView: View, protected val adapter: Settings abstract override fun onClick(clicked: View) 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 + } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt index e4e321bd3..b42d955aa 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt @@ -17,28 +17,33 @@ class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: Setti override fun bind(item: SettingsItem) { setting = item binding.textSettingName.setText(item.nameId) - binding.textSettingDescription.visibility = View.VISIBLE if (item.descriptionId != 0) { binding.textSettingDescription.setText(item.descriptionId) - } else if (item is SingleChoiceSetting) { - val resMgr = binding.textSettingDescription.context.resources + binding.textSettingDescription.visibility = View.VISIBLE + } else { + 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) for (i in values.indices) { if (values[i] == item.selectedValue) { - binding.textSettingDescription.text = resMgr.getStringArray(item.choicesId)[i] - return + binding.textSettingValue.text = resMgr.getStringArray(item.choicesId)[i] + break } } } else if (item is StringSingleChoiceSetting) { for (i in item.values!!.indices) { if (item.values[i] == item.selectedValue) { - binding.textSettingDescription.text = item.choices[i] - return + binding.textSettingValue.text = item.choices[i] + break } } - } else { - binding.textSettingDescription.visibility = View.GONE } + + setStyle(setting.isEditable, binding) } override fun onClick(clicked: View) { diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SliderViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SliderViewHolder.kt index cc3f39aa5..a23b5d109 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SliderViewHolder.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SliderViewHolder.kt @@ -4,6 +4,7 @@ package org.yuzu.yuzu_emu.features.settings.ui.viewholder import android.view.View +import org.yuzu.yuzu_emu.R 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.SliderSetting @@ -22,6 +23,14 @@ class SliderViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAda } else { 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) { diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SubmenuViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SubmenuViewHolder.kt index c545b4174..1cf581a9d 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SubmenuViewHolder.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SubmenuViewHolder.kt @@ -22,6 +22,7 @@ class SubmenuViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAd } else { binding.textSettingDescription.visibility = View.GONE } + binding.textSettingValue.visibility = View.GONE } override fun onClick(clicked: View) { diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt index 54f531795..ef34bf5f4 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt @@ -25,12 +25,12 @@ class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter binding.textSettingDescription.text = "" binding.textSettingDescription.visibility = View.GONE } - binding.switchWidget.isChecked = setting.isChecked binding.switchWidget.setOnCheckedChangeListener { _: CompoundButton, _: Boolean -> adapter.onBooleanClick(item, bindingAdapterPosition, binding.switchWidget.isChecked) } + binding.switchWidget.isChecked = setting.isChecked - binding.switchWidget.isEnabled = setting.isEditable + setStyle(setting.isEditable, binding) } override fun onClick(clicked: View) { diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt index 0e7c1ba88..25b9d4018 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt @@ -297,11 +297,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { emulationActivity?.let { it.requestedOrientation = when (IntSetting.RENDERER_SCREEN_LAYOUT.int) { Settings.LayoutOption_MobileLandscape -> - ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE + ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE Settings.LayoutOption_MobilePortrait -> ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT Settings.LayoutOption_Unspecified -> ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED - else -> ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE + else -> ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE } } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt index 6c4ddaf6b..d50c421a0 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt @@ -19,6 +19,7 @@ import androidx.core.content.ContextCompat import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import androidx.core.view.isVisible +import androidx.core.view.updatePadding import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.navigation.findNavController @@ -32,10 +33,13 @@ import org.yuzu.yuzu_emu.adapters.SetupAdapter import org.yuzu.yuzu_emu.databinding.FragmentSetupBinding import org.yuzu.yuzu_emu.features.settings.model.Settings 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.StepState import org.yuzu.yuzu_emu.ui.main.MainActivity import org.yuzu.yuzu_emu.utils.DirectoryInitialization import org.yuzu.yuzu_emu.utils.GameHelper +import org.yuzu.yuzu_emu.utils.ViewUtils class SetupFragment : Fragment() { private var _binding: FragmentSetupBinding? = null @@ -112,14 +116,22 @@ class SetupFragment : Fragment() { 0, false, R.string.give_permission, - { permissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) }, + { + notificationCallback = it + permissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) + }, true, R.string.notification_warning, R.string.notification_warning_description, 0, { - NotificationManagerCompat.from(requireContext()) + if (NotificationManagerCompat.from(requireContext()) .areNotificationsEnabled() + ) { + StepState.COMPLETE + } else { + StepState.INCOMPLETE + } } ) ) @@ -133,12 +145,22 @@ class SetupFragment : Fragment() { R.drawable.ic_add, true, R.string.select_keys, - { mainActivity.getProdKey.launch(arrayOf("*/*")) }, + { + keyCallback = it + getProdKey.launch(arrayOf("*/*")) + }, true, R.string.install_prod_keys_warning, R.string.install_prod_keys_warning_description, 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( @@ -150,9 +172,8 @@ class SetupFragment : Fragment() { true, R.string.add_games, { - mainActivity.getGamesDirectory.launch( - Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data - ) + gamesDirCallback = it + getGamesDirectory.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data) }, true, R.string.add_games_warning, @@ -163,7 +184,11 @@ class SetupFragment : Fragment() { PreferenceManager.getDefaultSharedPreferences( YuzuApplication.appContext ) - preferences.getString(GameHelper.KEY_GAME_PATH, "")!!.isNotEmpty() + if (preferences.getString(GameHelper.KEY_GAME_PATH, "")!!.isNotEmpty()) { + StepState.COMPLETE + } else { + StepState.INCOMPLETE + } } ) ) @@ -181,6 +206,13 @@ class SetupFragment : Fragment() { ) } + homeViewModel.shouldPageForward.observe(viewLifecycleOwner) { + if (it) { + pageForward() + homeViewModel.setShouldPageForward(false) + } + } + binding.viewPager2.apply { adapter = SetupAdapter(requireActivity() as AppCompatActivity, pages) offscreenPageLimit = 2 @@ -194,15 +226,15 @@ class SetupFragment : Fragment() { super.onPageSelected(position) if (position == 1 && previousPosition == 0) { - showView(binding.buttonNext) - showView(binding.buttonBack) + ViewUtils.showView(binding.buttonNext) + ViewUtils.showView(binding.buttonBack) } else if (position == 0 && previousPosition == 1) { - hideView(binding.buttonBack) - hideView(binding.buttonNext) + ViewUtils.hideView(binding.buttonBack) + ViewUtils.hideView(binding.buttonNext) } else if (position == pages.size - 1 && previousPosition == pages.size - 2) { - hideView(binding.buttonNext) + ViewUtils.hideView(binding.buttonNext) } else if (position == pages.size - 2 && previousPosition == pages.size - 1) { - showView(binding.buttonNext) + ViewUtils.showView(binding.buttonNext) } previousPosition = position @@ -215,7 +247,8 @@ class SetupFragment : Fragment() { // Checks if the user has completed the task on the current page if (currentPage.hasWarning) { - if (currentPage.taskCompleted.invoke()) { + val stepState = currentPage.stepCompleted.invoke() + if (stepState != StepState.INCOMPLETE) { pageForward() return@setOnClickListener } @@ -264,9 +297,15 @@ class SetupFragment : Fragment() { _binding = null } + private lateinit var notificationCallback: SetupCallback + @RequiresApi(Build.VERSION_CODES.TIRAMISU) private val permissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { + if (it) { + notificationCallback.onStepCompleted() + } + if (!it && !shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS) ) { @@ -277,6 +316,27 @@ 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() { PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext).edit() .putBoolean(Settings.PREF_FIRST_APP_LAUNCH, false) @@ -284,33 +344,6 @@ class SetupFragment : Fragment() { 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() { binding.viewPager2.currentItem = binding.viewPager2.currentItem + 1 } @@ -326,15 +359,29 @@ class SetupFragment : Fragment() { private fun setInsets() = ViewCompat.setOnApplyWindowInsetsListener( binding.root - ) { view: View, windowInsets: WindowInsetsCompat -> + ) { _: View, windowInsets: WindowInsetsCompat -> val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) - view.setPadding( - barInsets.left + cutoutInsets.left, - barInsets.top + cutoutInsets.top, - barInsets.right + cutoutInsets.right, - barInsets.bottom + cutoutInsets.bottom - ) + + val leftPadding = barInsets.left + cutoutInsets.left + val topPadding = barInsets.top + cutoutInsets.top + val rightPadding = barInsets.right + cutoutInsets.right + val bottomPadding = 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 } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt index 263ee7144..e13d84c9c 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt @@ -14,6 +14,9 @@ class HomeViewModel : ViewModel() { private val _statusBarShadeVisible = MutableLiveData(true) val statusBarShadeVisible: LiveData<Boolean> get() = _statusBarShadeVisible + private val _shouldPageForward = MutableLiveData(false) + val shouldPageForward: LiveData<Boolean> get() = _shouldPageForward + var navigatedToSetup = false init { @@ -33,4 +36,8 @@ class HomeViewModel : ViewModel() { } _statusBarShadeVisible.value = visible } + + fun setShouldPageForward(pageForward: Boolean) { + _shouldPageForward.value = pageForward + } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SetupPage.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SetupPage.kt index a0c878e1c..09a128ae6 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SetupPage.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SetupPage.kt @@ -10,10 +10,20 @@ data class SetupPage( val buttonIconId: Int, val leftAlignedIcon: Boolean, val buttonTextId: Int, - val buttonAction: () -> Unit, + val buttonAction: (callback: SetupCallback) -> Unit, val hasWarning: Boolean, val warningTitleId: Int = 0, val warningDescriptionId: Int = 0, val warningHelpLinkId: Int = 0, - val taskCompleted: () -> Boolean = { true } + val stepCompleted: () -> StepState = { StepState.UNDEFINED } ) + +interface SetupCallback { + fun onStepCompleted() +} + +enum class StepState { + COMPLETE, + INCOMPLETE, + UNDEFINED +} diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt index f7d7aed1e..f77d06262 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt @@ -266,73 +266,80 @@ class MainActivity : AppCompatActivity(), ThemeProvider { val getGamesDirectory = registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result -> - if (result == null) { - return@registerForActivityResult + if (result != null) { + processGamesDir(result) } + } - contentResolver.takePersistableUriPermission( - result, - Intent.FLAG_GRANT_READ_URI_PERMISSION - ) + fun processGamesDir(result: Uri) { + contentResolver.takePersistableUriPermission( + result, + Intent.FLAG_GRANT_READ_URI_PERMISSION + ) - // When a new directory is picked, we currently will reset the existing games - // database. This effectively means that only one game directory is supported. - PreferenceManager.getDefaultSharedPreferences(applicationContext).edit() - .putString(GameHelper.KEY_GAME_PATH, result.toString()) - .apply() + // When a new directory is picked, we currently will reset the existing games + // database. This effectively means that only one game directory is supported. + PreferenceManager.getDefaultSharedPreferences(applicationContext).edit() + .putString(GameHelper.KEY_GAME_PATH, result.toString()) + .apply() - Toast.makeText( - applicationContext, - R.string.games_dir_selected, - Toast.LENGTH_LONG - ).show() + Toast.makeText( + applicationContext, + R.string.games_dir_selected, + Toast.LENGTH_LONG + ).show() - gamesViewModel.reloadGames(true) - } + gamesViewModel.reloadGames(true) + } val getProdKey = registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> - if (result == null) { - return@registerForActivityResult + if (result != null) { + processKey(result) } + } - if (FileUtil.getExtension(result) != "keys") { - MessageDialogFragment.newInstance( - R.string.reading_keys_failure, - R.string.install_prod_keys_failure_extension_description - ).show(supportFragmentManager, MessageDialogFragment.TAG) - return@registerForActivityResult - } + fun processKey(result: Uri): Boolean { + if (FileUtil.getExtension(result) != "keys") { + MessageDialogFragment.newInstance( + R.string.reading_keys_failure, + R.string.install_prod_keys_failure_extension_description + ).show(supportFragmentManager, MessageDialogFragment.TAG) + return false + } - contentResolver.takePersistableUriPermission( + contentResolver.takePersistableUriPermission( + result, + Intent.FLAG_GRANT_READ_URI_PERMISSION + ) + + val dstPath = DirectoryInitialization.userDirectory + "/keys/" + if (FileUtil.copyUriToInternalStorage( + applicationContext, result, - Intent.FLAG_GRANT_READ_URI_PERMISSION + dstPath, + "prod.keys" ) - - val dstPath = DirectoryInitialization.userDirectory + "/keys/" - if (FileUtil.copyUriToInternalStorage( + ) { + if (NativeLibrary.reloadKeys()) { + Toast.makeText( applicationContext, - result, - dstPath, - "prod.keys" - ) - ) { - if (NativeLibrary.reloadKeys()) { - Toast.makeText( - applicationContext, - R.string.install_keys_success, - Toast.LENGTH_SHORT - ).show() - gamesViewModel.reloadGames(true) - } else { - MessageDialogFragment.newInstance( - R.string.invalid_keys_error, - R.string.install_keys_failure_description, - R.string.dumping_keys_quickstart_link - ).show(supportFragmentManager, MessageDialogFragment.TAG) - } + R.string.install_keys_success, + Toast.LENGTH_SHORT + ).show() + gamesViewModel.reloadGames(true) + return true + } else { + MessageDialogFragment.newInstance( + R.string.invalid_keys_error, + R.string.install_keys_failure_description, + R.string.dumping_keys_quickstart_link + ).show(supportFragmentManager, MessageDialogFragment.TAG) + return false } } + return false + } val getFirmware = registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ViewUtils.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ViewUtils.kt new file mode 100644 index 000000000..f9a3e4126 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ViewUtils.kt @@ -0,0 +1,35 @@ +// 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() + } +} diff --git a/src/android/app/src/main/res/layout-w600dp/fragment_setup.xml b/src/android/app/src/main/res/layout-w600dp/fragment_setup.xml index cbe631d88..406df9eab 100644 --- a/src/android/app/src/main/res/layout-w600dp/fragment_setup.xml +++ b/src/android/app/src/main/res/layout-w600dp/fragment_setup.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<androidx.constraintlayout.widget.ConstraintLayout +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/setup_root" @@ -8,33 +8,39 @@ <androidx.viewpager2.widget.ViewPager2 android:id="@+id/viewPager2" - android:layout_width="0dp" - android:layout_height="0dp" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" /> + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_alignParentTop="true" + android:layout_alignParentBottom="true" + android:clipToPadding="false" /> - <com.google.android.material.button.MaterialButton - style="@style/Widget.Material3.Button.TextButton" - android:id="@+id/button_next" - android:layout_width="wrap_content" + <androidx.constraintlayout.widget.ConstraintLayout + android:id="@+id/constraint_buttons" + android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_margin="16dp" - android:text="@string/next" - android:visibility="invisible" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="parent" /> + android:layout_alignParentBottom="true" + android:layout_margin="8dp"> - <com.google.android.material.button.MaterialButton - android:id="@+id/button_back" - style="@style/Widget.Material3.Button.TextButton" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_margin="16dp" - android:text="@string/back" - android:visibility="invisible" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintStart_toStartOf="parent" /> + <com.google.android.material.button.MaterialButton + android:id="@+id/button_next" + style="@style/Widget.Material3.Button.TextButton" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/next" + android:visibility="invisible" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" /> + + <com.google.android.material.button.MaterialButton + android:id="@+id/button_back" + style="@style/Widget.Material3.Button.TextButton" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/back" + android:visibility="invisible" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent" /> + + </androidx.constraintlayout.widget.ConstraintLayout> -</androidx.constraintlayout.widget.ConstraintLayout> +</RelativeLayout> diff --git a/src/android/app/src/main/res/layout-w600dp/page_setup.xml b/src/android/app/src/main/res/layout-w600dp/page_setup.xml index e1c26b2f8..9e0ab8ecb 100644 --- a/src/android/app/src/main/res/layout-w600dp/page_setup.xml +++ b/src/android/app/src/main/res/layout-w600dp/page_setup.xml @@ -21,45 +21,76 @@ </LinearLayout> - <LinearLayout + <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent" - android:layout_weight="1" - android:orientation="vertical" - android:gravity="center"> + android:layout_weight="1"> <com.google.android.material.textview.MaterialTextView - style="@style/TextAppearance.Material3.DisplaySmall" android:id="@+id/text_title" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:textAlignment="center" + style="@style/TextAppearance.Material3.DisplaySmall" + android:layout_width="0dp" + android:layout_height="0dp" + android:gravity="center" android:textColor="?attr/colorOnSurface" 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" /> <com.google.android.material.textview.MaterialTextView - style="@style/TextAppearance.Material3.TitleLarge" android:id="@+id/text_description" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="16dp" - android:paddingHorizontal="32dp" - android:textAlignment="center" - android:textSize="26sp" - app:lineHeight="40sp" + style="@style/TextAppearance.Material3.TitleLarge" + android:layout_width="0dp" + android:layout_height="0dp" + android:gravity="center" + android:textSize="20sp" + android:paddingHorizontal="16dp" + app:layout_constraintBottom_toTopOf="@+id/button_action" + 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" /> + <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 android:id="@+id/button_action" android:layout_width="wrap_content" android:layout_height="56dp" - android:layout_marginTop="32dp" + android:layout_marginTop="16dp" + android:layout_marginBottom="48dp" android:textSize="20sp" - app:iconSize="24sp" app:iconGravity="end" + app:iconSize="24sp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/text_description" tools:text="Get started" /> - </LinearLayout> + </androidx.constraintlayout.widget.ConstraintLayout> </LinearLayout> diff --git a/src/android/app/src/main/res/layout/dialog_slider.xml b/src/android/app/src/main/res/layout/dialog_slider.xml index 8c84cb606..d1cb31739 100644 --- a/src/android/app/src/main/res/layout/dialog_slider.xml +++ b/src/android/app/src/main/res/layout/dialog_slider.xml @@ -5,23 +5,16 @@ android:layout_height="wrap_content" android:orientation="vertical"> - <TextView + <com.google.android.material.textview.MaterialTextView android:id="@+id/text_value" + style="@style/TextAppearance.Material3.LabelMedium" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_centerHorizontal="true" android:layout_marginBottom="@dimen/spacing_medlarge" android:layout_marginTop="@dimen/spacing_medlarge" - 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="%" /> + tools:text="75%" /> <com.google.android.material.slider.Slider android:id="@+id/slider" diff --git a/src/android/app/src/main/res/layout/fragment_setup.xml b/src/android/app/src/main/res/layout/fragment_setup.xml index d7bafaea2..9499f6463 100644 --- a/src/android/app/src/main/res/layout/fragment_setup.xml +++ b/src/android/app/src/main/res/layout/fragment_setup.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<androidx.constraintlayout.widget.ConstraintLayout +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/setup_root" @@ -8,35 +8,39 @@ <androidx.viewpager2.widget.ViewPager2 android:id="@+id/viewPager2" - android:layout_width="0dp" - android:layout_height="0dp" - android:clipToPadding="false" - android:layout_marginBottom="16dp" - app:layout_constraintBottom_toTopOf="@+id/button_next" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" /> - - <com.google.android.material.button.MaterialButton - style="@style/Widget.Material3.Button.TextButton" - android:id="@+id/button_next" - android:layout_width="wrap_content" + android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_margin="12dp" - android:text="@string/next" - android:visibility="invisible" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="parent" /> + android:layout_above="@+id/constraint_buttons" + android:layout_alignParentTop="true" + android:clipToPadding="false" /> - <com.google.android.material.button.MaterialButton - style="@style/Widget.Material3.Button.TextButton" - android:id="@+id/button_back" - android:layout_width="wrap_content" + <androidx.constraintlayout.widget.ConstraintLayout + android:id="@+id/constraint_buttons" + android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_margin="12dp" - android:text="@string/back" - android:visibility="invisible" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintStart_toStartOf="parent" /> + android:layout_margin="8dp" + android:layout_alignParentBottom="true"> + + <com.google.android.material.button.MaterialButton + android:id="@+id/button_next" + style="@style/Widget.Material3.Button.TextButton" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/next" + android:visibility="invisible" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" /> + + <com.google.android.material.button.MaterialButton + android:id="@+id/button_back" + style="@style/Widget.Material3.Button.TextButton" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/back" + android:visibility="invisible" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent" /> + + </androidx.constraintlayout.widget.ConstraintLayout> -</androidx.constraintlayout.widget.ConstraintLayout> +</RelativeLayout> diff --git a/src/android/app/src/main/res/layout/list_item_setting.xml b/src/android/app/src/main/res/layout/list_item_setting.xml index ec896342b..f1037a740 100644 --- a/src/android/app/src/main/res/layout/list_item_setting.xml +++ b/src/android/app/src/main/res/layout/list_item_setting.xml @@ -1,9 +1,10 @@ <?xml version="1.0" encoding="utf-8"?> -<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" +<RelativeLayout + 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" android:layout_width="match_parent" android:layout_height="wrap_content" - xmlns:app="http://schemas.android.com/apk/res-auto" android:background="?android:attr/selectableItemBackground" android:clickable="true" android:focusable="true" @@ -11,31 +12,40 @@ android:minHeight="72dp" android:padding="@dimen/spacing_large"> - <com.google.android.material.textview.MaterialTextView - style="@style/TextAppearance.Material3.HeadlineMedium" - android:id="@+id/text_setting_name" - android:layout_width="0dp" + <LinearLayout + android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_alignParentEnd="true" - android:layout_alignParentStart="true" - android:layout_alignParentTop="true" - android:textSize="16sp" - android:textAlignment="viewStart" - app:lineHeight="28dp" - tools:text="Setting Name" /> + android:orientation="vertical"> - <TextView - style="@style/TextAppearance.Material3.BodySmall" - android:id="@+id/text_setting_description" - android:layout_width="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:visibility="visible" - android:textAlignment="viewStart" - tools:text="@string/app_disclaimer" /> + <com.google.android.material.textview.MaterialTextView + android:id="@+id/text_setting_name" + style="@style/TextAppearance.Material3.HeadlineMedium" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textAlignment="viewStart" + android:textSize="16sp" + app:lineHeight="22dp" + tools:text="Setting Name" /> + + <com.google.android.material.textview.MaterialTextView + android:id="@+id/text_setting_description" + style="@style/TextAppearance.Material3.BodySmall" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/spacing_small" + android:textAlignment="viewStart" + 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> diff --git a/src/android/app/src/main/res/layout/page_setup.xml b/src/android/app/src/main/res/layout/page_setup.xml index 1436ef308..535abcf02 100644 --- a/src/android/app/src/main/res/layout/page_setup.xml +++ b/src/android/app/src/main/res/layout/page_setup.xml @@ -21,11 +21,12 @@ app:layout_constraintVertical_chainStyle="spread" app:layout_constraintWidth_max="220dp" 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 android:id="@+id/text_title" - style="@style/TextAppearance.Material3.DisplayMedium" + style="@style/TextAppearance.Material3.DisplaySmall" android:layout_width="0dp" android:layout_height="0dp" android:textAlignment="center" @@ -44,23 +45,42 @@ android:layout_width="0dp" android:layout_height="0dp" android:textAlignment="center" - android:textSize="26sp" + android:textSize="20sp" android:paddingHorizontal="16dp" app:layout_constraintBottom_toTopOf="@+id/button_action" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/text_title" app:layout_constraintVertical_weight="2" - app:lineHeight="40sp" + app:lineHeight="30sp" 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 android:id="@+id/button_action" android:layout_width="wrap_content" android:layout_height="56dp" - android:textSize="20sp" android:layout_marginTop="16dp" android:layout_marginBottom="48dp" + android:textSize="20sp" app:iconGravity="end" app:iconSize="24sp" app:layout_constraintBottom_toBottomOf="parent" diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index 02e25504d..de1b2909b 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -29,6 +29,7 @@ <string name="back">Back</string> <string name="add_games">Add Games</string> <string name="add_games_description">Select your games folder</string> + <string name="step_complete">Complete!</string> <!-- Home strings --> <string name="home_games">Games</string> @@ -149,6 +150,7 @@ <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="cpu_accuracy">CPU accuracy</string> + <string name="value_with_units">%1$s%2$s</string> <!-- System settings strings --> <string name="use_docked_mode">Docked Mode</string> diff --git a/src/common/alignment.h b/src/common/alignment.h index fa715d497..fc5c26898 100644 --- a/src/common/alignment.h +++ b/src/common/alignment.h @@ -3,6 +3,7 @@ #pragma once +#include <bit> #include <cstddef> #include <new> #include <type_traits> @@ -10,8 +11,10 @@ namespace Common { template <typename T> - requires std::is_unsigned_v<T> -[[nodiscard]] constexpr T AlignUp(T value, size_t size) { + requires std::is_integral_v<T> +[[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)}; value -= mod; return static_cast<T>(mod == T{0} ? value : value + size); @@ -24,8 +27,10 @@ template <typename T> } template <typename T> - requires std::is_unsigned_v<T> -[[nodiscard]] constexpr T AlignDown(T value, size_t size) { + requires std::is_integral_v<T> +[[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); } @@ -55,6 +60,30 @@ template <typename T, typename U> 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> class AlignmentAllocator { public: diff --git a/src/common/lz4_compression.cpp b/src/common/lz4_compression.cpp index ffb32fecf..d85ab1742 100644 --- a/src/common/lz4_compression.cpp +++ b/src/common/lz4_compression.cpp @@ -71,4 +71,10 @@ std::vector<u8> DecompressDataLZ4(std::span<const u8> compressed, std::size_t un 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 diff --git a/src/common/lz4_compression.h b/src/common/lz4_compression.h index 7fd53a960..3ae17c2bb 100644 --- a/src/common/lz4_compression.h +++ b/src/common/lz4_compression.h @@ -56,4 +56,6 @@ namespace Common::Compression { [[nodiscard]] std::vector<u8> DecompressDataLZ4(std::span<const u8> compressed, std::size_t uncompressed_size); +[[nodiscard]] int DecompressDataLZ4(void* dst, size_t dst_size, const void* src, size_t src_size); + } // namespace Common::Compression diff --git a/src/common/settings_common.cpp b/src/common/settings_common.cpp index dedf5ef90..137b65d5f 100644 --- a/src/common/settings_common.cpp +++ b/src/common/settings_common.cpp @@ -1,7 +1,9 @@ // 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 { diff --git a/src/common/settings_enums.h b/src/common/settings_enums.h index a1a29ebf6..e7cb59ea5 100644 --- a/src/common/settings_enums.h +++ b/src/common/settings_enums.h @@ -12,8 +12,8 @@ namespace Settings { template <typename T> struct EnumMetadata { - static constexpr std::vector<std::pair<std::string, T>> Canonicalizations(); - static constexpr u32 Index(); + 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__)) @@ -66,11 +66,11 @@ struct EnumMetadata { #define ENUM(NAME, ...) \ enum class NAME : u32 { __VA_ARGS__ }; \ template <> \ - constexpr std::vector<std::pair<std::string, NAME>> EnumMetadata<NAME>::Canonicalizations() { \ + inline std::vector<std::pair<std::string, NAME>> EnumMetadata<NAME>::Canonicalizations() { \ return {PAIR(NAME, __VA_ARGS__)}; \ } \ template <> \ - constexpr u32 EnumMetadata<NAME>::Index() { \ + inline u32 EnumMetadata<NAME>::Index() { \ return __COUNTER__; \ } @@ -85,7 +85,7 @@ enum class AudioEngine : u32 { }; template <> -constexpr std::vector<std::pair<std::string, AudioEngine>> +inline std::vector<std::pair<std::string, AudioEngine>> EnumMetadata<AudioEngine>::Canonicalizations() { return { {"auto", AudioEngine::Auto}, @@ -96,7 +96,7 @@ EnumMetadata<AudioEngine>::Canonicalizations() { } template <> -constexpr u32 EnumMetadata<AudioEngine>::Index() { +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; @@ -147,7 +147,7 @@ ENUM(AntiAliasing, None, Fxaa, Smaa, MaxEnum); ENUM(AspectRatio, R16_9, R4_3, R21_9, R16_10, Stretch); template <typename Type> -constexpr std::string CanonicalizeEnum(Type id) { +inline std::string CanonicalizeEnum(Type id) { const auto group = EnumMetadata<Type>::Canonicalizations(); for (auto& [name, value] : group) { if (value == id) { @@ -158,7 +158,7 @@ constexpr std::string CanonicalizeEnum(Type id) { } template <typename Type> -constexpr Type ToEnum(const std::string& canonicalization) { +inline Type ToEnum(const std::string& canonicalization) { const auto group = EnumMetadata<Type>::Canonicalizations(); for (auto& [name, value] : group) { if (name == canonicalization) { diff --git a/src/common/settings_setting.h b/src/common/settings_setting.h index a8beb06e9..e10843c73 100644 --- a/src/common/settings_setting.h +++ b/src/common/settings_setting.h @@ -190,7 +190,7 @@ public: } } - [[nodiscard]] std::string constexpr Canonicalize() const override final { + [[nodiscard]] std::string Canonicalize() const override final { if constexpr (std::is_enum_v<Type>) { return CanonicalizeEnum(this->GetValue()); } else { @@ -256,11 +256,11 @@ public: * @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, - BasicSetting* other_setting_ = nullptr) - requires(!ranged) + typename std::enable_if<!ranged, T*>::type other_setting_ = nullptr) : Setting<Type, false>{ linkage, default_val, name, category_, specialization_, save_, runtime_modifiable_, other_setting_} { @@ -282,12 +282,12 @@ public: * @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, - BasicSetting* other_setting_ = nullptr) - requires(ranged) + 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_, diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 4b7395be8..012648d69 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -37,6 +37,49 @@ add_library(core STATIC debugger/gdbstub.h device_memory.cpp 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.h file_sys/card_image.cpp @@ -57,8 +100,6 @@ add_library(core STATIC file_sys/mode.h file_sys/nca_metadata.cpp file_sys/nca_metadata.h - file_sys/nca_patch.cpp - file_sys/nca_patch.h file_sys/partition_filesystem.cpp file_sys/partition_filesystem.h file_sys/patch_manager.cpp diff --git a/src/core/debugger/gdbstub.cpp b/src/core/debugger/gdbstub.cpp index 0f839d5b4..e55831f27 100644 --- a/src/core/debugger/gdbstub.cpp +++ b/src/core/debugger/gdbstub.cpp @@ -263,6 +263,23 @@ void GDBStub::ExecuteCommand(std::string_view packet, std::vector<DebuggerAction std::vector<u8> mem(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)); } else { SendReply(GDB_STUB_REPLY_ERR); diff --git a/src/core/file_sys/card_image.cpp b/src/core/file_sys/card_image.cpp index 5d02865f4..3b2588c84 100644 --- a/src/core/file_sys/card_image.cpp +++ b/src/core/file_sys/card_image.cpp @@ -183,9 +183,9 @@ u32 XCI::GetSystemUpdateVersion() { } for (const auto& update_file : update->GetFiles()) { - NCA nca{update_file, nullptr, 0}; + NCA nca{update_file}; - if (nca.GetStatus() != Loader::ResultStatus::Success) { + if (nca.GetStatus() != Loader::ResultStatus::Success || nca.GetSubdirectories().empty()) { continue; } @@ -296,7 +296,7 @@ Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) { continue; } - auto nca = std::make_shared<NCA>(partition_file, nullptr, 0); + auto nca = std::make_shared<NCA>(partition_file); if (nca->IsUpdate()) { continue; } diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp index 06efab46d..44e6852fe 100644 --- a/src/core/file_sys/content_archive.cpp +++ b/src/core/file_sys/content_archive.cpp @@ -12,545 +12,109 @@ #include "core/crypto/ctr_encryption_layer.h" #include "core/crypto/key_manager.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/vfs_offset.h" #include "core/loader/loader.h" -namespace FileSys { +#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" -// Media offsets in headers are stored divided by 512. Mult. by this to get real offset. -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'); -} +namespace FileSys { -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()} { +NCA::NCA(VirtualFile file_, const NCA* base_nca) + : file(std::move(file_)), keys{Core::Crypto::KeyManager::Instance()} { if (file == nullptr) { status = Loader::ResultStatus::ErrorNullFile; return; } - if (sizeof(NCAHeader) != file->ReadObject(&header)) { - LOG_ERROR(Loader, "File reader errored out during header read."); + reader = std::make_shared<NcaReader>(); + if (Result rc = + 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; return; } - if (!HandlePotentialHeaderDecryption()) { - return; - } - - has_rights_id = std::ranges::any_of(header.rights_id, [](char c) { return c != '\0'; }); - - 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; - } - - status = Loader::ResultStatus::Success; -} - -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; -} + RightsId rights_id{}; + 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; -bool NCA::HandlePotentialHeaderDecryption() { - if (IsValidNCA(header)) { - return true; - } - - if (!CheckSupportedNCA(header)) { - return false; - } + u128 rights_id_u128; + std::memcpy(rights_id_u128.data(), rights_id.data(), sizeof(rights_id)); - 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; + 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; } - if (keys.HasKey(Core::Crypto::S256KeyType::Header)) { - status = Loader::ResultStatus::ErrorIncorrectHeaderKey; - } else { - status = Loader::ResultStatus::ErrorMissingHeaderKey; + if (!keys.HasKey(Core::Crypto::S128KeyType::Titlekek, key_generation)) { + status = Loader::ResultStatus::ErrorMissingTitlekek; + return; } - return false; - } - return true; -} + 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); -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); + reader->SetExternalDecryptionKey(titlekey.data(), titlekey.size()); } - 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; + 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; } - 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; + if (header_reader.GetFsType() == NcaFsHeader::FsType::RomFs) { + files.push_back(filesystems[i]); + romfs = files.back(); } - 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 (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 (bktr_base_romfs == nullptr) { - status = Loader::ResultStatus::ErrorMissingBKTRBaseRomFS; - return false; + if (header_reader.GetEncryptionType() == NcaFsHeader::EncryptionType::AesCtrEx) { + is_update = true; } - - 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; - } + if (is_update && base_nca == nullptr) { + status = Loader::ResultStatus::ErrorMissingBKTRBaseRomFS; } else { - if (status != Loader::ResultStatus::Success) - return false; - if (has_rights_id) - status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek; - else - status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey; - return false; + status = Loader::ResultStatus::Success; } - - 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; - } -} +NCA::~NCA() = default; Loader::ResultStatus NCA::GetStatus() const { return status; @@ -579,21 +143,24 @@ VirtualDir NCA::GetParentDirectory() const { } NCAContentType NCA::GetType() const { - return header.content_type; + return static_cast<NCAContentType>(reader->GetContentType()); } u64 NCA::GetTitleId() const { - if (is_update || status == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) - return header.title_id | 0x800; - return header.title_id; + if (is_update) { + return reader->GetProgramId() | 0x800; + } + return reader->GetProgramId(); } -std::array<u8, 16> NCA::GetRightsId() const { - return header.rights_id; +RightsId NCA::GetRightsId() const { + RightsId result; + reader->GetRightsId(result.data(), result.size()); + return result; } u32 NCA::GetSDKVersion() const { - return header.sdk_version; + return reader->GetSdkAddonVersion(); } bool NCA::IsUpdate() const { @@ -612,10 +179,6 @@ VirtualFile NCA::GetBaseFile() const { return file; } -u64 NCA::GetBaseIVFCOffset() const { - return ivfc_offset; -} - VirtualDir NCA::GetLogoPartition() const { return logo; } diff --git a/src/core/file_sys/content_archive.h b/src/core/file_sys/content_archive.h index 20f524f80..af521d453 100644 --- a/src/core/file_sys/content_archive.h +++ b/src/core/file_sys/content_archive.h @@ -21,7 +21,7 @@ enum class ResultStatus : u16; namespace FileSys { -union NCASectionHeader; +class NcaReader; /// Describes the type of content within an NCA archive. enum class NCAContentType : u8 { @@ -45,41 +45,7 @@ enum class NCAContentType : u8 { PublicData = 5, }; -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."); +using RightsId = std::array<u8, 0x10>; inline bool IsDirectoryExeFS(const VirtualDir& pfs) { // According to switchbrew, an exefs must only contain these two files: @@ -97,8 +63,7 @@ inline bool IsDirectoryLogoPartition(const VirtualDir& pfs) { // After construction, use GetStatus to determine if the file is valid and ready to be used. class NCA : public ReadOnlyVfsDirectory { public: - explicit NCA(VirtualFile file, VirtualFile bktr_base_romfs = nullptr, - u64 bktr_base_ivfc_offset = 0); + explicit NCA(VirtualFile file, const NCA* base_nca = nullptr); ~NCA() override; Loader::ResultStatus GetStatus() const; @@ -110,7 +75,7 @@ public: NCAContentType GetType() const; u64 GetTitleId() const; - std::array<u8, 0x10> GetRightsId() const; + RightsId GetRightsId() const; u32 GetSDKVersion() const; bool IsUpdate() const; @@ -119,26 +84,9 @@ public: VirtualFile GetBaseFile() const; - // Returns the base ivfc offset used in BKTR patching. - u64 GetBaseIVFCOffset() const; - VirtualDir GetLogoPartition() const; 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<VirtualFile> files; @@ -146,11 +94,6 @@ private: VirtualDir exefs = nullptr; VirtualDir logo = nullptr; VirtualFile file; - VirtualFile bktr_base_romfs; - u64 ivfc_offset = 0; - - NCAHeader header{}; - bool has_rights_id{}; Loader::ResultStatus status{}; @@ -158,6 +101,7 @@ private: bool is_update = false; Core::Crypto::KeyManager& keys; + std::shared_ptr<NcaReader> reader; }; } // namespace FileSys diff --git a/src/core/file_sys/errors.h b/src/core/file_sys/errors.h index 7cee0c7df..2f5045a67 100644 --- a/src/core/file_sys/errors.h +++ b/src/core/file_sys/errors.h @@ -17,4 +17,74 @@ constexpr Result ERROR_INVALID_ARGUMENT{ErrorModule::FS, 6001}; constexpr Result ERROR_INVALID_OFFSET{ErrorModule::FS, 6061}; 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 diff --git a/src/core/file_sys/fssystem/fs_i_storage.h b/src/core/file_sys/fssystem/fs_i_storage.h new file mode 100644 index 000000000..416dd57b8 --- /dev/null +++ b/src/core/file_sys/fssystem/fs_i_storage.h @@ -0,0 +1,58 @@ +// 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 diff --git a/src/core/file_sys/fssystem/fs_types.h b/src/core/file_sys/fssystem/fs_types.h new file mode 100644 index 000000000..43aeaf447 --- /dev/null +++ b/src/core/file_sys/fssystem/fs_types.h @@ -0,0 +1,46 @@ +// 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 diff --git a/src/core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.cpp b/src/core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.cpp new file mode 100644 index 000000000..f25c95472 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.cpp @@ -0,0 +1,251 @@ +// 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 diff --git a/src/core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.h b/src/core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.h new file mode 100644 index 000000000..d0e9ceed0 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.h @@ -0,0 +1,114 @@ +// 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 diff --git a/src/core/file_sys/fssystem/fssystem_aes_ctr_storage.cpp b/src/core/file_sys/fssystem/fssystem_aes_ctr_storage.cpp new file mode 100644 index 000000000..b65aca18d --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_aes_ctr_storage.cpp @@ -0,0 +1,129 @@ +// 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 diff --git a/src/core/file_sys/fssystem/fssystem_aes_ctr_storage.h b/src/core/file_sys/fssystem/fssystem_aes_ctr_storage.h new file mode 100644 index 000000000..339e49697 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_aes_ctr_storage.h @@ -0,0 +1,43 @@ +// 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 diff --git a/src/core/file_sys/fssystem/fssystem_aes_xts_storage.cpp b/src/core/file_sys/fssystem/fssystem_aes_xts_storage.cpp new file mode 100644 index 000000000..022424229 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_aes_xts_storage.cpp @@ -0,0 +1,112 @@ +// 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 diff --git a/src/core/file_sys/fssystem/fssystem_aes_xts_storage.h b/src/core/file_sys/fssystem/fssystem_aes_xts_storage.h new file mode 100644 index 000000000..f342efb57 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_aes_xts_storage.h @@ -0,0 +1,42 @@ +// 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 diff --git a/src/core/file_sys/fssystem/fssystem_alignment_matching_storage.h b/src/core/file_sys/fssystem/fssystem_alignment_matching_storage.h new file mode 100644 index 000000000..f96691d03 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_alignment_matching_storage.h @@ -0,0 +1,146 @@ +// 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 diff --git a/src/core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.cpp b/src/core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.cpp new file mode 100644 index 000000000..641c888ae --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.cpp @@ -0,0 +1,204 @@ +// 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 diff --git a/src/core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.h b/src/core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.h new file mode 100644 index 000000000..4a05b0e88 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.h @@ -0,0 +1,21 @@ +// 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 diff --git a/src/core/file_sys/fssystem/fssystem_bucket_tree.cpp b/src/core/file_sys/fssystem/fssystem_bucket_tree.cpp new file mode 100644 index 000000000..af8541009 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_bucket_tree.cpp @@ -0,0 +1,598 @@ +// 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 diff --git a/src/core/file_sys/fssystem/fssystem_bucket_tree.h b/src/core/file_sys/fssystem/fssystem_bucket_tree.h new file mode 100644 index 000000000..46850cd48 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_bucket_tree.h @@ -0,0 +1,416 @@ +// 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 diff --git a/src/core/file_sys/fssystem/fssystem_bucket_tree_template_impl.h b/src/core/file_sys/fssystem/fssystem_bucket_tree_template_impl.h new file mode 100644 index 000000000..030b2916b --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_bucket_tree_template_impl.h @@ -0,0 +1,170 @@ +// 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 diff --git a/src/core/file_sys/fssystem/fssystem_bucket_tree_utils.h b/src/core/file_sys/fssystem/fssystem_bucket_tree_utils.h new file mode 100644 index 000000000..5503613fc --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_bucket_tree_utils.h @@ -0,0 +1,110 @@ +// 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 diff --git a/src/core/file_sys/fssystem/fssystem_compressed_storage.h b/src/core/file_sys/fssystem/fssystem_compressed_storage.h new file mode 100644 index 000000000..33d93938e --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_compressed_storage.h @@ -0,0 +1,963 @@ +// 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 diff --git a/src/core/file_sys/fssystem/fssystem_compression_common.h b/src/core/file_sys/fssystem/fssystem_compression_common.h new file mode 100644 index 000000000..266e0a7e5 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_compression_common.h @@ -0,0 +1,43 @@ +// 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 diff --git a/src/core/file_sys/fssystem/fssystem_compression_configuration.cpp b/src/core/file_sys/fssystem/fssystem_compression_configuration.cpp new file mode 100644 index 000000000..ef552cefe --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_compression_configuration.cpp @@ -0,0 +1,36 @@ +// 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 diff --git a/src/core/file_sys/fssystem/fssystem_compression_configuration.h b/src/core/file_sys/fssystem/fssystem_compression_configuration.h new file mode 100644 index 000000000..ec9b48e9a --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_compression_configuration.h @@ -0,0 +1,12 @@ +// 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(); + +} diff --git a/src/core/file_sys/fssystem/fssystem_crypto_configuration.cpp b/src/core/file_sys/fssystem/fssystem_crypto_configuration.cpp new file mode 100644 index 000000000..a4f0cde28 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_crypto_configuration.cpp @@ -0,0 +1,65 @@ +// 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 diff --git a/src/core/file_sys/fssystem/fssystem_crypto_configuration.h b/src/core/file_sys/fssystem/fssystem_crypto_configuration.h new file mode 100644 index 000000000..7fd9c5a8d --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_crypto_configuration.h @@ -0,0 +1,12 @@ +// 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(); + +} diff --git a/src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.cpp b/src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.cpp new file mode 100644 index 000000000..4a75b5308 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.cpp @@ -0,0 +1,127 @@ +// 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 diff --git a/src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h b/src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h new file mode 100644 index 000000000..5cf697efe --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h @@ -0,0 +1,164 @@ +// 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 diff --git a/src/core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.cpp b/src/core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.cpp new file mode 100644 index 000000000..caea0b8f8 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.cpp @@ -0,0 +1,80 @@ +// 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 diff --git a/src/core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.h b/src/core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.h new file mode 100644 index 000000000..18df400af --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.h @@ -0,0 +1,44 @@ +// 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 diff --git a/src/core/file_sys/fssystem/fssystem_indirect_storage.cpp b/src/core/file_sys/fssystem/fssystem_indirect_storage.cpp new file mode 100644 index 000000000..7544e70b2 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_indirect_storage.cpp @@ -0,0 +1,119 @@ +// 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 diff --git a/src/core/file_sys/fssystem/fssystem_indirect_storage.h b/src/core/file_sys/fssystem/fssystem_indirect_storage.h new file mode 100644 index 000000000..7854335bf --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_indirect_storage.h @@ -0,0 +1,294 @@ +// 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 diff --git a/src/core/file_sys/fssystem/fssystem_integrity_romfs_storage.cpp b/src/core/file_sys/fssystem/fssystem_integrity_romfs_storage.cpp new file mode 100644 index 000000000..2c3da230c --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_integrity_romfs_storage.cpp @@ -0,0 +1,30 @@ +// 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 diff --git a/src/core/file_sys/fssystem/fssystem_integrity_romfs_storage.h b/src/core/file_sys/fssystem/fssystem_integrity_romfs_storage.h new file mode 100644 index 000000000..5f8512b2a --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_integrity_romfs_storage.h @@ -0,0 +1,42 @@ +// 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 diff --git a/src/core/file_sys/fssystem/fssystem_integrity_verification_storage.cpp b/src/core/file_sys/fssystem/fssystem_integrity_verification_storage.cpp new file mode 100644 index 000000000..2f73abf86 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_integrity_verification_storage.cpp @@ -0,0 +1,91 @@ +// 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 diff --git a/src/core/file_sys/fssystem/fssystem_integrity_verification_storage.h b/src/core/file_sys/fssystem/fssystem_integrity_verification_storage.h new file mode 100644 index 000000000..09f76799d --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_integrity_verification_storage.h @@ -0,0 +1,65 @@ +// 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 diff --git a/src/core/file_sys/fssystem/fssystem_memory_resource_buffer_hold_storage.h b/src/core/file_sys/fssystem/fssystem_memory_resource_buffer_hold_storage.h new file mode 100644 index 000000000..c07a127fb --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_memory_resource_buffer_hold_storage.h @@ -0,0 +1,61 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "core/file_sys/fssystem/fs_i_storage.h" + +namespace FileSys { + +class MemoryResourceBufferHoldStorage : public IStorage { + YUZU_NON_COPYABLE(MemoryResourceBufferHoldStorage); + YUZU_NON_MOVEABLE(MemoryResourceBufferHoldStorage); + +public: + MemoryResourceBufferHoldStorage(VirtualFile storage, size_t buffer_size) + : m_storage(std::move(storage)), m_buffer(::operator new(buffer_size)), + m_buffer_size(buffer_size) {} + + virtual ~MemoryResourceBufferHoldStorage() { + // If we have a buffer, deallocate it. + if (m_buffer != nullptr) { + ::operator delete(m_buffer); + } + } + + bool IsValid() const { + return m_buffer != nullptr; + } + void* GetBuffer() const { + return m_buffer; + } + +public: + virtual size_t Read(u8* buffer, size_t size, size_t offset) const override { + // Check pre-conditions. + ASSERT(m_storage != nullptr); + + return m_storage->Read(buffer, size, offset); + } + + virtual size_t GetSize() const override { + // Check pre-conditions. + ASSERT(m_storage != nullptr); + + return m_storage->GetSize(); + } + + virtual size_t Write(const u8* buffer, size_t size, size_t offset) override { + // Check pre-conditions. + ASSERT(m_storage != nullptr); + + return m_storage->Write(buffer, size, offset); + } + +private: + VirtualFile m_storage; + void* m_buffer; + size_t m_buffer_size; +}; + +} // namespace FileSys diff --git a/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.cpp b/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.cpp new file mode 100644 index 000000000..0f5432203 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.cpp @@ -0,0 +1,1351 @@ +// 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_aes_xts_storage.h" +#include "core/file_sys/fssystem/fssystem_alignment_matching_storage.h" +#include "core/file_sys/fssystem/fssystem_compressed_storage.h" +#include "core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h" +#include "core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.h" +#include "core/file_sys/fssystem/fssystem_indirect_storage.h" +#include "core/file_sys/fssystem/fssystem_integrity_romfs_storage.h" +#include "core/file_sys/fssystem/fssystem_memory_resource_buffer_hold_storage.h" +#include "core/file_sys/fssystem/fssystem_nca_file_system_driver.h" +#include "core/file_sys/fssystem/fssystem_sparse_storage.h" +#include "core/file_sys/fssystem/fssystem_switch_storage.h" +#include "core/file_sys/vfs_offset.h" +#include "core/file_sys/vfs_vector.h" + +namespace FileSys { + +namespace { + +constexpr inline s32 IntegrityDataCacheCount = 24; +constexpr inline s32 IntegrityHashCacheCount = 8; + +constexpr inline s32 IntegrityDataCacheCountForMeta = 16; +constexpr inline s32 IntegrityHashCacheCountForMeta = 2; + +class SharedNcaBodyStorage : public IReadOnlyStorage { + YUZU_NON_COPYABLE(SharedNcaBodyStorage); + YUZU_NON_MOVEABLE(SharedNcaBodyStorage); + +private: + VirtualFile m_storage; + std::shared_ptr<NcaReader> m_nca_reader; + +public: + SharedNcaBodyStorage(VirtualFile s, std::shared_ptr<NcaReader> r) + : m_storage(std::move(s)), m_nca_reader(std::move(r)) {} + + virtual size_t Read(u8* buffer, size_t size, size_t offset) const override { + // Validate pre-conditions. + ASSERT(m_storage != nullptr); + + // Read from the base storage. + return m_storage->Read(buffer, size, offset); + } + + virtual size_t GetSize() const override { + // Validate pre-conditions. + ASSERT(m_storage != nullptr); + + return m_storage->GetSize(); + } +}; + +inline s64 GetFsOffset(const NcaReader& reader, s32 fs_index) { + return static_cast<s64>(reader.GetFsOffset(fs_index)); +} + +inline s64 GetFsEndOffset(const NcaReader& reader, s32 fs_index) { + return static_cast<s64>(reader.GetFsEndOffset(fs_index)); +} + +using Sha256DataRegion = NcaFsHeader::Region; +using IntegrityLevelInfo = NcaFsHeader::HashData::IntegrityMetaInfo::LevelHashInfo; +using IntegrityDataInfo = IntegrityLevelInfo::HierarchicalIntegrityVerificationLevelInformation; + +} // namespace + +Result NcaFileSystemDriver::OpenStorageWithContext(VirtualFile* out, + NcaFsHeaderReader* out_header_reader, + s32 fs_index, StorageContext* ctx) { + // Open storage. + R_RETURN(this->OpenStorageImpl(out, out_header_reader, fs_index, ctx)); +} + +Result NcaFileSystemDriver::OpenStorageImpl(VirtualFile* out, NcaFsHeaderReader* out_header_reader, + s32 fs_index, StorageContext* ctx) { + // Validate preconditions. + ASSERT(out != nullptr); + ASSERT(out_header_reader != nullptr); + ASSERT(0 <= fs_index && fs_index < NcaHeader::FsCountMax); + + // Validate the fs index. + R_UNLESS(m_reader->HasFsInfo(fs_index), ResultPartitionNotFound); + + // Initialize our header reader for the fs index. + R_TRY(out_header_reader->Initialize(*m_reader, fs_index)); + + // Declare the storage we're opening. + VirtualFile storage; + + // Process sparse layer. + s64 fs_data_offset = 0; + if (out_header_reader->ExistsSparseLayer()) { + // Get the sparse info. + const auto& sparse_info = out_header_reader->GetSparseInfo(); + + // Create based on whether we have a meta hash layer. + if (out_header_reader->ExistsSparseMetaHashLayer()) { + // Create the sparse storage with verification. + R_TRY(this->CreateSparseStorageWithVerification( + std::addressof(storage), std::addressof(fs_data_offset), + ctx != nullptr ? std::addressof(ctx->current_sparse_storage) : nullptr, + ctx != nullptr ? std::addressof(ctx->sparse_storage_meta_storage) : nullptr, + ctx != nullptr ? std::addressof(ctx->sparse_layer_info_storage) : nullptr, fs_index, + out_header_reader->GetAesCtrUpperIv(), sparse_info, + out_header_reader->GetSparseMetaDataHashDataInfo(), + out_header_reader->GetSparseMetaHashType())); + } else { + // Create the sparse storage. + R_TRY(this->CreateSparseStorage( + std::addressof(storage), std::addressof(fs_data_offset), + ctx != nullptr ? std::addressof(ctx->current_sparse_storage) : nullptr, + ctx != nullptr ? std::addressof(ctx->sparse_storage_meta_storage) : nullptr, + fs_index, out_header_reader->GetAesCtrUpperIv(), sparse_info)); + } + } else { + // Get the data offsets. + fs_data_offset = GetFsOffset(*m_reader, fs_index); + const auto fs_end_offset = GetFsEndOffset(*m_reader, fs_index); + + // Validate that we're within range. + const auto data_size = fs_end_offset - fs_data_offset; + R_UNLESS(data_size > 0, ResultInvalidNcaHeader); + + // Create the body substorage. + R_TRY(this->CreateBodySubStorage(std::addressof(storage), fs_data_offset, data_size)); + + // Potentially save the body substorage to our context. + if (ctx != nullptr) { + ctx->body_substorage = storage; + } + } + + // Process patch layer. + const auto& patch_info = out_header_reader->GetPatchInfo(); + VirtualFile patch_meta_aes_ctr_ex_meta_storage; + VirtualFile patch_meta_indirect_meta_storage; + if (out_header_reader->ExistsPatchMetaHashLayer()) { + // Check the meta hash type. + R_UNLESS(out_header_reader->GetPatchMetaHashType() == + NcaFsHeader::MetaDataHashType::HierarchicalIntegrity, + ResultRomNcaInvalidPatchMetaDataHashType); + + // Create the patch meta storage. + R_TRY(this->CreatePatchMetaStorage( + std::addressof(patch_meta_aes_ctr_ex_meta_storage), + std::addressof(patch_meta_indirect_meta_storage), + ctx != nullptr ? std::addressof(ctx->patch_layer_info_storage) : nullptr, storage, + fs_data_offset, out_header_reader->GetAesCtrUpperIv(), patch_info, + out_header_reader->GetPatchMetaDataHashDataInfo())); + } + + if (patch_info.HasAesCtrExTable()) { + // Check the encryption type. + ASSERT(out_header_reader->GetEncryptionType() == NcaFsHeader::EncryptionType::None || + out_header_reader->GetEncryptionType() == NcaFsHeader::EncryptionType::AesCtrEx || + out_header_reader->GetEncryptionType() == + NcaFsHeader::EncryptionType::AesCtrExSkipLayerHash); + + // Create the ex meta storage. + VirtualFile aes_ctr_ex_storage_meta_storage = patch_meta_aes_ctr_ex_meta_storage; + if (aes_ctr_ex_storage_meta_storage == nullptr) { + // If we don't have a meta storage, we must not have a patch meta hash layer. + ASSERT(!out_header_reader->ExistsPatchMetaHashLayer()); + + R_TRY(this->CreateAesCtrExStorageMetaStorage( + std::addressof(aes_ctr_ex_storage_meta_storage), storage, fs_data_offset, + out_header_reader->GetEncryptionType(), out_header_reader->GetAesCtrUpperIv(), + patch_info)); + } + + // Create the ex storage. + VirtualFile aes_ctr_ex_storage; + R_TRY(this->CreateAesCtrExStorage( + std::addressof(aes_ctr_ex_storage), + ctx != nullptr ? std::addressof(ctx->aes_ctr_ex_storage) : nullptr, std::move(storage), + aes_ctr_ex_storage_meta_storage, fs_data_offset, out_header_reader->GetAesCtrUpperIv(), + patch_info)); + + // Set the base storage as the ex storage. + storage = std::move(aes_ctr_ex_storage); + + // Potentially save storages to our context. + if (ctx != nullptr) { + ctx->aes_ctr_ex_storage_meta_storage = aes_ctr_ex_storage_meta_storage; + ctx->aes_ctr_ex_storage_data_storage = storage; + ctx->fs_data_storage = storage; + } + } else { + // Create the appropriate storage for the encryption type. + switch (out_header_reader->GetEncryptionType()) { + case NcaFsHeader::EncryptionType::None: + // If there's no encryption, use the base storage we made previously. + break; + case NcaFsHeader::EncryptionType::AesXts: + R_TRY(this->CreateAesXtsStorage(std::addressof(storage), std::move(storage), + fs_data_offset)); + break; + case NcaFsHeader::EncryptionType::AesCtr: + R_TRY(this->CreateAesCtrStorage(std::addressof(storage), std::move(storage), + fs_data_offset, out_header_reader->GetAesCtrUpperIv(), + AlignmentStorageRequirement::None)); + break; + case NcaFsHeader::EncryptionType::AesCtrSkipLayerHash: { + // Create the aes ctr storage. + VirtualFile aes_ctr_storage; + R_TRY(this->CreateAesCtrStorage(std::addressof(aes_ctr_storage), storage, + fs_data_offset, out_header_reader->GetAesCtrUpperIv(), + AlignmentStorageRequirement::None)); + + // Create region switch storage. + R_TRY(this->CreateRegionSwitchStorage(std::addressof(storage), out_header_reader, + std::move(storage), std::move(aes_ctr_storage))); + } break; + default: + R_THROW(ResultInvalidNcaFsHeaderEncryptionType); + } + + // Potentially save storages to our context. + if (ctx != nullptr) { + ctx->fs_data_storage = storage; + } + } + + // Process indirect layer. + if (patch_info.HasIndirectTable()) { + // Create the indirect meta storage. + VirtualFile indirect_storage_meta_storage = patch_meta_indirect_meta_storage; + if (indirect_storage_meta_storage == nullptr) { + // If we don't have a meta storage, we must not have a patch meta hash layer. + ASSERT(!out_header_reader->ExistsPatchMetaHashLayer()); + + R_TRY(this->CreateIndirectStorageMetaStorage( + std::addressof(indirect_storage_meta_storage), storage, patch_info)); + } + + // Potentially save the indirect meta storage to our context. + if (ctx != nullptr) { + ctx->indirect_storage_meta_storage = indirect_storage_meta_storage; + } + + // Get the original indirectable storage. + VirtualFile original_indirectable_storage; + if (m_original_reader != nullptr && m_original_reader->HasFsInfo(fs_index)) { + // Create a driver for the original. + NcaFileSystemDriver original_driver(m_original_reader); + + // Create a header reader for the original. + NcaFsHeaderReader original_header_reader; + R_TRY(original_header_reader.Initialize(*m_original_reader, fs_index)); + + // Open original indirectable storage. + R_TRY(original_driver.OpenIndirectableStorageAsOriginal( + std::addressof(original_indirectable_storage), + std::addressof(original_header_reader), ctx)); + } else if (ctx != nullptr && ctx->external_original_storage != nullptr) { + // Use the external original storage. + original_indirectable_storage = ctx->external_original_storage; + } else { + // Allocate a dummy memory storage as original storage. + original_indirectable_storage = std::make_shared<VectorVfsFile>(); + R_UNLESS(original_indirectable_storage != nullptr, + ResultAllocationMemoryFailedAllocateShared); + } + + // Create the indirect storage. + VirtualFile indirect_storage; + R_TRY(this->CreateIndirectStorage( + std::addressof(indirect_storage), + ctx != nullptr ? std::addressof(ctx->indirect_storage) : nullptr, std::move(storage), + std::move(original_indirectable_storage), std::move(indirect_storage_meta_storage), + patch_info)); + + // Set storage as the indirect storage. + storage = std::move(indirect_storage); + } + + // Check if we're sparse or requested to skip the integrity layer. + if (out_header_reader->ExistsSparseLayer() || (ctx != nullptr && ctx->open_raw_storage)) { + *out = std::move(storage); + R_SUCCEED(); + } + + // Create the non-raw storage. + R_RETURN(this->CreateStorageByRawStorage(out, out_header_reader, std::move(storage), ctx)); +} + +Result NcaFileSystemDriver::CreateStorageByRawStorage(VirtualFile* out, + const NcaFsHeaderReader* header_reader, + VirtualFile raw_storage, + StorageContext* ctx) { + // Initialize storage as raw storage. + VirtualFile storage = std::move(raw_storage); + + // Process hash/integrity layer. + switch (header_reader->GetHashType()) { + case NcaFsHeader::HashType::HierarchicalSha256Hash: + R_TRY(this->CreateSha256Storage(std::addressof(storage), std::move(storage), + header_reader->GetHashData().hierarchical_sha256_data)); + break; + case NcaFsHeader::HashType::HierarchicalIntegrityHash: + R_TRY(this->CreateIntegrityVerificationStorage( + std::addressof(storage), std::move(storage), + header_reader->GetHashData().integrity_meta_info)); + break; + default: + R_THROW(ResultInvalidNcaFsHeaderHashType); + } + + // Process compression layer. + if (header_reader->ExistsCompressionLayer()) { + R_TRY(this->CreateCompressedStorage( + std::addressof(storage), + ctx != nullptr ? std::addressof(ctx->compressed_storage) : nullptr, + ctx != nullptr ? std::addressof(ctx->compressed_storage_meta_storage) : nullptr, + std::move(storage), header_reader->GetCompressionInfo())); + } + + // Set output storage. + *out = std::move(storage); + R_SUCCEED(); +} + +Result NcaFileSystemDriver::OpenIndirectableStorageAsOriginal( + VirtualFile* out, const NcaFsHeaderReader* header_reader, StorageContext* ctx) { + // Get the fs index. + const auto fs_index = header_reader->GetFsIndex(); + + // Declare the storage we're opening. + VirtualFile storage; + + // Process sparse layer. + s64 fs_data_offset = 0; + if (header_reader->ExistsSparseLayer()) { + // Get the sparse info. + const auto& sparse_info = header_reader->GetSparseInfo(); + + // Create based on whether we have a meta hash layer. + if (header_reader->ExistsSparseMetaHashLayer()) { + // Create the sparse storage with verification. + R_TRY(this->CreateSparseStorageWithVerification( + std::addressof(storage), std::addressof(fs_data_offset), + ctx != nullptr ? std::addressof(ctx->original_sparse_storage) : nullptr, + ctx != nullptr ? std::addressof(ctx->sparse_storage_meta_storage) : nullptr, + ctx != nullptr ? std::addressof(ctx->sparse_layer_info_storage) : nullptr, fs_index, + header_reader->GetAesCtrUpperIv(), sparse_info, + header_reader->GetSparseMetaDataHashDataInfo(), + header_reader->GetSparseMetaHashType())); + } else { + // Create the sparse storage. + R_TRY(this->CreateSparseStorage( + std::addressof(storage), std::addressof(fs_data_offset), + ctx != nullptr ? std::addressof(ctx->original_sparse_storage) : nullptr, + ctx != nullptr ? std::addressof(ctx->sparse_storage_meta_storage) : nullptr, + fs_index, header_reader->GetAesCtrUpperIv(), sparse_info)); + } + } else { + // Get the data offsets. + fs_data_offset = GetFsOffset(*m_reader, fs_index); + const auto fs_end_offset = GetFsEndOffset(*m_reader, fs_index); + + // Validate that we're within range. + const auto data_size = fs_end_offset - fs_data_offset; + R_UNLESS(data_size > 0, ResultInvalidNcaHeader); + + // Create the body substorage. + R_TRY(this->CreateBodySubStorage(std::addressof(storage), fs_data_offset, data_size)); + } + + // Create the appropriate storage for the encryption type. + switch (header_reader->GetEncryptionType()) { + case NcaFsHeader::EncryptionType::None: + // If there's no encryption, use the base storage we made previously. + break; + case NcaFsHeader::EncryptionType::AesXts: + R_TRY( + this->CreateAesXtsStorage(std::addressof(storage), std::move(storage), fs_data_offset)); + break; + case NcaFsHeader::EncryptionType::AesCtr: + R_TRY(this->CreateAesCtrStorage(std::addressof(storage), std::move(storage), fs_data_offset, + header_reader->GetAesCtrUpperIv(), + AlignmentStorageRequirement::CacheBlockSize)); + break; + default: + R_THROW(ResultInvalidNcaFsHeaderEncryptionType); + } + + // Set output storage. + *out = std::move(storage); + R_SUCCEED(); +} + +Result NcaFileSystemDriver::CreateBodySubStorage(VirtualFile* out, s64 offset, s64 size) { + // Create the body storage. + auto body_storage = + std::make_shared<SharedNcaBodyStorage>(m_reader->GetSharedBodyStorage(), m_reader); + R_UNLESS(body_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Get the body storage size. + s64 body_size = body_storage->GetSize(); + + // Check that we're within range. + R_UNLESS(offset + size <= body_size, ResultNcaBaseStorageOutOfRangeB); + + // Create substorage. + auto body_substorage = std::make_shared<OffsetVfsFile>(std::move(body_storage), size, offset); + R_UNLESS(body_substorage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Set the output storage. + *out = std::move(body_substorage); + R_SUCCEED(); +} + +Result NcaFileSystemDriver::CreateAesCtrStorage( + VirtualFile* out, VirtualFile base_storage, s64 offset, const NcaAesCtrUpperIv& upper_iv, + AlignmentStorageRequirement alignment_storage_requirement) { + // Check pre-conditions. + ASSERT(out != nullptr); + ASSERT(base_storage != nullptr); + + // Create the iv. + std::array<u8, AesCtrStorage::IvSize> iv{}; + AesCtrStorage::MakeIv(iv.data(), sizeof(iv), upper_iv.value, offset); + + // Create the ctr storage. + VirtualFile aes_ctr_storage; + if (m_reader->HasExternalDecryptionKey()) { + aes_ctr_storage = std::make_shared<AesCtrStorage>( + std::move(base_storage), m_reader->GetExternalDecryptionKey(), AesCtrStorage::KeySize, + iv.data(), AesCtrStorage::IvSize); + R_UNLESS(aes_ctr_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + } else { + // Create software decryption storage. + auto sw_storage = std::make_shared<AesCtrStorage>( + base_storage, m_reader->GetDecryptionKey(NcaHeader::DecryptionKey_AesCtr), + AesCtrStorage::KeySize, iv.data(), AesCtrStorage::IvSize); + R_UNLESS(sw_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + aes_ctr_storage = std::move(sw_storage); + } + + // Create alignment matching storage. + auto aligned_storage = std::make_shared<AlignmentMatchingStorage<NcaHeader::CtrBlockSize, 1>>( + std::move(aes_ctr_storage)); + R_UNLESS(aligned_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Set the out storage. + *out = std::move(aligned_storage); + R_SUCCEED(); +} + +Result NcaFileSystemDriver::CreateAesXtsStorage(VirtualFile* out, VirtualFile base_storage, + s64 offset) { + // Check pre-conditions. + ASSERT(out != nullptr); + ASSERT(base_storage != nullptr); + + // Create the iv. + std::array<u8, AesXtsStorage::IvSize> iv{}; + AesXtsStorage::MakeAesXtsIv(iv.data(), sizeof(iv), offset, NcaHeader::XtsBlockSize); + + // Make the aes xts storage. + const auto* const key1 = m_reader->GetDecryptionKey(NcaHeader::DecryptionKey_AesXts1); + const auto* const key2 = m_reader->GetDecryptionKey(NcaHeader::DecryptionKey_AesXts2); + auto xts_storage = + std::make_shared<AesXtsStorage>(std::move(base_storage), key1, key2, AesXtsStorage::KeySize, + iv.data(), AesXtsStorage::IvSize, NcaHeader::XtsBlockSize); + R_UNLESS(xts_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Create alignment matching storage. + auto aligned_storage = std::make_shared<AlignmentMatchingStorage<NcaHeader::XtsBlockSize, 1>>( + std::move(xts_storage)); + R_UNLESS(aligned_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Set the out storage. + *out = std::move(xts_storage); + R_SUCCEED(); +} + +Result NcaFileSystemDriver::CreateSparseStorageMetaStorage(VirtualFile* out, + VirtualFile base_storage, s64 offset, + const NcaAesCtrUpperIv& upper_iv, + const NcaSparseInfo& sparse_info) { + // Validate preconditions. + ASSERT(out != nullptr); + ASSERT(base_storage != nullptr); + + // Get the base storage size. + s64 base_size = base_storage->GetSize(); + + // Get the meta extents. + const auto meta_offset = sparse_info.bucket.offset; + const auto meta_size = sparse_info.bucket.size; + R_UNLESS(meta_offset + meta_size - offset <= base_size, ResultNcaBaseStorageOutOfRangeB); + + // Create the encrypted storage. + auto enc_storage = + std::make_shared<OffsetVfsFile>(std::move(base_storage), meta_size, meta_offset); + R_UNLESS(enc_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Create the decrypted storage. + VirtualFile decrypted_storage; + R_TRY(this->CreateAesCtrStorage(std::addressof(decrypted_storage), std::move(enc_storage), + offset + meta_offset, sparse_info.MakeAesCtrUpperIv(upper_iv), + AlignmentStorageRequirement::None)); + + // Create buffered storage. + std::vector<u8> meta_data(meta_size); + decrypted_storage->Read(meta_data.data(), meta_size, 0); + + auto buffered_storage = std::make_shared<VectorVfsFile>(std::move(meta_data)); + R_UNLESS(buffered_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Set the output. + *out = std::move(buffered_storage); + R_SUCCEED(); +} + +Result NcaFileSystemDriver::CreateSparseStorageCore(std::shared_ptr<SparseStorage>* out, + VirtualFile base_storage, s64 base_size, + VirtualFile meta_storage, + const NcaSparseInfo& sparse_info, + bool external_info) { + // Validate preconditions. + ASSERT(out != nullptr); + ASSERT(base_storage != nullptr); + ASSERT(meta_storage != nullptr); + + // Read and verify the bucket tree header. + BucketTree::Header header; + std::memcpy(std::addressof(header), sparse_info.bucket.header.data(), sizeof(header)); + R_TRY(header.Verify()); + + // Determine storage extents. + const auto node_offset = 0; + const auto node_size = SparseStorage::QueryNodeStorageSize(header.entry_count); + const auto entry_offset = node_offset + node_size; + const auto entry_size = SparseStorage::QueryEntryStorageSize(header.entry_count); + + // Create the sparse storage. + auto sparse_storage = std::make_shared<SparseStorage>(); + R_UNLESS(sparse_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Sanity check that we can be doing this. + ASSERT(header.entry_count != 0); + + // Initialize the sparse storage. + R_TRY(sparse_storage->Initialize( + std::make_shared<OffsetVfsFile>(meta_storage, node_size, node_offset), + std::make_shared<OffsetVfsFile>(meta_storage, entry_size, entry_offset), + header.entry_count)); + + // If not external, set the data storage. + if (!external_info) { + sparse_storage->SetDataStorage( + std::make_shared<OffsetVfsFile>(std::move(base_storage), base_size, 0)); + } + + // Set the output. + *out = std::move(sparse_storage); + R_SUCCEED(); +} + +Result NcaFileSystemDriver::CreateSparseStorage(VirtualFile* out, s64* out_fs_data_offset, + std::shared_ptr<SparseStorage>* out_sparse_storage, + VirtualFile* out_meta_storage, s32 index, + const NcaAesCtrUpperIv& upper_iv, + const NcaSparseInfo& sparse_info) { + // Validate preconditions. + ASSERT(out != nullptr); + ASSERT(out_fs_data_offset != nullptr); + + // Check the sparse info generation. + R_UNLESS(sparse_info.generation != 0, ResultInvalidNcaHeader); + + // Read and verify the bucket tree header. + BucketTree::Header header; + std::memcpy(std::addressof(header), sparse_info.bucket.header.data(), sizeof(header)); + R_TRY(header.Verify()); + + // Determine the storage extents. + const auto fs_offset = GetFsOffset(*m_reader, index); + const auto fs_end_offset = GetFsEndOffset(*m_reader, index); + const auto fs_size = fs_end_offset - fs_offset; + + // Create the sparse storage. + std::shared_ptr<SparseStorage> sparse_storage; + if (header.entry_count != 0) { + // Create the body substorage. + VirtualFile body_substorage; + R_TRY(this->CreateBodySubStorage(std::addressof(body_substorage), + sparse_info.physical_offset, + sparse_info.GetPhysicalSize())); + + // Create the meta storage. + VirtualFile meta_storage; + R_TRY(this->CreateSparseStorageMetaStorage(std::addressof(meta_storage), body_substorage, + sparse_info.physical_offset, upper_iv, + sparse_info)); + + // Potentially set the output meta storage. + if (out_meta_storage != nullptr) { + *out_meta_storage = meta_storage; + } + + // Create the sparse storage. + R_TRY(this->CreateSparseStorageCore(std::addressof(sparse_storage), body_substorage, + sparse_info.GetPhysicalSize(), std::move(meta_storage), + sparse_info, false)); + } else { + // If there are no entries, there's nothing to actually do. + sparse_storage = std::make_shared<SparseStorage>(); + R_UNLESS(sparse_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + sparse_storage->Initialize(fs_size); + } + + // Potentially set the output sparse storage. + if (out_sparse_storage != nullptr) { + *out_sparse_storage = sparse_storage; + } + + // Set the output fs data offset. + *out_fs_data_offset = fs_offset; + + // Set the output storage. + *out = std::move(sparse_storage); + R_SUCCEED(); +} + +Result NcaFileSystemDriver::CreateSparseStorageMetaStorageWithVerification( + VirtualFile* out, VirtualFile* out_layer_info_storage, VirtualFile base_storage, s64 offset, + const NcaAesCtrUpperIv& upper_iv, const NcaSparseInfo& sparse_info, + const NcaMetaDataHashDataInfo& meta_data_hash_data_info) { + // Validate preconditions. + ASSERT(out != nullptr); + ASSERT(base_storage != nullptr); + + // Get the base storage size. + s64 base_size = base_storage->GetSize(); + + // Get the meta extents. + const auto meta_offset = sparse_info.bucket.offset; + const auto meta_size = sparse_info.bucket.size; + R_UNLESS(meta_offset + meta_size - offset <= base_size, ResultNcaBaseStorageOutOfRangeB); + + // Get the meta data hash data extents. + const s64 meta_data_hash_data_offset = meta_data_hash_data_info.offset; + const s64 meta_data_hash_data_size = + Common::AlignUp<s64>(meta_data_hash_data_info.size, NcaHeader::CtrBlockSize); + R_UNLESS(meta_data_hash_data_offset + meta_data_hash_data_size <= base_size, + ResultNcaBaseStorageOutOfRangeB); + + // Check that the meta is before the hash data. + R_UNLESS(meta_offset + meta_size <= meta_data_hash_data_offset, + ResultRomNcaInvalidSparseMetaDataHashDataOffset); + + // Check that offsets are appropriately aligned. + R_UNLESS(Common::IsAligned<s64>(meta_data_hash_data_offset, NcaHeader::CtrBlockSize), + ResultRomNcaInvalidSparseMetaDataHashDataOffset); + R_UNLESS(Common::IsAligned<s64>(meta_offset, NcaHeader::CtrBlockSize), + ResultInvalidNcaFsHeader); + + // Create the meta storage. + auto enc_storage = std::make_shared<OffsetVfsFile>( + std::move(base_storage), + meta_data_hash_data_offset + meta_data_hash_data_size - meta_offset, meta_offset); + R_UNLESS(enc_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Create the decrypted storage. + VirtualFile decrypted_storage; + R_TRY(this->CreateAesCtrStorage(std::addressof(decrypted_storage), std::move(enc_storage), + offset + meta_offset, sparse_info.MakeAesCtrUpperIv(upper_iv), + AlignmentStorageRequirement::None)); + + // Create the verification storage. + VirtualFile integrity_storage; + Result rc = this->CreateIntegrityVerificationStorageForMeta( + std::addressof(integrity_storage), out_layer_info_storage, std::move(decrypted_storage), + meta_offset, meta_data_hash_data_info); + if (rc == ResultInvalidNcaMetaDataHashDataSize) { + R_THROW(ResultRomNcaInvalidSparseMetaDataHashDataSize); + } + if (rc == ResultInvalidNcaMetaDataHashDataHash) { + R_THROW(ResultRomNcaInvalidSparseMetaDataHashDataHash); + } + R_TRY(rc); + + // Create the meta storage. + auto meta_storage = std::make_shared<OffsetVfsFile>(std::move(integrity_storage), meta_size, 0); + R_UNLESS(meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Set the output. + *out = std::move(meta_storage); + R_SUCCEED(); +} + +Result NcaFileSystemDriver::CreateSparseStorageWithVerification( + VirtualFile* out, s64* out_fs_data_offset, std::shared_ptr<SparseStorage>* out_sparse_storage, + VirtualFile* out_meta_storage, VirtualFile* out_layer_info_storage, s32 index, + const NcaAesCtrUpperIv& upper_iv, const NcaSparseInfo& sparse_info, + const NcaMetaDataHashDataInfo& meta_data_hash_data_info, + NcaFsHeader::MetaDataHashType meta_data_hash_type) { + // Validate preconditions. + ASSERT(out != nullptr); + ASSERT(out_fs_data_offset != nullptr); + + // Check the sparse info generation. + R_UNLESS(sparse_info.generation != 0, ResultInvalidNcaHeader); + + // Read and verify the bucket tree header. + BucketTree::Header header; + std::memcpy(std::addressof(header), sparse_info.bucket.header.data(), sizeof(header)); + R_TRY(header.Verify()); + + // Determine the storage extents. + const auto fs_offset = GetFsOffset(*m_reader, index); + const auto fs_end_offset = GetFsEndOffset(*m_reader, index); + const auto fs_size = fs_end_offset - fs_offset; + + // Create the sparse storage. + std::shared_ptr<SparseStorage> sparse_storage; + if (header.entry_count != 0) { + // Create the body substorage. + VirtualFile body_substorage; + R_TRY(this->CreateBodySubStorage( + std::addressof(body_substorage), sparse_info.physical_offset, + Common::AlignUp<s64>(static_cast<s64>(meta_data_hash_data_info.offset) + + static_cast<s64>(meta_data_hash_data_info.size), + NcaHeader::CtrBlockSize))); + + // Check the meta data hash type. + R_UNLESS(meta_data_hash_type == NcaFsHeader::MetaDataHashType::HierarchicalIntegrity, + ResultRomNcaInvalidSparseMetaDataHashType); + + // Create the meta storage. + VirtualFile meta_storage; + R_TRY(this->CreateSparseStorageMetaStorageWithVerification( + std::addressof(meta_storage), out_layer_info_storage, body_substorage, + sparse_info.physical_offset, upper_iv, sparse_info, meta_data_hash_data_info)); + + // Potentially set the output meta storage. + if (out_meta_storage != nullptr) { + *out_meta_storage = meta_storage; + } + + // Create the sparse storage. + R_TRY(this->CreateSparseStorageCore(std::addressof(sparse_storage), body_substorage, + sparse_info.GetPhysicalSize(), std::move(meta_storage), + sparse_info, false)); + } else { + // If there are no entries, there's nothing to actually do. + sparse_storage = std::make_shared<SparseStorage>(); + R_UNLESS(sparse_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + sparse_storage->Initialize(fs_size); + } + + // Potentially set the output sparse storage. + if (out_sparse_storage != nullptr) { + *out_sparse_storage = sparse_storage; + } + + // Set the output fs data offset. + *out_fs_data_offset = fs_offset; + + // Set the output storage. + *out = std::move(sparse_storage); + R_SUCCEED(); +} + +Result NcaFileSystemDriver::CreateAesCtrExStorageMetaStorage( + VirtualFile* out, VirtualFile base_storage, s64 offset, + NcaFsHeader::EncryptionType encryption_type, const NcaAesCtrUpperIv& upper_iv, + const NcaPatchInfo& patch_info) { + // Validate preconditions. + ASSERT(out != nullptr); + ASSERT(base_storage != nullptr); + ASSERT(encryption_type == NcaFsHeader::EncryptionType::None || + encryption_type == NcaFsHeader::EncryptionType::AesCtrEx || + encryption_type == NcaFsHeader::EncryptionType::AesCtrExSkipLayerHash); + ASSERT(patch_info.HasAesCtrExTable()); + + // Validate patch info extents. + R_UNLESS(patch_info.indirect_size > 0, ResultInvalidNcaPatchInfoIndirectSize); + R_UNLESS(patch_info.aes_ctr_ex_size > 0, ResultInvalidNcaPatchInfoAesCtrExSize); + R_UNLESS(patch_info.indirect_size + patch_info.indirect_offset <= patch_info.aes_ctr_ex_offset, + ResultInvalidNcaPatchInfoAesCtrExOffset); + + // Get the base storage size. + s64 base_size = base_storage->GetSize(); + + // Get and validate the meta extents. + const s64 meta_offset = patch_info.aes_ctr_ex_offset; + const s64 meta_size = + Common::AlignUp(static_cast<s64>(patch_info.aes_ctr_ex_size), NcaHeader::XtsBlockSize); + R_UNLESS(meta_offset + meta_size <= base_size, ResultNcaBaseStorageOutOfRangeB); + + // Create the encrypted storage. + auto enc_storage = + std::make_shared<OffsetVfsFile>(std::move(base_storage), meta_size, meta_offset); + R_UNLESS(enc_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Create the decrypted storage. + VirtualFile decrypted_storage; + if (encryption_type != NcaFsHeader::EncryptionType::None) { + R_TRY(this->CreateAesCtrStorage(std::addressof(decrypted_storage), std::move(enc_storage), + offset + meta_offset, upper_iv, + AlignmentStorageRequirement::None)); + } else { + // If encryption type is none, don't do any decryption. + decrypted_storage = std::move(enc_storage); + } + + // Create meta storage. + auto meta_storage = std::make_shared<OffsetVfsFile>(decrypted_storage, meta_size, 0); + R_UNLESS(meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Create buffered storage. + std::vector<u8> meta_data(meta_size); + meta_storage->Read(meta_data.data(), meta_size, 0); + + auto buffered_storage = std::make_shared<VectorVfsFile>(std::move(meta_data)); + R_UNLESS(buffered_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Set the output. + *out = std::move(buffered_storage); + R_SUCCEED(); +} + +Result NcaFileSystemDriver::CreateAesCtrExStorage( + VirtualFile* out, std::shared_ptr<AesCtrCounterExtendedStorage>* out_ext, + VirtualFile base_storage, VirtualFile meta_storage, s64 counter_offset, + const NcaAesCtrUpperIv& upper_iv, const NcaPatchInfo& patch_info) { + // Validate pre-conditions. + ASSERT(out != nullptr); + ASSERT(base_storage != nullptr); + ASSERT(meta_storage != nullptr); + ASSERT(patch_info.HasAesCtrExTable()); + + // Read the bucket tree header. + BucketTree::Header header; + std::memcpy(std::addressof(header), patch_info.aes_ctr_ex_header.data(), sizeof(header)); + R_TRY(header.Verify()); + + // Determine the bucket extents. + const auto entry_count = header.entry_count; + const s64 data_offset = 0; + const s64 data_size = patch_info.aes_ctr_ex_offset; + const s64 node_offset = 0; + const s64 node_size = AesCtrCounterExtendedStorage::QueryNodeStorageSize(entry_count); + const s64 entry_offset = node_offset + node_size; + const s64 entry_size = AesCtrCounterExtendedStorage::QueryEntryStorageSize(entry_count); + + // Create bucket storages. + auto data_storage = + std::make_shared<OffsetVfsFile>(std::move(base_storage), data_size, data_offset); + auto node_storage = std::make_shared<OffsetVfsFile>(meta_storage, node_size, node_offset); + auto entry_storage = std::make_shared<OffsetVfsFile>(meta_storage, entry_size, entry_offset); + + // Get the secure value. + const auto secure_value = upper_iv.part.secure_value; + + // Create the aes ctr ex storage. + VirtualFile aes_ctr_ex_storage; + if (m_reader->HasExternalDecryptionKey()) { + // Create the decryptor. + std::unique_ptr<AesCtrCounterExtendedStorage::IDecryptor> decryptor; + R_TRY(AesCtrCounterExtendedStorage::CreateSoftwareDecryptor(std::addressof(decryptor))); + + // Create the aes ctr ex storage. + auto impl_storage = std::make_shared<AesCtrCounterExtendedStorage>(); + R_UNLESS(impl_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Initialize the aes ctr ex storage. + R_TRY(impl_storage->Initialize(m_reader->GetExternalDecryptionKey(), AesCtrStorage::KeySize, + secure_value, counter_offset, data_storage, node_storage, + entry_storage, entry_count, std::move(decryptor))); + + // Potentially set the output implementation storage. + if (out_ext != nullptr) { + *out_ext = impl_storage; + } + + // Set the implementation storage. + aes_ctr_ex_storage = std::move(impl_storage); + } else { + // Create the software decryptor. + std::unique_ptr<AesCtrCounterExtendedStorage::IDecryptor> sw_decryptor; + R_TRY(AesCtrCounterExtendedStorage::CreateSoftwareDecryptor(std::addressof(sw_decryptor))); + + // Make the software storage. + auto sw_storage = std::make_shared<AesCtrCounterExtendedStorage>(); + R_UNLESS(sw_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Initialize the software storage. + R_TRY(sw_storage->Initialize(m_reader->GetDecryptionKey(NcaHeader::DecryptionKey_AesCtr), + AesCtrStorage::KeySize, secure_value, counter_offset, + data_storage, node_storage, entry_storage, entry_count, + std::move(sw_decryptor))); + + // Potentially set the output implementation storage. + if (out_ext != nullptr) { + *out_ext = sw_storage; + } + + // Set the implementation storage. + aes_ctr_ex_storage = std::move(sw_storage); + } + + // Create an alignment-matching storage. + using AlignedStorage = AlignmentMatchingStorage<NcaHeader::CtrBlockSize, 1>; + auto aligned_storage = std::make_shared<AlignedStorage>(std::move(aes_ctr_ex_storage)); + R_UNLESS(aligned_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Set the output. + *out = std::move(aligned_storage); + R_SUCCEED(); +} + +Result NcaFileSystemDriver::CreateIndirectStorageMetaStorage(VirtualFile* out, + VirtualFile base_storage, + const NcaPatchInfo& patch_info) { + // Validate preconditions. + ASSERT(out != nullptr); + ASSERT(base_storage != nullptr); + ASSERT(patch_info.HasIndirectTable()); + + // Get the base storage size. + s64 base_size = base_storage->GetSize(); + + // Check that we're within range. + R_UNLESS(patch_info.indirect_offset + patch_info.indirect_size <= base_size, + ResultNcaBaseStorageOutOfRangeE); + + // Create the meta storage. + auto meta_storage = std::make_shared<OffsetVfsFile>(base_storage, patch_info.indirect_size, + patch_info.indirect_offset); + R_UNLESS(meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Create buffered storage. + std::vector<u8> meta_data(patch_info.indirect_size); + meta_storage->Read(meta_data.data(), patch_info.indirect_size, 0); + + auto buffered_storage = std::make_shared<VectorVfsFile>(std::move(meta_data)); + R_UNLESS(buffered_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Set the output. + *out = std::move(buffered_storage); + R_SUCCEED(); +} + +Result NcaFileSystemDriver::CreateIndirectStorage( + VirtualFile* out, std::shared_ptr<IndirectStorage>* out_ind, VirtualFile base_storage, + VirtualFile original_data_storage, VirtualFile meta_storage, const NcaPatchInfo& patch_info) { + // Validate preconditions. + ASSERT(out != nullptr); + ASSERT(base_storage != nullptr); + ASSERT(meta_storage != nullptr); + ASSERT(patch_info.HasIndirectTable()); + + // Read the bucket tree header. + BucketTree::Header header; + std::memcpy(std::addressof(header), patch_info.indirect_header.data(), sizeof(header)); + R_TRY(header.Verify()); + + // Determine the storage sizes. + const auto node_size = IndirectStorage::QueryNodeStorageSize(header.entry_count); + const auto entry_size = IndirectStorage::QueryEntryStorageSize(header.entry_count); + R_UNLESS(node_size + entry_size <= patch_info.indirect_size, + ResultInvalidNcaIndirectStorageOutOfRange); + + // Get the indirect data size. + const s64 indirect_data_size = patch_info.indirect_offset; + ASSERT(Common::IsAligned(indirect_data_size, NcaHeader::XtsBlockSize)); + + // Create the indirect data storage. + auto indirect_data_storage = + std::make_shared<OffsetVfsFile>(base_storage, indirect_data_size, 0); + R_UNLESS(indirect_data_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Create the indirect storage. + auto indirect_storage = std::make_shared<IndirectStorage>(); + R_UNLESS(indirect_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Initialize the indirect storage. + R_TRY(indirect_storage->Initialize( + std::make_shared<OffsetVfsFile>(meta_storage, node_size, 0), + std::make_shared<OffsetVfsFile>(meta_storage, entry_size, node_size), header.entry_count)); + + // Get the original data size. + s64 original_data_size = original_data_storage->GetSize(); + + // Set the indirect storages. + indirect_storage->SetStorage( + 0, std::make_shared<OffsetVfsFile>(original_data_storage, original_data_size, 0)); + indirect_storage->SetStorage( + 1, std::make_shared<OffsetVfsFile>(indirect_data_storage, indirect_data_size, 0)); + + // If necessary, set the output indirect storage. + if (out_ind != nullptr) { + *out_ind = indirect_storage; + } + + // Set the output. + *out = std::move(indirect_storage); + R_SUCCEED(); +} + +Result NcaFileSystemDriver::CreatePatchMetaStorage( + VirtualFile* out_aes_ctr_ex_meta, VirtualFile* out_indirect_meta, + VirtualFile* out_layer_info_storage, VirtualFile base_storage, s64 offset, + const NcaAesCtrUpperIv& upper_iv, const NcaPatchInfo& patch_info, + const NcaMetaDataHashDataInfo& meta_data_hash_data_info) { + // Validate preconditions. + ASSERT(out_aes_ctr_ex_meta != nullptr); + ASSERT(out_indirect_meta != nullptr); + ASSERT(base_storage != nullptr); + ASSERT(patch_info.HasAesCtrExTable()); + ASSERT(patch_info.HasIndirectTable()); + ASSERT(Common::IsAligned<s64>(patch_info.aes_ctr_ex_size, NcaHeader::XtsBlockSize)); + + // Validate patch info extents. + R_UNLESS(patch_info.indirect_size > 0, ResultInvalidNcaPatchInfoIndirectSize); + R_UNLESS(patch_info.aes_ctr_ex_size >= 0, ResultInvalidNcaPatchInfoAesCtrExSize); + R_UNLESS(patch_info.indirect_size + patch_info.indirect_offset <= patch_info.aes_ctr_ex_offset, + ResultInvalidNcaPatchInfoAesCtrExOffset); + R_UNLESS(patch_info.aes_ctr_ex_offset + patch_info.aes_ctr_ex_size <= + meta_data_hash_data_info.offset, + ResultRomNcaInvalidPatchMetaDataHashDataOffset); + + // Get the base storage size. + s64 base_size = base_storage->GetSize(); + + // Check that extents remain within range. + R_UNLESS(patch_info.indirect_offset + patch_info.indirect_size <= base_size, + ResultNcaBaseStorageOutOfRangeE); + R_UNLESS(patch_info.aes_ctr_ex_offset + patch_info.aes_ctr_ex_size <= base_size, + ResultNcaBaseStorageOutOfRangeB); + + // Check that metadata hash data extents remain within range. + const s64 meta_data_hash_data_offset = meta_data_hash_data_info.offset; + const s64 meta_data_hash_data_size = + Common::AlignUp<s64>(meta_data_hash_data_info.size, NcaHeader::CtrBlockSize); + R_UNLESS(meta_data_hash_data_offset + meta_data_hash_data_size <= base_size, + ResultNcaBaseStorageOutOfRangeB); + + // Create the encrypted storage. + auto enc_storage = std::make_shared<OffsetVfsFile>( + std::move(base_storage), + meta_data_hash_data_offset + meta_data_hash_data_size - patch_info.indirect_offset, + patch_info.indirect_offset); + R_UNLESS(enc_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Create the decrypted storage. + VirtualFile decrypted_storage; + R_TRY(this->CreateAesCtrStorage(std::addressof(decrypted_storage), std::move(enc_storage), + offset + patch_info.indirect_offset, upper_iv, + AlignmentStorageRequirement::None)); + + // Create the verification storage. + VirtualFile integrity_storage; + Result rc = this->CreateIntegrityVerificationStorageForMeta( + std::addressof(integrity_storage), out_layer_info_storage, std::move(decrypted_storage), + patch_info.indirect_offset, meta_data_hash_data_info); + if (rc == ResultInvalidNcaMetaDataHashDataSize) { + R_THROW(ResultRomNcaInvalidPatchMetaDataHashDataSize); + } + if (rc == ResultInvalidNcaMetaDataHashDataHash) { + R_THROW(ResultRomNcaInvalidPatchMetaDataHashDataHash); + } + R_TRY(rc); + + // Create the indirect meta storage. + auto indirect_meta_storage = + std::make_shared<OffsetVfsFile>(integrity_storage, patch_info.indirect_size, + patch_info.indirect_offset - patch_info.indirect_offset); + R_UNLESS(indirect_meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Create the aes ctr ex meta storage. + auto aes_ctr_ex_meta_storage = + std::make_shared<OffsetVfsFile>(integrity_storage, patch_info.aes_ctr_ex_size, + patch_info.aes_ctr_ex_offset - patch_info.indirect_offset); + R_UNLESS(aes_ctr_ex_meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Set the output. + *out_aes_ctr_ex_meta = std::move(aes_ctr_ex_meta_storage); + *out_indirect_meta = std::move(indirect_meta_storage); + R_SUCCEED(); +} + +Result NcaFileSystemDriver::CreateSha256Storage( + VirtualFile* out, VirtualFile base_storage, + const NcaFsHeader::HashData::HierarchicalSha256Data& hash_data) { + // Validate preconditions. + ASSERT(out != nullptr); + ASSERT(base_storage != nullptr); + + // Define storage types. + using VerificationStorage = HierarchicalSha256Storage; + + // Validate the hash data. + R_UNLESS(Common::IsPowerOfTwo(hash_data.hash_block_size), + ResultInvalidHierarchicalSha256BlockSize); + R_UNLESS(hash_data.hash_layer_count == VerificationStorage::LayerCount - 1, + ResultInvalidHierarchicalSha256LayerCount); + + // Get the regions. + const auto& hash_region = hash_data.hash_layer_region[0]; + const auto& data_region = hash_data.hash_layer_region[1]; + + // Determine buffer sizes. + constexpr s32 CacheBlockCount = 2; + const auto hash_buffer_size = static_cast<size_t>(hash_region.size); + const auto cache_buffer_size = CacheBlockCount * hash_data.hash_block_size; + const auto total_buffer_size = hash_buffer_size + cache_buffer_size; + + // Make a buffer holder storage. + auto buffer_hold_storage = std::make_shared<MemoryResourceBufferHoldStorage>( + std::move(base_storage), total_buffer_size); + R_UNLESS(buffer_hold_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + R_UNLESS(buffer_hold_storage->IsValid(), ResultAllocationMemoryFailedInNcaFileSystemDriverI); + + // Get storage size. + s64 base_size = buffer_hold_storage->GetSize(); + + // Check that we're within range. + R_UNLESS(hash_region.offset + hash_region.size <= base_size, ResultNcaBaseStorageOutOfRangeC); + R_UNLESS(data_region.offset + data_region.size <= base_size, ResultNcaBaseStorageOutOfRangeC); + + // Create the master hash storage. + auto master_hash_storage = + std::make_shared<ArrayVfsFile<sizeof(Hash)>>(hash_data.fs_data_master_hash.value); + + // Make the verification storage. + auto verification_storage = std::make_shared<VerificationStorage>(); + R_UNLESS(verification_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Make layer storages. + std::array<VirtualFile, VerificationStorage::LayerCount> layer_storages{ + std::make_shared<OffsetVfsFile>(master_hash_storage, sizeof(Hash), 0), + std::make_shared<OffsetVfsFile>(buffer_hold_storage, hash_region.size, hash_region.offset), + std::make_shared<OffsetVfsFile>(buffer_hold_storage, data_region.size, data_region.offset), + }; + + // Initialize the verification storage. + R_TRY(verification_storage->Initialize(layer_storages.data(), VerificationStorage::LayerCount, + hash_data.hash_block_size, + buffer_hold_storage->GetBuffer(), hash_buffer_size)); + + // Set the output. + *out = std::move(verification_storage); + R_SUCCEED(); +} + +Result NcaFileSystemDriver::CreateIntegrityVerificationStorage( + VirtualFile* out, VirtualFile base_storage, + const NcaFsHeader::HashData::IntegrityMetaInfo& meta_info) { + R_RETURN(this->CreateIntegrityVerificationStorageImpl( + out, base_storage, meta_info, 0, IntegrityDataCacheCount, IntegrityHashCacheCount, + HierarchicalIntegrityVerificationStorage::GetDefaultDataCacheBufferLevel( + meta_info.level_hash_info.max_layers))); +} + +Result NcaFileSystemDriver::CreateIntegrityVerificationStorageForMeta( + VirtualFile* out, VirtualFile* out_layer_info_storage, VirtualFile base_storage, s64 offset, + const NcaMetaDataHashDataInfo& meta_data_hash_data_info) { + // Validate preconditions. + ASSERT(out != nullptr); + + // Check the meta data hash data size. + R_UNLESS(meta_data_hash_data_info.size == sizeof(NcaMetaDataHashData), + ResultInvalidNcaMetaDataHashDataSize); + + // Read the meta data hash data. + NcaMetaDataHashData meta_data_hash_data; + base_storage->ReadObject(std::addressof(meta_data_hash_data), + meta_data_hash_data_info.offset - offset); + + // Set the out layer info storage, if necessary. + if (out_layer_info_storage != nullptr) { + auto layer_info_storage = std::make_shared<OffsetVfsFile>( + base_storage, + meta_data_hash_data_info.offset + meta_data_hash_data_info.size - + meta_data_hash_data.layer_info_offset, + meta_data_hash_data.layer_info_offset - offset); + R_UNLESS(layer_info_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + *out_layer_info_storage = std::move(layer_info_storage); + } + + // Create the meta storage. + auto meta_storage = std::make_shared<OffsetVfsFile>( + std::move(base_storage), meta_data_hash_data_info.offset - offset, 0); + R_UNLESS(meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Create the integrity verification storage. + R_RETURN(this->CreateIntegrityVerificationStorageImpl( + out, std::move(meta_storage), meta_data_hash_data.integrity_meta_info, + meta_data_hash_data.layer_info_offset - offset, IntegrityDataCacheCountForMeta, + IntegrityHashCacheCountForMeta, 0)); +} + +Result NcaFileSystemDriver::CreateIntegrityVerificationStorageImpl( + VirtualFile* out, VirtualFile base_storage, + const NcaFsHeader::HashData::IntegrityMetaInfo& meta_info, s64 layer_info_offset, + int max_data_cache_entries, int max_hash_cache_entries, s8 buffer_level) { + // Validate preconditions. + ASSERT(out != nullptr); + ASSERT(base_storage != nullptr); + ASSERT(layer_info_offset >= 0); + + // Define storage types. + using VerificationStorage = HierarchicalIntegrityVerificationStorage; + using StorageInfo = VerificationStorage::HierarchicalStorageInformation; + + // Validate the meta info. + HierarchicalIntegrityVerificationInformation level_hash_info; + std::memcpy(std::addressof(level_hash_info), std::addressof(meta_info.level_hash_info), + sizeof(level_hash_info)); + + R_UNLESS(IntegrityMinLayerCount <= level_hash_info.max_layers, + ResultInvalidNcaHierarchicalIntegrityVerificationLayerCount); + R_UNLESS(level_hash_info.max_layers <= IntegrityMaxLayerCount, + ResultInvalidNcaHierarchicalIntegrityVerificationLayerCount); + + // Get the base storage size. + s64 base_storage_size = base_storage->GetSize(); + + // Create storage info. + StorageInfo storage_info; + for (s32 i = 0; i < static_cast<s32>(level_hash_info.max_layers - 2); ++i) { + const auto& layer_info = level_hash_info.info[i]; + R_UNLESS(layer_info_offset + layer_info.offset + layer_info.size <= base_storage_size, + ResultNcaBaseStorageOutOfRangeD); + + storage_info[i + 1] = std::make_shared<OffsetVfsFile>( + base_storage, layer_info.size, layer_info_offset + layer_info.offset); + } + + // Set the last layer info. + const auto& layer_info = level_hash_info.info[level_hash_info.max_layers - 2]; + const s64 last_layer_info_offset = layer_info_offset > 0 ? 0LL : layer_info.offset.Get(); + R_UNLESS(last_layer_info_offset + layer_info.size <= base_storage_size, + ResultNcaBaseStorageOutOfRangeD); + if (layer_info_offset > 0) { + R_UNLESS(last_layer_info_offset + layer_info.size <= layer_info_offset, + ResultRomNcaInvalidIntegrityLayerInfoOffset); + } + storage_info.SetDataStorage(std::make_shared<OffsetVfsFile>( + std::move(base_storage), layer_info.size, last_layer_info_offset)); + + // Make the integrity romfs storage. + auto integrity_storage = std::make_shared<IntegrityRomFsStorage>(); + R_UNLESS(integrity_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Initialize the integrity storage. + R_TRY(integrity_storage->Initialize(level_hash_info, meta_info.master_hash, storage_info, + max_data_cache_entries, max_hash_cache_entries, + buffer_level)); + + // Set the output. + *out = std::move(integrity_storage); + R_SUCCEED(); +} + +Result NcaFileSystemDriver::CreateRegionSwitchStorage(VirtualFile* out, + const NcaFsHeaderReader* header_reader, + VirtualFile inside_storage, + VirtualFile outside_storage) { + // Check pre-conditions. + ASSERT(header_reader->GetHashType() == NcaFsHeader::HashType::HierarchicalIntegrityHash); + + // Create the region. + RegionSwitchStorage::Region region = {}; + R_TRY(header_reader->GetHashTargetOffset(std::addressof(region.size))); + + // Create the region switch storage. + auto region_switch_storage = std::make_shared<RegionSwitchStorage>( + std::move(inside_storage), std::move(outside_storage), region); + R_UNLESS(region_switch_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Set the output. + *out = std::move(region_switch_storage); + R_SUCCEED(); +} + +Result NcaFileSystemDriver::CreateCompressedStorage(VirtualFile* out, + std::shared_ptr<CompressedStorage>* out_cmp, + VirtualFile* out_meta, VirtualFile base_storage, + const NcaCompressionInfo& compression_info) { + R_RETURN(this->CreateCompressedStorage(out, out_cmp, out_meta, std::move(base_storage), + compression_info, m_reader->GetDecompressor())); +} + +Result NcaFileSystemDriver::CreateCompressedStorage(VirtualFile* out, + std::shared_ptr<CompressedStorage>* out_cmp, + VirtualFile* out_meta, VirtualFile base_storage, + const NcaCompressionInfo& compression_info, + GetDecompressorFunction get_decompressor) { + // Check pre-conditions. + ASSERT(out != nullptr); + ASSERT(base_storage != nullptr); + ASSERT(get_decompressor != nullptr); + + // Read and verify the bucket tree header. + BucketTree::Header header; + std::memcpy(std::addressof(header), compression_info.bucket.header.data(), sizeof(header)); + R_TRY(header.Verify()); + + // Determine the storage extents. + const auto table_offset = compression_info.bucket.offset; + const auto table_size = compression_info.bucket.size; + const auto node_size = CompressedStorage::QueryNodeStorageSize(header.entry_count); + const auto entry_size = CompressedStorage::QueryEntryStorageSize(header.entry_count); + R_UNLESS(node_size + entry_size <= table_size, ResultInvalidCompressedStorageSize); + + // If we should, set the output meta storage. + if (out_meta != nullptr) { + auto meta_storage = std::make_shared<OffsetVfsFile>(base_storage, table_size, table_offset); + R_UNLESS(meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + *out_meta = std::move(meta_storage); + } + + // Allocate the compressed storage. + auto compressed_storage = std::make_shared<CompressedStorage>(); + R_UNLESS(compressed_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Initialize the compressed storage. + R_TRY(compressed_storage->Initialize( + std::make_shared<OffsetVfsFile>(base_storage, table_offset, 0), + std::make_shared<OffsetVfsFile>(base_storage, node_size, table_offset), + std::make_shared<OffsetVfsFile>(base_storage, entry_size, table_offset + node_size), + header.entry_count, 64_KiB, 640_KiB, get_decompressor, 16_KiB, 16_KiB, 32)); + + // Potentially set the output compressed storage. + if (out_cmp) { + *out_cmp = compressed_storage; + } + + // Set the output. + *out = std::move(compressed_storage); + R_SUCCEED(); +} + +} // namespace FileSys diff --git a/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.h b/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.h new file mode 100644 index 000000000..5771a21fc --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.h @@ -0,0 +1,364 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "core/file_sys/fssystem/fssystem_compression_common.h" +#include "core/file_sys/fssystem/fssystem_nca_header.h" +#include "core/file_sys/vfs.h" + +namespace FileSys { + +class CompressedStorage; +class AesCtrCounterExtendedStorage; +class IndirectStorage; +class SparseStorage; + +struct NcaCryptoConfiguration; + +using KeyGenerationFunction = void (*)(void* dst_key, size_t dst_key_size, const void* src_key, + size_t src_key_size, s32 key_type); +using VerifySign1Function = bool (*)(const void* sig, size_t sig_size, const void* data, + size_t data_size, u8 generation); + +struct NcaCryptoConfiguration { + static constexpr size_t Rsa2048KeyModulusSize = 2048 / 8; + static constexpr size_t Rsa2048KeyPublicExponentSize = 3; + static constexpr size_t Rsa2048KeyPrivateExponentSize = Rsa2048KeyModulusSize; + + static constexpr size_t Aes128KeySize = 128 / 8; + + static constexpr size_t Header1SignatureKeyGenerationMax = 1; + + static constexpr s32 KeyAreaEncryptionKeyIndexCount = 3; + static constexpr s32 HeaderEncryptionKeyCount = 2; + + static constexpr u8 KeyAreaEncryptionKeyIndexZeroKey = 0xFF; + + static constexpr size_t KeyGenerationMax = 32; + + std::array<const u8*, Header1SignatureKeyGenerationMax + 1> header_1_sign_key_moduli; + std::array<u8, Rsa2048KeyPublicExponentSize> header_1_sign_key_public_exponent; + std::array<std::array<u8, Aes128KeySize>, KeyAreaEncryptionKeyIndexCount> + key_area_encryption_key_source; + std::array<u8, Aes128KeySize> header_encryption_key_source; + std::array<std::array<u8, Aes128KeySize>, HeaderEncryptionKeyCount> + header_encrypted_encryption_keys; + KeyGenerationFunction generate_key; + VerifySign1Function verify_sign1; + bool is_plaintext_header_available; + bool is_available_sw_key; +}; +static_assert(std::is_trivial_v<NcaCryptoConfiguration>); + +struct NcaCompressionConfiguration { + GetDecompressorFunction get_decompressor; +}; +static_assert(std::is_trivial_v<NcaCompressionConfiguration>); + +constexpr inline s32 KeyAreaEncryptionKeyCount = + NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount * + NcaCryptoConfiguration::KeyGenerationMax; + +enum class KeyType : s32 { + ZeroKey = -2, + InvalidKey = -1, + NcaHeaderKey1 = KeyAreaEncryptionKeyCount + 0, + NcaHeaderKey2 = KeyAreaEncryptionKeyCount + 1, + NcaExternalKey = KeyAreaEncryptionKeyCount + 2, + SaveDataDeviceUniqueMac = KeyAreaEncryptionKeyCount + 3, + SaveDataSeedUniqueMac = KeyAreaEncryptionKeyCount + 4, + SaveDataTransferMac = KeyAreaEncryptionKeyCount + 5, +}; + +constexpr inline bool IsInvalidKeyTypeValue(s32 key_type) { + return key_type < 0; +} + +constexpr inline s32 GetKeyTypeValue(u8 key_index, u8 key_generation) { + if (key_index == NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexZeroKey) { + return static_cast<s32>(KeyType::ZeroKey); + } + + if (key_index >= NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount) { + return static_cast<s32>(KeyType::InvalidKey); + } + + return NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount * key_generation + key_index; +} + +class NcaReader { + YUZU_NON_COPYABLE(NcaReader); + YUZU_NON_MOVEABLE(NcaReader); + +public: + NcaReader(); + ~NcaReader(); + + Result Initialize(VirtualFile base_storage, const NcaCryptoConfiguration& crypto_cfg, + const NcaCompressionConfiguration& compression_cfg); + + VirtualFile GetSharedBodyStorage(); + u32 GetMagic() const; + NcaHeader::DistributionType GetDistributionType() const; + NcaHeader::ContentType GetContentType() const; + u8 GetHeaderSign1KeyGeneration() const; + u8 GetKeyGeneration() const; + u8 GetKeyIndex() const; + u64 GetContentSize() const; + u64 GetProgramId() const; + u32 GetContentIndex() const; + u32 GetSdkAddonVersion() const; + void GetRightsId(u8* dst, size_t dst_size) const; + bool HasFsInfo(s32 index) const; + s32 GetFsCount() const; + const Hash& GetFsHeaderHash(s32 index) const; + void GetFsHeaderHash(Hash* dst, s32 index) const; + void GetFsInfo(NcaHeader::FsInfo* dst, s32 index) const; + u64 GetFsOffset(s32 index) const; + u64 GetFsEndOffset(s32 index) const; + u64 GetFsSize(s32 index) const; + void GetEncryptedKey(void* dst, size_t size) const; + const void* GetDecryptionKey(s32 index) const; + bool HasValidInternalKey() const; + bool HasInternalDecryptionKeyForAesHw() const; + bool IsSoftwareAesPrioritized() const; + void PrioritizeSoftwareAes(); + bool IsAvailableSwKey() const; + bool HasExternalDecryptionKey() const; + const void* GetExternalDecryptionKey() const; + void SetExternalDecryptionKey(const void* src, size_t size); + void GetRawData(void* dst, size_t dst_size) const; + NcaHeader::EncryptionType GetEncryptionType() const; + Result ReadHeader(NcaFsHeader* dst, s32 index) const; + + GetDecompressorFunction GetDecompressor() const; + + bool GetHeaderSign1Valid() const; + + void GetHeaderSign2(void* dst, size_t size) const; + +private: + NcaHeader m_header; + std::array<std::array<u8, NcaCryptoConfiguration::Aes128KeySize>, + NcaHeader::DecryptionKey_Count> + m_decryption_keys; + VirtualFile m_body_storage; + VirtualFile m_header_storage; + std::array<u8, NcaCryptoConfiguration::Aes128KeySize> m_external_decryption_key; + bool m_is_software_aes_prioritized; + bool m_is_available_sw_key; + NcaHeader::EncryptionType m_header_encryption_type; + bool m_is_header_sign1_signature_valid; + GetDecompressorFunction m_get_decompressor; +}; + +class NcaFsHeaderReader { + YUZU_NON_COPYABLE(NcaFsHeaderReader); + YUZU_NON_MOVEABLE(NcaFsHeaderReader); + +public: + NcaFsHeaderReader() : m_fs_index(-1) { + std::memset(std::addressof(m_data), 0, sizeof(m_data)); + } + + Result Initialize(const NcaReader& reader, s32 index); + bool IsInitialized() const { + return m_fs_index >= 0; + } + + void GetRawData(void* dst, size_t dst_size) const; + + NcaFsHeader::HashData& GetHashData(); + const NcaFsHeader::HashData& GetHashData() const; + u16 GetVersion() const; + s32 GetFsIndex() const; + NcaFsHeader::FsType GetFsType() const; + NcaFsHeader::HashType GetHashType() const; + NcaFsHeader::EncryptionType GetEncryptionType() const; + NcaPatchInfo& GetPatchInfo(); + const NcaPatchInfo& GetPatchInfo() const; + const NcaAesCtrUpperIv GetAesCtrUpperIv() const; + + bool IsSkipLayerHashEncryption() const; + Result GetHashTargetOffset(s64* out) const; + + bool ExistsSparseLayer() const; + NcaSparseInfo& GetSparseInfo(); + const NcaSparseInfo& GetSparseInfo() const; + + bool ExistsCompressionLayer() const; + NcaCompressionInfo& GetCompressionInfo(); + const NcaCompressionInfo& GetCompressionInfo() const; + + bool ExistsPatchMetaHashLayer() const; + NcaMetaDataHashDataInfo& GetPatchMetaDataHashDataInfo(); + const NcaMetaDataHashDataInfo& GetPatchMetaDataHashDataInfo() const; + NcaFsHeader::MetaDataHashType GetPatchMetaHashType() const; + + bool ExistsSparseMetaHashLayer() const; + NcaMetaDataHashDataInfo& GetSparseMetaDataHashDataInfo(); + const NcaMetaDataHashDataInfo& GetSparseMetaDataHashDataInfo() const; + NcaFsHeader::MetaDataHashType GetSparseMetaHashType() const; + +private: + NcaFsHeader m_data; + s32 m_fs_index; +}; + +class NcaFileSystemDriver { + YUZU_NON_COPYABLE(NcaFileSystemDriver); + YUZU_NON_MOVEABLE(NcaFileSystemDriver); + +public: + struct StorageContext { + bool open_raw_storage; + VirtualFile body_substorage; + std::shared_ptr<SparseStorage> current_sparse_storage; + VirtualFile sparse_storage_meta_storage; + std::shared_ptr<SparseStorage> original_sparse_storage; + void* external_current_sparse_storage; + void* external_original_sparse_storage; + VirtualFile aes_ctr_ex_storage_meta_storage; + VirtualFile aes_ctr_ex_storage_data_storage; + std::shared_ptr<AesCtrCounterExtendedStorage> aes_ctr_ex_storage; + VirtualFile indirect_storage_meta_storage; + std::shared_ptr<IndirectStorage> indirect_storage; + VirtualFile fs_data_storage; + VirtualFile compressed_storage_meta_storage; + std::shared_ptr<CompressedStorage> compressed_storage; + + VirtualFile patch_layer_info_storage; + VirtualFile sparse_layer_info_storage; + + VirtualFile external_original_storage; + }; + +private: + enum class AlignmentStorageRequirement { + CacheBlockSize = 0, + None = 1, + }; + +public: + static Result SetupFsHeaderReader(NcaFsHeaderReader* out, const NcaReader& reader, + s32 fs_index); + +public: + NcaFileSystemDriver(std::shared_ptr<NcaReader> reader) : m_original_reader(), m_reader(reader) { + ASSERT(m_reader != nullptr); + } + + NcaFileSystemDriver(std::shared_ptr<NcaReader> original_reader, + std::shared_ptr<NcaReader> reader) + : m_original_reader(original_reader), m_reader(reader) { + ASSERT(m_reader != nullptr); + } + + Result OpenStorageWithContext(VirtualFile* out, NcaFsHeaderReader* out_header_reader, + s32 fs_index, StorageContext* ctx); + + Result OpenStorage(VirtualFile* out, NcaFsHeaderReader* out_header_reader, s32 fs_index) { + // Create a storage context. + StorageContext ctx{}; + + // Open the storage. + R_RETURN(OpenStorageWithContext(out, out_header_reader, fs_index, std::addressof(ctx))); + } + +public: + Result CreateStorageByRawStorage(VirtualFile* out, const NcaFsHeaderReader* header_reader, + VirtualFile raw_storage, StorageContext* ctx); + +private: + Result OpenStorageImpl(VirtualFile* out, NcaFsHeaderReader* out_header_reader, s32 fs_index, + StorageContext* ctx); + + Result OpenIndirectableStorageAsOriginal(VirtualFile* out, + const NcaFsHeaderReader* header_reader, + StorageContext* ctx); + + Result CreateBodySubStorage(VirtualFile* out, s64 offset, s64 size); + + Result CreateAesCtrStorage(VirtualFile* out, VirtualFile base_storage, s64 offset, + const NcaAesCtrUpperIv& upper_iv, + AlignmentStorageRequirement alignment_storage_requirement); + Result CreateAesXtsStorage(VirtualFile* out, VirtualFile base_storage, s64 offset); + + Result CreateSparseStorageMetaStorage(VirtualFile* out, VirtualFile base_storage, s64 offset, + const NcaAesCtrUpperIv& upper_iv, + const NcaSparseInfo& sparse_info); + Result CreateSparseStorageCore(std::shared_ptr<SparseStorage>* out, VirtualFile base_storage, + s64 base_size, VirtualFile meta_storage, + const NcaSparseInfo& sparse_info, bool external_info); + Result CreateSparseStorage(VirtualFile* out, s64* out_fs_data_offset, + std::shared_ptr<SparseStorage>* out_sparse_storage, + VirtualFile* out_meta_storage, s32 index, + const NcaAesCtrUpperIv& upper_iv, const NcaSparseInfo& sparse_info); + + Result CreateSparseStorageMetaStorageWithVerification( + VirtualFile* out, VirtualFile* out_verification, VirtualFile base_storage, s64 offset, + const NcaAesCtrUpperIv& upper_iv, const NcaSparseInfo& sparse_info, + const NcaMetaDataHashDataInfo& meta_data_hash_data_info); + Result CreateSparseStorageWithVerification( + VirtualFile* out, s64* out_fs_data_offset, + std::shared_ptr<SparseStorage>* out_sparse_storage, VirtualFile* out_meta_storage, + VirtualFile* out_verification, s32 index, const NcaAesCtrUpperIv& upper_iv, + const NcaSparseInfo& sparse_info, const NcaMetaDataHashDataInfo& meta_data_hash_data_info, + NcaFsHeader::MetaDataHashType meta_data_hash_type); + + Result CreateAesCtrExStorageMetaStorage(VirtualFile* out, VirtualFile base_storage, s64 offset, + NcaFsHeader::EncryptionType encryption_type, + const NcaAesCtrUpperIv& upper_iv, + const NcaPatchInfo& patch_info); + Result CreateAesCtrExStorage(VirtualFile* out, + std::shared_ptr<AesCtrCounterExtendedStorage>* out_ext, + VirtualFile base_storage, VirtualFile meta_storage, + s64 counter_offset, const NcaAesCtrUpperIv& upper_iv, + const NcaPatchInfo& patch_info); + + Result CreateIndirectStorageMetaStorage(VirtualFile* out, VirtualFile base_storage, + const NcaPatchInfo& patch_info); + Result CreateIndirectStorage(VirtualFile* out, std::shared_ptr<IndirectStorage>* out_ind, + VirtualFile base_storage, VirtualFile original_data_storage, + VirtualFile meta_storage, const NcaPatchInfo& patch_info); + + Result CreatePatchMetaStorage(VirtualFile* out_aes_ctr_ex_meta, VirtualFile* out_indirect_meta, + VirtualFile* out_verification, VirtualFile base_storage, + s64 offset, const NcaAesCtrUpperIv& upper_iv, + const NcaPatchInfo& patch_info, + const NcaMetaDataHashDataInfo& meta_data_hash_data_info); + + Result CreateSha256Storage(VirtualFile* out, VirtualFile base_storage, + const NcaFsHeader::HashData::HierarchicalSha256Data& sha256_data); + + Result CreateIntegrityVerificationStorage( + VirtualFile* out, VirtualFile base_storage, + const NcaFsHeader::HashData::IntegrityMetaInfo& meta_info); + Result CreateIntegrityVerificationStorageForMeta( + VirtualFile* out, VirtualFile* out_verification, VirtualFile base_storage, s64 offset, + const NcaMetaDataHashDataInfo& meta_data_hash_data_info); + Result CreateIntegrityVerificationStorageImpl( + VirtualFile* out, VirtualFile base_storage, + const NcaFsHeader::HashData::IntegrityMetaInfo& meta_info, s64 layer_info_offset, + int max_data_cache_entries, int max_hash_cache_entries, s8 buffer_level); + + Result CreateRegionSwitchStorage(VirtualFile* out, const NcaFsHeaderReader* header_reader, + VirtualFile inside_storage, VirtualFile outside_storage); + + Result CreateCompressedStorage(VirtualFile* out, std::shared_ptr<CompressedStorage>* out_cmp, + VirtualFile* out_meta, VirtualFile base_storage, + const NcaCompressionInfo& compression_info); + +public: + Result CreateCompressedStorage(VirtualFile* out, std::shared_ptr<CompressedStorage>* out_cmp, + VirtualFile* out_meta, VirtualFile base_storage, + const NcaCompressionInfo& compression_info, + GetDecompressorFunction get_decompressor); + +private: + std::shared_ptr<NcaReader> m_original_reader; + std::shared_ptr<NcaReader> m_reader; +}; + +} // namespace FileSys diff --git a/src/core/file_sys/fssystem/fssystem_nca_header.cpp b/src/core/file_sys/fssystem/fssystem_nca_header.cpp new file mode 100644 index 000000000..bf5742d39 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_nca_header.cpp @@ -0,0 +1,20 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/file_sys/fssystem/fssystem_nca_header.h" + +namespace FileSys { + +u8 NcaHeader::GetProperKeyGeneration() const { + return std::max(this->key_generation, this->key_generation_2); +} + +bool NcaPatchInfo::HasIndirectTable() const { + return this->indirect_size != 0; +} + +bool NcaPatchInfo::HasAesCtrExTable() const { + return this->aes_ctr_ex_size != 0; +} + +} // namespace FileSys diff --git a/src/core/file_sys/fssystem/fssystem_nca_header.h b/src/core/file_sys/fssystem/fssystem_nca_header.h new file mode 100644 index 000000000..a02c5d881 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_nca_header.h @@ -0,0 +1,338 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "common/literals.h" + +#include "core/file_sys/errors.h" +#include "core/file_sys/fssystem/fs_types.h" + +namespace FileSys { + +using namespace Common::Literals; + +struct Hash { + static constexpr std::size_t Size = 256 / 8; + std::array<u8, Size> value; +}; +static_assert(sizeof(Hash) == Hash::Size); +static_assert(std::is_trivial_v<Hash>); + +using NcaDigest = Hash; + +struct NcaHeader { + enum class ContentType : u8 { + Program = 0, + Meta = 1, + Control = 2, + Manual = 3, + Data = 4, + PublicData = 5, + + Start = Program, + End = PublicData, + }; + + enum class DistributionType : u8 { + Download = 0, + GameCard = 1, + + Start = Download, + End = GameCard, + }; + + enum class EncryptionType : u8 { + Auto = 0, + None = 1, + }; + + enum DecryptionKey { + DecryptionKey_AesXts = 0, + DecryptionKey_AesXts1 = DecryptionKey_AesXts, + DecryptionKey_AesXts2 = 1, + DecryptionKey_AesCtr = 2, + DecryptionKey_AesCtrEx = 3, + DecryptionKey_AesCtrHw = 4, + DecryptionKey_Count, + }; + + struct FsInfo { + u32 start_sector; + u32 end_sector; + u32 hash_sectors; + u32 reserved; + }; + static_assert(sizeof(FsInfo) == 0x10); + static_assert(std::is_trivial_v<FsInfo>); + + static constexpr u32 Magic0 = Common::MakeMagic('N', 'C', 'A', '0'); + static constexpr u32 Magic1 = Common::MakeMagic('N', 'C', 'A', '1'); + static constexpr u32 Magic2 = Common::MakeMagic('N', 'C', 'A', '2'); + static constexpr u32 Magic3 = Common::MakeMagic('N', 'C', 'A', '3'); + + static constexpr u32 Magic = Magic3; + + static constexpr std::size_t Size = 1_KiB; + static constexpr s32 FsCountMax = 4; + static constexpr std::size_t HeaderSignCount = 2; + static constexpr std::size_t HeaderSignSize = 0x100; + static constexpr std::size_t EncryptedKeyAreaSize = 0x100; + static constexpr std::size_t SectorSize = 0x200; + static constexpr std::size_t SectorShift = 9; + static constexpr std::size_t RightsIdSize = 0x10; + static constexpr std::size_t XtsBlockSize = 0x200; + static constexpr std::size_t CtrBlockSize = 0x10; + + static_assert(SectorSize == (1 << SectorShift)); + + // Data members. + std::array<u8, HeaderSignSize> header_sign_1; + std::array<u8, HeaderSignSize> header_sign_2; + u32 magic; + DistributionType distribution_type; + ContentType content_type; + u8 key_generation; + u8 key_index; + u64 content_size; + u64 program_id; + u32 content_index; + u32 sdk_addon_version; + u8 key_generation_2; + u8 header1_signature_key_generation; + std::array<u8, 2> reserved_222; + std::array<u32, 3> reserved_224; + std::array<u8, RightsIdSize> rights_id; + std::array<FsInfo, FsCountMax> fs_info; + std::array<Hash, FsCountMax> fs_header_hash; + std::array<u8, EncryptedKeyAreaSize> encrypted_key_area; + + static constexpr u64 SectorToByte(u32 sector) { + return static_cast<u64>(sector) << SectorShift; + } + + static constexpr u32 ByteToSector(u64 byte) { + return static_cast<u32>(byte >> SectorShift); + } + + u8 GetProperKeyGeneration() const; +}; +static_assert(sizeof(NcaHeader) == NcaHeader::Size); +static_assert(std::is_trivial_v<NcaHeader>); + +struct NcaBucketInfo { + static constexpr size_t HeaderSize = 0x10; + Int64 offset; + Int64 size; + std::array<u8, HeaderSize> header; +}; +static_assert(std::is_trivial_v<NcaBucketInfo>); + +struct NcaPatchInfo { + static constexpr size_t Size = 0x40; + static constexpr size_t Offset = 0x100; + + Int64 indirect_offset; + Int64 indirect_size; + std::array<u8, NcaBucketInfo::HeaderSize> indirect_header; + Int64 aes_ctr_ex_offset; + Int64 aes_ctr_ex_size; + std::array<u8, NcaBucketInfo::HeaderSize> aes_ctr_ex_header; + + bool HasIndirectTable() const; + bool HasAesCtrExTable() const; +}; +static_assert(std::is_trivial_v<NcaPatchInfo>); + +union NcaAesCtrUpperIv { + u64 value; + struct { + u32 generation; + u32 secure_value; + } part; +}; +static_assert(std::is_trivial_v<NcaAesCtrUpperIv>); + +struct NcaSparseInfo { + NcaBucketInfo bucket; + Int64 physical_offset; + u16 generation; + std::array<u8, 6> reserved; + + s64 GetPhysicalSize() const { + return this->bucket.offset + this->bucket.size; + } + + u32 GetGeneration() const { + return static_cast<u32>(this->generation) << 16; + } + + const NcaAesCtrUpperIv MakeAesCtrUpperIv(NcaAesCtrUpperIv upper_iv) const { + NcaAesCtrUpperIv sparse_upper_iv = upper_iv; + sparse_upper_iv.part.generation = this->GetGeneration(); + return sparse_upper_iv; + } +}; +static_assert(std::is_trivial_v<NcaSparseInfo>); + +struct NcaCompressionInfo { + NcaBucketInfo bucket; + std::array<u8, 8> resreved; +}; +static_assert(std::is_trivial_v<NcaCompressionInfo>); + +struct NcaMetaDataHashDataInfo { + Int64 offset; + Int64 size; + Hash hash; +}; +static_assert(std::is_trivial_v<NcaMetaDataHashDataInfo>); + +struct NcaFsHeader { + static constexpr size_t Size = 0x200; + static constexpr size_t HashDataOffset = 0x8; + + struct Region { + Int64 offset; + Int64 size; + }; + static_assert(std::is_trivial_v<Region>); + + enum class FsType : u8 { + RomFs = 0, + PartitionFs = 1, + }; + + enum class EncryptionType : u8 { + Auto = 0, + None = 1, + AesXts = 2, + AesCtr = 3, + AesCtrEx = 4, + AesCtrSkipLayerHash = 5, + AesCtrExSkipLayerHash = 6, + }; + + enum class HashType : u8 { + Auto = 0, + None = 1, + HierarchicalSha256Hash = 2, + HierarchicalIntegrityHash = 3, + AutoSha3 = 4, + HierarchicalSha3256Hash = 5, + HierarchicalIntegritySha3Hash = 6, + }; + + enum class MetaDataHashType : u8 { + None = 0, + HierarchicalIntegrity = 1, + }; + + union HashData { + struct HierarchicalSha256Data { + static constexpr size_t HashLayerCountMax = 5; + static const size_t MasterHashOffset; + + Hash fs_data_master_hash; + s32 hash_block_size; + s32 hash_layer_count; + std::array<Region, HashLayerCountMax> hash_layer_region; + } hierarchical_sha256_data; + static_assert(std::is_trivial_v<HierarchicalSha256Data>); + + struct IntegrityMetaInfo { + static const size_t MasterHashOffset; + + u32 magic; + u32 version; + u32 master_hash_size; + + struct LevelHashInfo { + u32 max_layers; + + struct HierarchicalIntegrityVerificationLevelInformation { + static constexpr size_t IntegrityMaxLayerCount = 7; + Int64 offset; + Int64 size; + s32 block_order; + std::array<u8, 4> reserved; + }; + std::array< + HierarchicalIntegrityVerificationLevelInformation, + HierarchicalIntegrityVerificationLevelInformation::IntegrityMaxLayerCount - 1> + info; + + struct SignatureSalt { + static constexpr size_t Size = 0x20; + std::array<u8, Size> value; + }; + SignatureSalt seed; + } level_hash_info; + + Hash master_hash; + } integrity_meta_info; + static_assert(std::is_trivial_v<IntegrityMetaInfo>); + + std::array<u8, NcaPatchInfo::Offset - HashDataOffset> padding; + }; + + u16 version; + FsType fs_type; + HashType hash_type; + EncryptionType encryption_type; + MetaDataHashType meta_data_hash_type; + std::array<u8, 2> reserved; + HashData hash_data; + NcaPatchInfo patch_info; + NcaAesCtrUpperIv aes_ctr_upper_iv; + NcaSparseInfo sparse_info; + NcaCompressionInfo compression_info; + NcaMetaDataHashDataInfo meta_data_hash_data_info; + std::array<u8, 0x30> pad; + + bool IsSkipLayerHashEncryption() const { + return this->encryption_type == EncryptionType::AesCtrSkipLayerHash || + this->encryption_type == EncryptionType::AesCtrExSkipLayerHash; + } + + Result GetHashTargetOffset(s64* out) const { + switch (this->hash_type) { + case HashType::HierarchicalIntegrityHash: + case HashType::HierarchicalIntegritySha3Hash: + *out = this->hash_data.integrity_meta_info.level_hash_info + .info[this->hash_data.integrity_meta_info.level_hash_info.max_layers - 2] + .offset; + R_SUCCEED(); + case HashType::HierarchicalSha256Hash: + case HashType::HierarchicalSha3256Hash: + *out = + this->hash_data.hierarchical_sha256_data + .hash_layer_region[this->hash_data.hierarchical_sha256_data.hash_layer_count - + 1] + .offset; + R_SUCCEED(); + default: + R_THROW(ResultInvalidNcaFsHeader); + } + } +}; +static_assert(sizeof(NcaFsHeader) == NcaFsHeader::Size); +static_assert(std::is_trivial_v<NcaFsHeader>); +static_assert(offsetof(NcaFsHeader, patch_info) == NcaPatchInfo::Offset); + +inline constexpr const size_t NcaFsHeader::HashData::HierarchicalSha256Data::MasterHashOffset = + offsetof(NcaFsHeader, hash_data.hierarchical_sha256_data.fs_data_master_hash); +inline constexpr const size_t NcaFsHeader::HashData::IntegrityMetaInfo::MasterHashOffset = + offsetof(NcaFsHeader, hash_data.integrity_meta_info.master_hash); + +struct NcaMetaDataHashData { + s64 layer_info_offset; + NcaFsHeader::HashData::IntegrityMetaInfo integrity_meta_info; +}; +static_assert(sizeof(NcaMetaDataHashData) == + sizeof(NcaFsHeader::HashData::IntegrityMetaInfo) + sizeof(s64)); +static_assert(std::is_trivial_v<NcaMetaDataHashData>); + +} // namespace FileSys diff --git a/src/core/file_sys/fssystem/fssystem_nca_reader.cpp b/src/core/file_sys/fssystem/fssystem_nca_reader.cpp new file mode 100644 index 000000000..a3714ab37 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_nca_reader.cpp @@ -0,0 +1,531 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/file_sys/fssystem/fssystem_aes_xts_storage.h" +#include "core/file_sys/fssystem/fssystem_nca_file_system_driver.h" +#include "core/file_sys/vfs_offset.h" + +namespace FileSys { + +namespace { + +constexpr inline u32 SdkAddonVersionMin = 0x000B0000; +constexpr inline size_t Aes128KeySize = 0x10; +constexpr const std::array<u8, Aes128KeySize> ZeroKey{}; + +constexpr Result CheckNcaMagic(u32 magic) { + // Verify the magic is not a deprecated one. + R_UNLESS(magic != NcaHeader::Magic0, ResultUnsupportedSdkVersion); + R_UNLESS(magic != NcaHeader::Magic1, ResultUnsupportedSdkVersion); + R_UNLESS(magic != NcaHeader::Magic2, ResultUnsupportedSdkVersion); + + // Verify the magic is the current one. + R_UNLESS(magic == NcaHeader::Magic3, ResultInvalidNcaSignature); + + R_SUCCEED(); +} + +} // namespace + +NcaReader::NcaReader() + : m_body_storage(), m_header_storage(), m_is_software_aes_prioritized(false), + m_is_available_sw_key(false), m_header_encryption_type(NcaHeader::EncryptionType::Auto), + m_get_decompressor() { + std::memset(std::addressof(m_header), 0, sizeof(m_header)); + std::memset(std::addressof(m_decryption_keys), 0, sizeof(m_decryption_keys)); + std::memset(std::addressof(m_external_decryption_key), 0, sizeof(m_external_decryption_key)); +} + +NcaReader::~NcaReader() {} + +Result NcaReader::Initialize(VirtualFile base_storage, const NcaCryptoConfiguration& crypto_cfg, + const NcaCompressionConfiguration& compression_cfg) { + // Validate preconditions. + ASSERT(base_storage != nullptr); + ASSERT(m_body_storage == nullptr); + + // Create the work header storage storage. + VirtualFile work_header_storage; + + // We need to be able to generate keys. + R_UNLESS(crypto_cfg.generate_key != nullptr, ResultInvalidArgument); + + // Generate keys for header. + using AesXtsStorageForNcaHeader = AesXtsStorage; + + constexpr std::array<s32, NcaCryptoConfiguration::HeaderEncryptionKeyCount> + HeaderKeyTypeValues = { + static_cast<s32>(KeyType::NcaHeaderKey1), + static_cast<s32>(KeyType::NcaHeaderKey2), + }; + + std::array<std::array<u8, NcaCryptoConfiguration::Aes128KeySize>, + NcaCryptoConfiguration::HeaderEncryptionKeyCount> + header_decryption_keys; + for (size_t i = 0; i < NcaCryptoConfiguration::HeaderEncryptionKeyCount; i++) { + crypto_cfg.generate_key(header_decryption_keys[i].data(), + AesXtsStorageForNcaHeader::KeySize, + crypto_cfg.header_encrypted_encryption_keys[i].data(), + AesXtsStorageForNcaHeader::KeySize, HeaderKeyTypeValues[i]); + } + + // Create the header storage. + std::array<u8, AesXtsStorageForNcaHeader::IvSize> header_iv = {}; + work_header_storage = std::make_unique<AesXtsStorageForNcaHeader>( + base_storage, header_decryption_keys[0].data(), header_decryption_keys[1].data(), + AesXtsStorageForNcaHeader::KeySize, header_iv.data(), AesXtsStorageForNcaHeader::IvSize, + NcaHeader::XtsBlockSize); + + // Check that we successfully created the storage. + R_UNLESS(work_header_storage != nullptr, ResultAllocationMemoryFailedInNcaReaderA); + + // Read the header. + work_header_storage->ReadObject(std::addressof(m_header), 0); + + // Validate the magic. + if (const Result magic_result = CheckNcaMagic(m_header.magic); R_FAILED(magic_result)) { + // Try to use a plaintext header. + base_storage->ReadObject(std::addressof(m_header), 0); + R_UNLESS(R_SUCCEEDED(CheckNcaMagic(m_header.magic)), magic_result); + + // Configure to use the plaintext header. + auto base_storage_size = base_storage->GetSize(); + work_header_storage = std::make_shared<OffsetVfsFile>(base_storage, base_storage_size, 0); + R_UNLESS(work_header_storage != nullptr, ResultAllocationMemoryFailedInNcaReaderA); + + // Set encryption type as plaintext. + m_header_encryption_type = NcaHeader::EncryptionType::None; + } + + // Verify the header sign1. + if (crypto_cfg.verify_sign1 != nullptr) { + const u8* sig = m_header.header_sign_1.data(); + const size_t sig_size = NcaHeader::HeaderSignSize; + const u8* msg = + static_cast<const u8*>(static_cast<const void*>(std::addressof(m_header.magic))); + const size_t msg_size = + NcaHeader::Size - NcaHeader::HeaderSignSize * NcaHeader::HeaderSignCount; + + m_is_header_sign1_signature_valid = crypto_cfg.verify_sign1( + sig, sig_size, msg, msg_size, m_header.header1_signature_key_generation); + + if (!m_is_header_sign1_signature_valid) { + LOG_WARNING(Common_Filesystem, "Invalid NCA header sign1"); + } + } + + // Validate the sdk version. + R_UNLESS(m_header.sdk_addon_version >= SdkAddonVersionMin, ResultUnsupportedSdkVersion); + + // Validate the key index. + R_UNLESS(m_header.key_index < NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount || + m_header.key_index == NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexZeroKey, + ResultInvalidNcaKeyIndex); + + // Check if we have a rights id. + constexpr const std::array<u8, NcaHeader::RightsIdSize> ZeroRightsId{}; + if (std::memcmp(ZeroRightsId.data(), m_header.rights_id.data(), NcaHeader::RightsIdSize) == 0) { + // If we don't, then we don't have an external key, so we need to generate decryption keys. + crypto_cfg.generate_key( + m_decryption_keys[NcaHeader::DecryptionKey_AesCtr].data(), Aes128KeySize, + m_header.encrypted_key_area.data() + NcaHeader::DecryptionKey_AesCtr * Aes128KeySize, + Aes128KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration())); + crypto_cfg.generate_key( + m_decryption_keys[NcaHeader::DecryptionKey_AesXts1].data(), Aes128KeySize, + m_header.encrypted_key_area.data() + NcaHeader::DecryptionKey_AesXts1 * Aes128KeySize, + Aes128KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration())); + crypto_cfg.generate_key( + m_decryption_keys[NcaHeader::DecryptionKey_AesXts2].data(), Aes128KeySize, + m_header.encrypted_key_area.data() + NcaHeader::DecryptionKey_AesXts2 * Aes128KeySize, + Aes128KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration())); + crypto_cfg.generate_key( + m_decryption_keys[NcaHeader::DecryptionKey_AesCtrEx].data(), Aes128KeySize, + m_header.encrypted_key_area.data() + NcaHeader::DecryptionKey_AesCtrEx * Aes128KeySize, + Aes128KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration())); + + // Copy the hardware speed emulation key. + std::memcpy(m_decryption_keys[NcaHeader::DecryptionKey_AesCtrHw].data(), + m_header.encrypted_key_area.data() + + NcaHeader::DecryptionKey_AesCtrHw * Aes128KeySize, + Aes128KeySize); + } + + // Clear the external decryption key. + std::memset(m_external_decryption_key.data(), 0, m_external_decryption_key.size()); + + // Set software key availability. + m_is_available_sw_key = crypto_cfg.is_available_sw_key; + + // Set our decompressor function getter. + m_get_decompressor = compression_cfg.get_decompressor; + + // Set our storages. + m_header_storage = std::move(work_header_storage); + m_body_storage = std::move(base_storage); + + R_SUCCEED(); +} + +VirtualFile NcaReader::GetSharedBodyStorage() { + ASSERT(m_body_storage != nullptr); + return m_body_storage; +} + +u32 NcaReader::GetMagic() const { + ASSERT(m_body_storage != nullptr); + return m_header.magic; +} + +NcaHeader::DistributionType NcaReader::GetDistributionType() const { + ASSERT(m_body_storage != nullptr); + return m_header.distribution_type; +} + +NcaHeader::ContentType NcaReader::GetContentType() const { + ASSERT(m_body_storage != nullptr); + return m_header.content_type; +} + +u8 NcaReader::GetHeaderSign1KeyGeneration() const { + ASSERT(m_body_storage != nullptr); + return m_header.header1_signature_key_generation; +} + +u8 NcaReader::GetKeyGeneration() const { + ASSERT(m_body_storage != nullptr); + return m_header.GetProperKeyGeneration(); +} + +u8 NcaReader::GetKeyIndex() const { + ASSERT(m_body_storage != nullptr); + return m_header.key_index; +} + +u64 NcaReader::GetContentSize() const { + ASSERT(m_body_storage != nullptr); + return m_header.content_size; +} + +u64 NcaReader::GetProgramId() const { + ASSERT(m_body_storage != nullptr); + return m_header.program_id; +} + +u32 NcaReader::GetContentIndex() const { + ASSERT(m_body_storage != nullptr); + return m_header.content_index; +} + +u32 NcaReader::GetSdkAddonVersion() const { + ASSERT(m_body_storage != nullptr); + return m_header.sdk_addon_version; +} + +void NcaReader::GetRightsId(u8* dst, size_t dst_size) const { + ASSERT(dst != nullptr); + ASSERT(dst_size >= NcaHeader::RightsIdSize); + + std::memcpy(dst, m_header.rights_id.data(), NcaHeader::RightsIdSize); +} + +bool NcaReader::HasFsInfo(s32 index) const { + ASSERT(0 <= index && index < NcaHeader::FsCountMax); + return m_header.fs_info[index].start_sector != 0 || m_header.fs_info[index].end_sector != 0; +} + +s32 NcaReader::GetFsCount() const { + ASSERT(m_body_storage != nullptr); + for (s32 i = 0; i < NcaHeader::FsCountMax; i++) { + if (!this->HasFsInfo(i)) { + return i; + } + } + return NcaHeader::FsCountMax; +} + +const Hash& NcaReader::GetFsHeaderHash(s32 index) const { + ASSERT(m_body_storage != nullptr); + ASSERT(0 <= index && index < NcaHeader::FsCountMax); + return m_header.fs_header_hash[index]; +} + +void NcaReader::GetFsHeaderHash(Hash* dst, s32 index) const { + ASSERT(m_body_storage != nullptr); + ASSERT(0 <= index && index < NcaHeader::FsCountMax); + ASSERT(dst != nullptr); + std::memcpy(dst, std::addressof(m_header.fs_header_hash[index]), sizeof(*dst)); +} + +void NcaReader::GetFsInfo(NcaHeader::FsInfo* dst, s32 index) const { + ASSERT(m_body_storage != nullptr); + ASSERT(0 <= index && index < NcaHeader::FsCountMax); + ASSERT(dst != nullptr); + std::memcpy(dst, std::addressof(m_header.fs_info[index]), sizeof(*dst)); +} + +u64 NcaReader::GetFsOffset(s32 index) const { + ASSERT(m_body_storage != nullptr); + ASSERT(0 <= index && index < NcaHeader::FsCountMax); + return NcaHeader::SectorToByte(m_header.fs_info[index].start_sector); +} + +u64 NcaReader::GetFsEndOffset(s32 index) const { + ASSERT(m_body_storage != nullptr); + ASSERT(0 <= index && index < NcaHeader::FsCountMax); + return NcaHeader::SectorToByte(m_header.fs_info[index].end_sector); +} + +u64 NcaReader::GetFsSize(s32 index) const { + ASSERT(m_body_storage != nullptr); + ASSERT(0 <= index && index < NcaHeader::FsCountMax); + return NcaHeader::SectorToByte(m_header.fs_info[index].end_sector - + m_header.fs_info[index].start_sector); +} + +void NcaReader::GetEncryptedKey(void* dst, size_t size) const { + ASSERT(m_body_storage != nullptr); + ASSERT(dst != nullptr); + ASSERT(size >= NcaHeader::EncryptedKeyAreaSize); + + std::memcpy(dst, m_header.encrypted_key_area.data(), NcaHeader::EncryptedKeyAreaSize); +} + +const void* NcaReader::GetDecryptionKey(s32 index) const { + ASSERT(m_body_storage != nullptr); + ASSERT(0 <= index && index < NcaHeader::DecryptionKey_Count); + return m_decryption_keys[index].data(); +} + +bool NcaReader::HasValidInternalKey() const { + for (s32 i = 0; i < NcaHeader::DecryptionKey_Count; i++) { + if (std::memcmp(ZeroKey.data(), m_header.encrypted_key_area.data() + i * Aes128KeySize, + Aes128KeySize) != 0) { + return true; + } + } + return false; +} + +bool NcaReader::HasInternalDecryptionKeyForAesHw() const { + return std::memcmp(ZeroKey.data(), this->GetDecryptionKey(NcaHeader::DecryptionKey_AesCtrHw), + Aes128KeySize) != 0; +} + +bool NcaReader::IsSoftwareAesPrioritized() const { + return m_is_software_aes_prioritized; +} + +void NcaReader::PrioritizeSoftwareAes() { + m_is_software_aes_prioritized = true; +} + +bool NcaReader::IsAvailableSwKey() const { + return m_is_available_sw_key; +} + +bool NcaReader::HasExternalDecryptionKey() const { + return std::memcmp(ZeroKey.data(), this->GetExternalDecryptionKey(), Aes128KeySize) != 0; +} + +const void* NcaReader::GetExternalDecryptionKey() const { + return m_external_decryption_key.data(); +} + +void NcaReader::SetExternalDecryptionKey(const void* src, size_t size) { + ASSERT(src != nullptr); + ASSERT(size == sizeof(m_external_decryption_key)); + + std::memcpy(m_external_decryption_key.data(), src, sizeof(m_external_decryption_key)); +} + +void NcaReader::GetRawData(void* dst, size_t dst_size) const { + ASSERT(m_body_storage != nullptr); + ASSERT(dst != nullptr); + ASSERT(dst_size >= sizeof(NcaHeader)); + + std::memcpy(dst, std::addressof(m_header), sizeof(NcaHeader)); +} + +GetDecompressorFunction NcaReader::GetDecompressor() const { + ASSERT(m_get_decompressor != nullptr); + return m_get_decompressor; +} + +NcaHeader::EncryptionType NcaReader::GetEncryptionType() const { + return m_header_encryption_type; +} + +Result NcaReader::ReadHeader(NcaFsHeader* dst, s32 index) const { + ASSERT(dst != nullptr); + ASSERT(0 <= index && index < NcaHeader::FsCountMax); + + const s64 offset = sizeof(NcaHeader) + sizeof(NcaFsHeader) * index; + m_header_storage->ReadObject(dst, offset); + + R_SUCCEED(); +} + +bool NcaReader::GetHeaderSign1Valid() const { + return m_is_header_sign1_signature_valid; +} + +void NcaReader::GetHeaderSign2(void* dst, size_t size) const { + ASSERT(dst != nullptr); + ASSERT(size == NcaHeader::HeaderSignSize); + + std::memcpy(dst, m_header.header_sign_2.data(), size); +} + +Result NcaFsHeaderReader::Initialize(const NcaReader& reader, s32 index) { + // Reset ourselves to uninitialized. + m_fs_index = -1; + + // Read the header. + R_TRY(reader.ReadHeader(std::addressof(m_data), index)); + + // Set our index. + m_fs_index = index; + R_SUCCEED(); +} + +void NcaFsHeaderReader::GetRawData(void* dst, size_t dst_size) const { + ASSERT(this->IsInitialized()); + ASSERT(dst != nullptr); + ASSERT(dst_size >= sizeof(NcaFsHeader)); + + std::memcpy(dst, std::addressof(m_data), sizeof(NcaFsHeader)); +} + +NcaFsHeader::HashData& NcaFsHeaderReader::GetHashData() { + ASSERT(this->IsInitialized()); + return m_data.hash_data; +} + +const NcaFsHeader::HashData& NcaFsHeaderReader::GetHashData() const { + ASSERT(this->IsInitialized()); + return m_data.hash_data; +} + +u16 NcaFsHeaderReader::GetVersion() const { + ASSERT(this->IsInitialized()); + return m_data.version; +} + +s32 NcaFsHeaderReader::GetFsIndex() const { + ASSERT(this->IsInitialized()); + return m_fs_index; +} + +NcaFsHeader::FsType NcaFsHeaderReader::GetFsType() const { + ASSERT(this->IsInitialized()); + return m_data.fs_type; +} + +NcaFsHeader::HashType NcaFsHeaderReader::GetHashType() const { + ASSERT(this->IsInitialized()); + return m_data.hash_type; +} + +NcaFsHeader::EncryptionType NcaFsHeaderReader::GetEncryptionType() const { + ASSERT(this->IsInitialized()); + return m_data.encryption_type; +} + +NcaPatchInfo& NcaFsHeaderReader::GetPatchInfo() { + ASSERT(this->IsInitialized()); + return m_data.patch_info; +} + +const NcaPatchInfo& NcaFsHeaderReader::GetPatchInfo() const { + ASSERT(this->IsInitialized()); + return m_data.patch_info; +} + +const NcaAesCtrUpperIv NcaFsHeaderReader::GetAesCtrUpperIv() const { + ASSERT(this->IsInitialized()); + return m_data.aes_ctr_upper_iv; +} + +bool NcaFsHeaderReader::IsSkipLayerHashEncryption() const { + ASSERT(this->IsInitialized()); + return m_data.IsSkipLayerHashEncryption(); +} + +Result NcaFsHeaderReader::GetHashTargetOffset(s64* out) const { + ASSERT(out != nullptr); + ASSERT(this->IsInitialized()); + + R_RETURN(m_data.GetHashTargetOffset(out)); +} + +bool NcaFsHeaderReader::ExistsSparseLayer() const { + ASSERT(this->IsInitialized()); + return m_data.sparse_info.generation != 0; +} + +NcaSparseInfo& NcaFsHeaderReader::GetSparseInfo() { + ASSERT(this->IsInitialized()); + return m_data.sparse_info; +} + +const NcaSparseInfo& NcaFsHeaderReader::GetSparseInfo() const { + ASSERT(this->IsInitialized()); + return m_data.sparse_info; +} + +bool NcaFsHeaderReader::ExistsCompressionLayer() const { + ASSERT(this->IsInitialized()); + return m_data.compression_info.bucket.offset != 0 && m_data.compression_info.bucket.size != 0; +} + +NcaCompressionInfo& NcaFsHeaderReader::GetCompressionInfo() { + ASSERT(this->IsInitialized()); + return m_data.compression_info; +} + +const NcaCompressionInfo& NcaFsHeaderReader::GetCompressionInfo() const { + ASSERT(this->IsInitialized()); + return m_data.compression_info; +} + +bool NcaFsHeaderReader::ExistsPatchMetaHashLayer() const { + ASSERT(this->IsInitialized()); + return m_data.meta_data_hash_data_info.size != 0 && this->GetPatchInfo().HasIndirectTable(); +} + +NcaMetaDataHashDataInfo& NcaFsHeaderReader::GetPatchMetaDataHashDataInfo() { + ASSERT(this->IsInitialized()); + return m_data.meta_data_hash_data_info; +} + +const NcaMetaDataHashDataInfo& NcaFsHeaderReader::GetPatchMetaDataHashDataInfo() const { + ASSERT(this->IsInitialized()); + return m_data.meta_data_hash_data_info; +} + +NcaFsHeader::MetaDataHashType NcaFsHeaderReader::GetPatchMetaHashType() const { + ASSERT(this->IsInitialized()); + return m_data.meta_data_hash_type; +} + +bool NcaFsHeaderReader::ExistsSparseMetaHashLayer() const { + ASSERT(this->IsInitialized()); + return m_data.meta_data_hash_data_info.size != 0 && this->ExistsSparseLayer(); +} + +NcaMetaDataHashDataInfo& NcaFsHeaderReader::GetSparseMetaDataHashDataInfo() { + ASSERT(this->IsInitialized()); + return m_data.meta_data_hash_data_info; +} + +const NcaMetaDataHashDataInfo& NcaFsHeaderReader::GetSparseMetaDataHashDataInfo() const { + ASSERT(this->IsInitialized()); + return m_data.meta_data_hash_data_info; +} + +NcaFsHeader::MetaDataHashType NcaFsHeaderReader::GetSparseMetaHashType() const { + ASSERT(this->IsInitialized()); + return m_data.meta_data_hash_type; +} + +} // namespace FileSys diff --git a/src/core/file_sys/fssystem/fssystem_pooled_buffer.cpp b/src/core/file_sys/fssystem/fssystem_pooled_buffer.cpp new file mode 100644 index 000000000..bbfaab255 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_pooled_buffer.cpp @@ -0,0 +1,61 @@ +// 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_pooled_buffer.h" + +namespace FileSys { + +namespace { + +constexpr size_t HeapBlockSize = BufferPoolAlignment; +static_assert(HeapBlockSize == 4_KiB); + +// A heap block is 4KiB. An order is a power of two. +// This gives blocks of the order 32KiB, 512KiB, 4MiB. +constexpr s32 HeapOrderMax = 7; +constexpr s32 HeapOrderMaxForLarge = HeapOrderMax + 3; + +constexpr size_t HeapAllocatableSizeMax = HeapBlockSize * (static_cast<size_t>(1) << HeapOrderMax); +constexpr size_t HeapAllocatableSizeMaxForLarge = + HeapBlockSize * (static_cast<size_t>(1) << HeapOrderMaxForLarge); + +} // namespace + +size_t PooledBuffer::GetAllocatableSizeMaxCore(bool large) { + return large ? HeapAllocatableSizeMaxForLarge : HeapAllocatableSizeMax; +} + +void PooledBuffer::AllocateCore(size_t ideal_size, size_t required_size, bool large) { + // Ensure preconditions. + ASSERT(m_buffer == nullptr); + + // Check that we can allocate this size. + ASSERT(required_size <= GetAllocatableSizeMaxCore(large)); + + const size_t target_size = + std::min(std::max(ideal_size, required_size), GetAllocatableSizeMaxCore(large)); + + // Dummy implementation for allocate. + if (target_size > 0) { + m_buffer = + reinterpret_cast<char*>(::operator new(target_size, std::align_val_t{HeapBlockSize})); + m_size = target_size; + + // Ensure postconditions. + ASSERT(m_buffer != nullptr); + } +} + +void PooledBuffer::Shrink(size_t ideal_size) { + ASSERT(ideal_size <= GetAllocatableSizeMaxCore(true)); + + // Shrinking to zero means that we have no buffer. + if (ideal_size == 0) { + ::operator delete(m_buffer, std::align_val_t{HeapBlockSize}); + m_buffer = nullptr; + m_size = ideal_size; + } +} + +} // namespace FileSys diff --git a/src/core/file_sys/fssystem/fssystem_pooled_buffer.h b/src/core/file_sys/fssystem/fssystem_pooled_buffer.h new file mode 100644 index 000000000..9a6adbcb5 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_pooled_buffer.h @@ -0,0 +1,95 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "common/literals.h" +#include "core/hle/result.h" + +namespace FileSys { + +using namespace Common::Literals; + +constexpr inline size_t BufferPoolAlignment = 4_KiB; +constexpr inline size_t BufferPoolWorkSize = 320; + +class PooledBuffer { + YUZU_NON_COPYABLE(PooledBuffer); + +public: + // Constructor/Destructor. + constexpr PooledBuffer() : m_buffer(), m_size() {} + + PooledBuffer(size_t ideal_size, size_t required_size) : m_buffer(), m_size() { + this->Allocate(ideal_size, required_size); + } + + ~PooledBuffer() { + this->Deallocate(); + } + + // Move and assignment. + explicit PooledBuffer(PooledBuffer&& rhs) : m_buffer(rhs.m_buffer), m_size(rhs.m_size) { + rhs.m_buffer = nullptr; + rhs.m_size = 0; + } + + PooledBuffer& operator=(PooledBuffer&& rhs) { + PooledBuffer(std::move(rhs)).Swap(*this); + return *this; + } + + // Allocation API. + void Allocate(size_t ideal_size, size_t required_size) { + return this->AllocateCore(ideal_size, required_size, false); + } + + void AllocateParticularlyLarge(size_t ideal_size, size_t required_size) { + return this->AllocateCore(ideal_size, required_size, true); + } + + void Shrink(size_t ideal_size); + + void Deallocate() { + // Shrink the buffer to empty. + this->Shrink(0); + ASSERT(m_buffer == nullptr); + } + + char* GetBuffer() const { + ASSERT(m_buffer != nullptr); + return m_buffer; + } + + size_t GetSize() const { + ASSERT(m_buffer != nullptr); + return m_size; + } + +public: + static size_t GetAllocatableSizeMax() { + return GetAllocatableSizeMaxCore(false); + } + static size_t GetAllocatableParticularlyLargeSizeMax() { + return GetAllocatableSizeMaxCore(true); + } + +private: + static size_t GetAllocatableSizeMaxCore(bool large); + +private: + void Swap(PooledBuffer& rhs) { + std::swap(m_buffer, rhs.m_buffer); + std::swap(m_size, rhs.m_size); + } + + void AllocateCore(size_t ideal_size, size_t required_size, bool large); + +private: + char* m_buffer; + size_t m_size; +}; + +} // namespace FileSys diff --git a/src/core/file_sys/fssystem/fssystem_sparse_storage.cpp b/src/core/file_sys/fssystem/fssystem_sparse_storage.cpp new file mode 100644 index 000000000..8574a11dd --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_sparse_storage.cpp @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/file_sys/fssystem/fssystem_sparse_storage.h" + +namespace FileSys { + +size_t SparseStorage::Read(u8* buffer, size_t size, size_t offset) const { + // Validate preconditions. + ASSERT(this->IsInitialized()); + ASSERT(buffer != nullptr); + + // Allow zero size. + if (size == 0) { + return size; + } + + SparseStorage* self = const_cast<SparseStorage*>(this); + + if (self->GetEntryTable().IsEmpty()) { + BucketTree::Offsets table_offsets; + ASSERT(R_SUCCEEDED(self->GetEntryTable().GetOffsets(std::addressof(table_offsets)))); + ASSERT(table_offsets.IsInclude(offset, size)); + + std::memset(buffer, 0, size); + } else { + self->OperatePerEntry<false, 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 diff --git a/src/core/file_sys/fssystem/fssystem_sparse_storage.h b/src/core/file_sys/fssystem/fssystem_sparse_storage.h new file mode 100644 index 000000000..6c196ec61 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_sparse_storage.h @@ -0,0 +1,72 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "core/file_sys/fssystem/fssystem_indirect_storage.h" + +namespace FileSys { + +class SparseStorage : public IndirectStorage { + YUZU_NON_COPYABLE(SparseStorage); + YUZU_NON_MOVEABLE(SparseStorage); + +private: + class ZeroStorage : public IReadOnlyStorage { + public: + ZeroStorage() {} + virtual ~ZeroStorage() {} + + virtual size_t GetSize() const override { + return std::numeric_limits<size_t>::max(); + } + + virtual size_t Read(u8* buffer, size_t size, size_t offset) const override { + ASSERT(buffer != nullptr || size == 0); + + if (size > 0) { + std::memset(buffer, 0, size); + } + + return size; + } + }; + +public: + SparseStorage() : IndirectStorage(), m_zero_storage(std::make_shared<ZeroStorage>()) {} + virtual ~SparseStorage() {} + + using IndirectStorage::Initialize; + + void Initialize(s64 end_offset) { + this->GetEntryTable().Initialize(NodeSize, end_offset); + this->SetZeroStorage(); + } + + void SetDataStorage(VirtualFile storage) { + ASSERT(this->IsInitialized()); + + this->SetStorage(0, storage); + this->SetZeroStorage(); + } + + template <typename T> + void SetDataStorage(T storage, s64 offset, s64 size) { + ASSERT(this->IsInitialized()); + + this->SetStorage(0, storage, offset, size); + this->SetZeroStorage(); + } + + virtual size_t Read(u8* buffer, size_t size, size_t offset) const override; + +private: + void SetZeroStorage() { + return this->SetStorage(1, m_zero_storage, 0, std::numeric_limits<s64>::max()); + } + +private: + VirtualFile m_zero_storage; +}; + +} // namespace FileSys diff --git a/src/core/file_sys/fssystem/fssystem_switch_storage.h b/src/core/file_sys/fssystem/fssystem_switch_storage.h new file mode 100644 index 000000000..2b43927cb --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_switch_storage.h @@ -0,0 +1,80 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "core/file_sys/fssystem/fs_i_storage.h" + +namespace FileSys { + +class RegionSwitchStorage : public IReadOnlyStorage { + YUZU_NON_COPYABLE(RegionSwitchStorage); + YUZU_NON_MOVEABLE(RegionSwitchStorage); + +public: + struct Region { + s64 offset; + s64 size; + }; + +public: + RegionSwitchStorage(VirtualFile&& i, VirtualFile&& o, Region r) + : m_inside_region_storage(std::move(i)), m_outside_region_storage(std::move(o)), + m_region(r) {} + + virtual size_t Read(u8* buffer, size_t size, size_t offset) const override { + // Process until we're done. + size_t processed = 0; + while (processed < size) { + // Process on the appropriate storage. + s64 cur_size = 0; + if (this->CheckRegions(std::addressof(cur_size), offset + processed, + size - processed)) { + m_inside_region_storage->Read(buffer + processed, cur_size, offset + processed); + } else { + m_outside_region_storage->Read(buffer + processed, cur_size, offset + processed); + } + + // Advance. + processed += cur_size; + } + + return size; + } + + virtual size_t GetSize() const override { + return m_inside_region_storage->GetSize(); + } + +private: + bool CheckRegions(s64* out_current_size, s64 offset, s64 size) const { + // Check if our region contains the access. + if (m_region.offset <= offset) { + if (offset < m_region.offset + m_region.size) { + if (m_region.offset + m_region.size <= offset + size) { + *out_current_size = m_region.offset + m_region.size - offset; + } else { + *out_current_size = size; + } + return true; + } else { + *out_current_size = size; + return false; + } + } else { + if (m_region.offset <= offset + size) { + *out_current_size = m_region.offset - offset; + } else { + *out_current_size = size; + } + return false; + } + } + +private: + VirtualFile m_inside_region_storage; + VirtualFile m_outside_region_storage; + Region m_region; +}; + +} // namespace FileSys diff --git a/src/core/file_sys/fssystem/fssystem_utility.cpp b/src/core/file_sys/fssystem/fssystem_utility.cpp new file mode 100644 index 000000000..ceabb8ff1 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_utility.cpp @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/file_sys/fssystem/fssystem_utility.h" + +namespace FileSys { + +void AddCounter(void* counter_, size_t counter_size, u64 value) { + u8* counter = static_cast<u8*>(counter_); + u64 remaining = value; + u8 carry = 0; + + for (size_t i = 0; i < counter_size; i++) { + auto sum = counter[counter_size - 1 - i] + (remaining & 0xFF) + carry; + carry = static_cast<u8>(sum >> (sizeof(u8) * 8)); + auto sum8 = static_cast<u8>(sum & 0xFF); + + counter[counter_size - 1 - i] = sum8; + + remaining >>= (sizeof(u8) * 8); + if (carry == 0 && remaining == 0) { + break; + } + } +} + +} // namespace FileSys diff --git a/src/core/file_sys/fssystem/fssystem_utility.h b/src/core/file_sys/fssystem/fssystem_utility.h new file mode 100644 index 000000000..284b8b811 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_utility.h @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/common_funcs.h" + +namespace FileSys { + +void AddCounter(void* counter, size_t counter_size, u64 value); + +} diff --git a/src/core/file_sys/nca_patch.cpp b/src/core/file_sys/nca_patch.cpp deleted file mode 100644 index 2735d053b..000000000 --- a/src/core/file_sys/nca_patch.cpp +++ /dev/null @@ -1,217 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include <algorithm> -#include <array> -#include <cstddef> -#include <cstring> - -#include "common/assert.h" -#include "core/crypto/aes_util.h" -#include "core/file_sys/nca_patch.h" - -namespace FileSys { -namespace { -template <bool Subsection, typename BlockType, typename BucketType> -std::pair<std::size_t, std::size_t> SearchBucketEntry(u64 offset, const BlockType& block, - const BucketType& buckets) { - if constexpr (Subsection) { - const auto& last_bucket = buckets[block.number_buckets - 1]; - if (offset >= last_bucket.entries[last_bucket.number_entries].address_patch) { - return {block.number_buckets - 1, last_bucket.number_entries}; - } - } else { - ASSERT_MSG(offset <= block.size, "Offset is out of bounds in BKTR relocation block."); - } - - std::size_t bucket_id = std::count_if( - block.base_offsets.begin() + 1, block.base_offsets.begin() + block.number_buckets, - [&offset](u64 base_offset) { return base_offset <= offset; }); - - const auto& bucket = buckets[bucket_id]; - - if (bucket.number_entries == 1) { - return {bucket_id, 0}; - } - - std::size_t low = 0; - std::size_t mid = 0; - std::size_t high = bucket.number_entries - 1; - while (low <= high) { - mid = (low + high) / 2; - if (bucket.entries[mid].address_patch > offset) { - high = mid - 1; - } else { - if (mid == bucket.number_entries - 1 || - bucket.entries[mid + 1].address_patch > offset) { - return {bucket_id, mid}; - } - - low = mid + 1; - } - } - ASSERT_MSG(false, "Offset could not be found in BKTR block."); - return {0, 0}; -} -} // Anonymous namespace - -BKTR::BKTR(VirtualFile base_romfs_, VirtualFile bktr_romfs_, RelocationBlock relocation_, - std::vector<RelocationBucket> relocation_buckets_, SubsectionBlock subsection_, - std::vector<SubsectionBucket> subsection_buckets_, bool is_encrypted_, - Core::Crypto::Key128 key_, u64 base_offset_, u64 ivfc_offset_, - std::array<u8, 8> section_ctr_) - : relocation(relocation_), relocation_buckets(std::move(relocation_buckets_)), - subsection(subsection_), subsection_buckets(std::move(subsection_buckets_)), - base_romfs(std::move(base_romfs_)), bktr_romfs(std::move(bktr_romfs_)), - encrypted(is_encrypted_), key(key_), base_offset(base_offset_), ivfc_offset(ivfc_offset_), - section_ctr(section_ctr_) { - for (std::size_t i = 0; i < relocation.number_buckets - 1; ++i) { - relocation_buckets[i].entries.push_back({relocation.base_offsets[i + 1], 0, 0}); - } - - for (std::size_t i = 0; i < subsection.number_buckets - 1; ++i) { - subsection_buckets[i].entries.push_back({subsection_buckets[i + 1].entries[0].address_patch, - {0}, - subsection_buckets[i + 1].entries[0].ctr}); - } - - relocation_buckets.back().entries.push_back({relocation.size, 0, 0}); -} - -BKTR::~BKTR() = default; - -std::size_t BKTR::Read(u8* data, std::size_t length, std::size_t offset) const { - // Read out of bounds. - if (offset >= relocation.size) { - return 0; - } - - const auto relocation_entry = GetRelocationEntry(offset); - const auto section_offset = - offset - relocation_entry.address_patch + relocation_entry.address_source; - const auto bktr_read = relocation_entry.from_patch; - - const auto next_relocation = GetNextRelocationEntry(offset); - - if (offset + length > next_relocation.address_patch) { - const u64 partition = next_relocation.address_patch - offset; - return Read(data, partition, offset) + - Read(data + partition, length - partition, offset + partition); - } - - if (!bktr_read) { - ASSERT_MSG(section_offset >= ivfc_offset, "Offset calculation negative."); - return base_romfs->Read(data, length, section_offset - ivfc_offset); - } - - if (!encrypted) { - return bktr_romfs->Read(data, length, section_offset); - } - - const auto subsection_entry = GetSubsectionEntry(section_offset); - Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(key, Core::Crypto::Mode::CTR); - - // Calculate AES IV - std::array<u8, 16> iv{}; - auto subsection_ctr = subsection_entry.ctr; - auto offset_iv = section_offset + base_offset; - for (std::size_t i = 0; i < section_ctr.size(); ++i) { - iv[i] = section_ctr[0x8 - i - 1]; - } - offset_iv >>= 4; - for (std::size_t i = 0; i < sizeof(u64); ++i) { - iv[0xF - i] = static_cast<u8>(offset_iv & 0xFF); - offset_iv >>= 8; - } - for (std::size_t i = 0; i < sizeof(u32); ++i) { - iv[0x7 - i] = static_cast<u8>(subsection_ctr & 0xFF); - subsection_ctr >>= 8; - } - cipher.SetIV(iv); - - const auto next_subsection = GetNextSubsectionEntry(section_offset); - - if (section_offset + length > next_subsection.address_patch) { - const u64 partition = next_subsection.address_patch - section_offset; - return Read(data, partition, offset) + - Read(data + partition, length - partition, offset + partition); - } - - const auto block_offset = section_offset & 0xF; - if (block_offset != 0) { - auto block = bktr_romfs->ReadBytes(0x10, section_offset & ~0xF); - cipher.Transcode(block.data(), block.size(), block.data(), Core::Crypto::Op::Decrypt); - if (length + block_offset < 0x10) { - std::memcpy(data, block.data() + block_offset, std::min(length, block.size())); - return std::min(length, block.size()); - } - - const auto read = 0x10 - block_offset; - std::memcpy(data, block.data() + block_offset, read); - return read + Read(data + read, length - read, offset + read); - } - - const auto raw_read = bktr_romfs->Read(data, length, section_offset); - cipher.Transcode(data, raw_read, data, Core::Crypto::Op::Decrypt); - return raw_read; -} - -RelocationEntry BKTR::GetRelocationEntry(u64 offset) const { - const auto res = SearchBucketEntry<false>(offset, relocation, relocation_buckets); - return relocation_buckets[res.first].entries[res.second]; -} - -RelocationEntry BKTR::GetNextRelocationEntry(u64 offset) const { - const auto res = SearchBucketEntry<false>(offset, relocation, relocation_buckets); - const auto bucket = relocation_buckets[res.first]; - if (res.second + 1 < bucket.entries.size()) - return bucket.entries[res.second + 1]; - return relocation_buckets[res.first + 1].entries[0]; -} - -SubsectionEntry BKTR::GetSubsectionEntry(u64 offset) const { - const auto res = SearchBucketEntry<true>(offset, subsection, subsection_buckets); - return subsection_buckets[res.first].entries[res.second]; -} - -SubsectionEntry BKTR::GetNextSubsectionEntry(u64 offset) const { - const auto res = SearchBucketEntry<true>(offset, subsection, subsection_buckets); - const auto bucket = subsection_buckets[res.first]; - if (res.second + 1 < bucket.entries.size()) - return bucket.entries[res.second + 1]; - return subsection_buckets[res.first + 1].entries[0]; -} - -std::string BKTR::GetName() const { - return base_romfs->GetName(); -} - -std::size_t BKTR::GetSize() const { - return relocation.size; -} - -bool BKTR::Resize(std::size_t new_size) { - return false; -} - -VirtualDir BKTR::GetContainingDirectory() const { - return base_romfs->GetContainingDirectory(); -} - -bool BKTR::IsWritable() const { - return false; -} - -bool BKTR::IsReadable() const { - return true; -} - -std::size_t BKTR::Write(const u8* data, std::size_t length, std::size_t offset) { - return 0; -} - -bool BKTR::Rename(std::string_view name) { - return base_romfs->Rename(name); -} - -} // namespace FileSys diff --git a/src/core/file_sys/nca_patch.h b/src/core/file_sys/nca_patch.h deleted file mode 100644 index 595e3ef09..000000000 --- a/src/core/file_sys/nca_patch.h +++ /dev/null @@ -1,145 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include <array> -#include <memory> -#include <vector> - -#include "common/common_funcs.h" -#include "common/common_types.h" -#include "common/swap.h" -#include "core/crypto/key_manager.h" - -namespace FileSys { - -#pragma pack(push, 1) -struct RelocationEntry { - u64_le address_patch; - u64_le address_source; - u32 from_patch; -}; -#pragma pack(pop) -static_assert(sizeof(RelocationEntry) == 0x14, "RelocationEntry has incorrect size."); - -struct RelocationBucketRaw { - INSERT_PADDING_BYTES(4); - u32_le number_entries; - u64_le end_offset; - std::array<RelocationEntry, 0x332> relocation_entries; - INSERT_PADDING_BYTES(8); -}; -static_assert(sizeof(RelocationBucketRaw) == 0x4000, "RelocationBucketRaw has incorrect size."); - -// Vector version of RelocationBucketRaw -struct RelocationBucket { - u32 number_entries; - u64 end_offset; - std::vector<RelocationEntry> entries; -}; - -struct RelocationBlock { - INSERT_PADDING_BYTES(4); - u32_le number_buckets; - u64_le size; - std::array<u64, 0x7FE> base_offsets; -}; -static_assert(sizeof(RelocationBlock) == 0x4000, "RelocationBlock has incorrect size."); - -struct SubsectionEntry { - u64_le address_patch; - INSERT_PADDING_BYTES(0x4); - u32_le ctr; -}; -static_assert(sizeof(SubsectionEntry) == 0x10, "SubsectionEntry has incorrect size."); - -struct SubsectionBucketRaw { - INSERT_PADDING_BYTES(4); - u32_le number_entries; - u64_le end_offset; - std::array<SubsectionEntry, 0x3FF> subsection_entries; -}; -static_assert(sizeof(SubsectionBucketRaw) == 0x4000, "SubsectionBucketRaw has incorrect size."); - -// Vector version of SubsectionBucketRaw -struct SubsectionBucket { - u32 number_entries; - u64 end_offset; - std::vector<SubsectionEntry> entries; -}; - -struct SubsectionBlock { - INSERT_PADDING_BYTES(4); - u32_le number_buckets; - u64_le size; - std::array<u64, 0x7FE> base_offsets; -}; -static_assert(sizeof(SubsectionBlock) == 0x4000, "SubsectionBlock has incorrect size."); - -inline RelocationBucket ConvertRelocationBucketRaw(RelocationBucketRaw raw) { - return {raw.number_entries, - raw.end_offset, - {raw.relocation_entries.begin(), raw.relocation_entries.begin() + raw.number_entries}}; -} - -inline SubsectionBucket ConvertSubsectionBucketRaw(SubsectionBucketRaw raw) { - return {raw.number_entries, - raw.end_offset, - {raw.subsection_entries.begin(), raw.subsection_entries.begin() + raw.number_entries}}; -} - -class BKTR : public VfsFile { -public: - BKTR(VirtualFile base_romfs, VirtualFile bktr_romfs, RelocationBlock relocation, - std::vector<RelocationBucket> relocation_buckets, SubsectionBlock subsection, - std::vector<SubsectionBucket> subsection_buckets, bool is_encrypted, - Core::Crypto::Key128 key, u64 base_offset, u64 ivfc_offset, std::array<u8, 8> section_ctr); - ~BKTR() override; - - std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override; - - std::string GetName() const override; - - std::size_t GetSize() const override; - - bool Resize(std::size_t new_size) override; - - VirtualDir GetContainingDirectory() const override; - - bool IsWritable() const override; - - bool IsReadable() const override; - - std::size_t Write(const u8* data, std::size_t length, std::size_t offset) override; - - bool Rename(std::string_view name) override; - -private: - RelocationEntry GetRelocationEntry(u64 offset) const; - RelocationEntry GetNextRelocationEntry(u64 offset) const; - - SubsectionEntry GetSubsectionEntry(u64 offset) const; - SubsectionEntry GetNextSubsectionEntry(u64 offset) const; - - RelocationBlock relocation; - std::vector<RelocationBucket> relocation_buckets; - SubsectionBlock subsection; - std::vector<SubsectionBucket> subsection_buckets; - - // Should be the raw base romfs, decrypted. - VirtualFile base_romfs; - // Should be the raw BKTR romfs, (located at media_offset with size media_size). - VirtualFile bktr_romfs; - - bool encrypted; - Core::Crypto::Key128 key; - - // Base offset into NCA, used for IV calculation. - u64 base_offset; - // Distance between IVFC start and RomFS start, used for base reads - u64 ivfc_offset; - std::array<u8, 8> section_ctr; -}; - -} // namespace FileSys diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp index 2ba1b34a4..0701e3f0e 100644 --- a/src/core/file_sys/patch_manager.cpp +++ b/src/core/file_sys/patch_manager.cpp @@ -141,8 +141,7 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const { const auto update_tid = GetUpdateTitleID(title_id); const auto update = content_provider.GetEntry(update_tid, ContentRecordType::Program); - if (!update_disabled && update != nullptr && update->GetExeFS() != nullptr && - update->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) { + if (!update_disabled && update != nullptr && update->GetExeFS() != nullptr) { LOG_INFO(Loader, " ExeFS: Update ({}) applied successfully", FormatTitleVersion(content_provider.GetEntryVersion(update_tid).value_or(0))); exefs = update->GetExeFS(); @@ -358,11 +357,6 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t return; } - auto extracted = ExtractRomFS(romfs); - if (extracted == nullptr) { - return; - } - const auto& disabled = Settings::values.disabled_addons[title_id]; std::vector<VirtualDir> patch_dirs = load_dir->GetSubdirectories(); if (std::find(disabled.cbegin(), disabled.cend(), "SDMC") == disabled.cend()) { @@ -394,6 +388,11 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t return; } + auto extracted = ExtractRomFS(romfs); + if (extracted == nullptr) { + return; + } + layers.push_back(std::move(extracted)); auto layered = LayeredVfsDirectory::MakeLayeredDirectory(std::move(layers)); @@ -412,39 +411,43 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t romfs = std::move(packed); } -VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, ContentRecordType type, - VirtualFile update_raw, bool apply_layeredfs) const { +VirtualFile PatchManager::PatchRomFS(const NCA* base_nca, VirtualFile base_romfs, + ContentRecordType type, VirtualFile packed_update_raw, + bool apply_layeredfs) const { const auto log_string = fmt::format("Patching RomFS for title_id={:016X}, type={:02X}", title_id, static_cast<u8>(type)); - if (type == ContentRecordType::Program || type == ContentRecordType::Data) { LOG_INFO(Loader, "{}", log_string); } else { LOG_DEBUG(Loader, "{}", log_string); } - if (romfs == nullptr) { - return romfs; + if (base_romfs == nullptr) { + return base_romfs; } + auto romfs = base_romfs; + // Game Updates const auto update_tid = GetUpdateTitleID(title_id); - const auto update = content_provider.GetEntryRaw(update_tid, type); + const auto update_raw = content_provider.GetEntryRaw(update_tid, type); const auto& disabled = Settings::values.disabled_addons[title_id]; const auto update_disabled = std::find(disabled.cbegin(), disabled.cend(), "Update") != disabled.cend(); - if (!update_disabled && update != nullptr) { - const auto new_nca = std::make_shared<NCA>(update, romfs, ivfc_offset); + if (!update_disabled && update_raw != nullptr && base_nca != nullptr) { + const auto new_nca = std::make_shared<NCA>(update_raw, base_nca); if (new_nca->GetStatus() == Loader::ResultStatus::Success && new_nca->GetRomFS() != nullptr) { LOG_INFO(Loader, " RomFS: Update ({}) applied successfully", FormatTitleVersion(content_provider.GetEntryVersion(update_tid).value_or(0))); romfs = new_nca->GetRomFS(); + const auto version = + FormatTitleVersion(content_provider.GetEntryVersion(update_tid).value_or(0)); } - } else if (!update_disabled && update_raw != nullptr) { - const auto new_nca = std::make_shared<NCA>(update_raw, romfs, ivfc_offset); + } else if (!update_disabled && packed_update_raw != nullptr && base_nca != nullptr) { + const auto new_nca = std::make_shared<NCA>(packed_update_raw, base_nca); if (new_nca->GetStatus() == Loader::ResultStatus::Success && new_nca->GetRomFS() != nullptr) { LOG_INFO(Loader, " RomFS: Update (PACKED) applied successfully"); @@ -608,7 +611,7 @@ PatchManager::Metadata PatchManager::ParseControlNCA(const NCA& nca) const { return {}; } - const auto romfs = PatchRomFS(base_romfs, nca.GetBaseIVFCOffset(), ContentRecordType::Control); + const auto romfs = PatchRomFS(&nca, base_romfs, ContentRecordType::Control); if (romfs == nullptr) { return {}; } diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h index 69d15e2f8..adcde7b7d 100644 --- a/src/core/file_sys/patch_manager.h +++ b/src/core/file_sys/patch_manager.h @@ -61,9 +61,9 @@ public: // Currently tracked RomFS patches: // - Game Updates // - LayeredFS - [[nodiscard]] VirtualFile PatchRomFS(VirtualFile base, u64 ivfc_offset, + [[nodiscard]] VirtualFile PatchRomFS(const NCA* base_nca, VirtualFile base_romfs, ContentRecordType type = ContentRecordType::Program, - VirtualFile update_raw = nullptr, + VirtualFile packed_update_raw = nullptr, bool apply_layeredfs = true) const; // Returns a vector of pairs between patch names and patch versions. diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp index a6960170c..a28af3594 100644 --- a/src/core/file_sys/registered_cache.cpp +++ b/src/core/file_sys/registered_cache.cpp @@ -416,9 +416,9 @@ void RegisteredCache::ProcessFiles(const std::vector<NcaID>& ids) { if (file == nullptr) continue; - const auto nca = std::make_shared<NCA>(parser(file, id), nullptr, 0); + const auto nca = std::make_shared<NCA>(parser(file, id)); if (nca->GetStatus() != Loader::ResultStatus::Success || - nca->GetType() != NCAContentType::Meta) { + nca->GetType() != NCAContentType::Meta || nca->GetSubdirectories().empty()) { continue; } @@ -500,7 +500,7 @@ std::unique_ptr<NCA> RegisteredCache::GetEntry(u64 title_id, ContentRecordType t const auto raw = GetEntryRaw(title_id, type); if (raw == nullptr) return nullptr; - return std::make_unique<NCA>(raw, nullptr, 0); + return std::make_unique<NCA>(raw); } template <typename T> @@ -964,7 +964,7 @@ std::unique_ptr<NCA> ManualContentProvider::GetEntry(u64 title_id, ContentRecord const auto res = GetEntryRaw(title_id, type); if (res == nullptr) return nullptr; - return std::make_unique<NCA>(res, nullptr, 0); + return std::make_unique<NCA>(res); } std::vector<ContentProviderEntry> ManualContentProvider::ListEntriesFilter( diff --git a/src/core/file_sys/romfs_factory.cpp b/src/core/file_sys/romfs_factory.cpp index ae7a3511b..1bc07dae5 100644 --- a/src/core/file_sys/romfs_factory.cpp +++ b/src/core/file_sys/romfs_factory.cpp @@ -26,60 +26,52 @@ RomFSFactory::RomFSFactory(Loader::AppLoader& app_loader, ContentProvider& provi } updatable = app_loader.IsRomFSUpdatable(); - ivfc_offset = app_loader.ReadRomFSIVFCOffset(); } RomFSFactory::~RomFSFactory() = default; void RomFSFactory::SetPackedUpdate(VirtualFile update_raw_file) { - update_raw = std::move(update_raw_file); + packed_update_raw = std::move(update_raw_file); } -ResultVal<VirtualFile> RomFSFactory::OpenCurrentProcess(u64 current_process_title_id) const { +VirtualFile RomFSFactory::OpenCurrentProcess(u64 current_process_title_id) const { if (!updatable) { return file; } + const auto type = ContentRecordType::Program; + const auto nca = content_provider.GetEntry(current_process_title_id, type); const PatchManager patch_manager{current_process_title_id, filesystem_controller, content_provider}; - return patch_manager.PatchRomFS(file, ivfc_offset, ContentRecordType::Program, update_raw); + return patch_manager.PatchRomFS(nca.get(), file, ContentRecordType::Program, packed_update_raw); } -ResultVal<VirtualFile> RomFSFactory::OpenPatchedRomFS(u64 title_id, ContentRecordType type) const { +VirtualFile RomFSFactory::OpenPatchedRomFS(u64 title_id, ContentRecordType type) const { auto nca = content_provider.GetEntry(title_id, type); if (nca == nullptr) { - // TODO: Find the right error code to use here - return ResultUnknown; + return nullptr; } const PatchManager patch_manager{title_id, filesystem_controller, content_provider}; - return patch_manager.PatchRomFS(nca->GetRomFS(), nca->GetBaseIVFCOffset(), type); + return patch_manager.PatchRomFS(nca.get(), nca->GetRomFS(), type); } -ResultVal<VirtualFile> RomFSFactory::OpenPatchedRomFSWithProgramIndex( - u64 title_id, u8 program_index, ContentRecordType type) const { +VirtualFile RomFSFactory::OpenPatchedRomFSWithProgramIndex(u64 title_id, u8 program_index, + ContentRecordType type) const { const auto res_title_id = GetBaseTitleIDWithProgramIndex(title_id, program_index); return OpenPatchedRomFS(res_title_id, type); } -ResultVal<VirtualFile> RomFSFactory::Open(u64 title_id, StorageId storage, - ContentRecordType type) const { +VirtualFile RomFSFactory::Open(u64 title_id, StorageId storage, ContentRecordType type) const { const std::shared_ptr<NCA> res = GetEntry(title_id, storage, type); if (res == nullptr) { - // TODO(DarkLordZach): Find the right error code to use here - return ResultUnknown; - } - - const auto romfs = res->GetRomFS(); - if (romfs == nullptr) { - // TODO(DarkLordZach): Find the right error code to use here - return ResultUnknown; + return nullptr; } - return romfs; + return res->GetRomFS(); } std::shared_ptr<NCA> RomFSFactory::GetEntry(u64 title_id, StorageId storage, diff --git a/src/core/file_sys/romfs_factory.h b/src/core/file_sys/romfs_factory.h index 14936031f..e4809bc94 100644 --- a/src/core/file_sys/romfs_factory.h +++ b/src/core/file_sys/romfs_factory.h @@ -40,23 +40,22 @@ public: Service::FileSystem::FileSystemController& controller); ~RomFSFactory(); - void SetPackedUpdate(VirtualFile update_raw_file); - [[nodiscard]] ResultVal<VirtualFile> OpenCurrentProcess(u64 current_process_title_id) const; - [[nodiscard]] ResultVal<VirtualFile> OpenPatchedRomFS(u64 title_id, - ContentRecordType type) const; - [[nodiscard]] ResultVal<VirtualFile> OpenPatchedRomFSWithProgramIndex( - u64 title_id, u8 program_index, ContentRecordType type) const; - [[nodiscard]] ResultVal<VirtualFile> Open(u64 title_id, StorageId storage, - ContentRecordType type) const; - -private: + void SetPackedUpdate(VirtualFile packed_update_raw); + [[nodiscard]] VirtualFile OpenCurrentProcess(u64 current_process_title_id) const; + [[nodiscard]] VirtualFile OpenPatchedRomFS(u64 title_id, ContentRecordType type) const; + [[nodiscard]] VirtualFile OpenPatchedRomFSWithProgramIndex(u64 title_id, u8 program_index, + ContentRecordType type) const; + [[nodiscard]] VirtualFile Open(u64 title_id, StorageId storage, ContentRecordType type) const; [[nodiscard]] std::shared_ptr<NCA> GetEntry(u64 title_id, StorageId storage, ContentRecordType type) const; +private: VirtualFile file; - VirtualFile update_raw; + VirtualFile packed_update_raw; + + VirtualFile base; + bool updatable; - u64 ivfc_offset; ContentProvider& content_provider; Service::FileSystem::FileSystemController& filesystem_controller; diff --git a/src/core/file_sys/savedata_factory.cpp b/src/core/file_sys/savedata_factory.cpp index 70b36f170..a4d060007 100644 --- a/src/core/file_sys/savedata_factory.cpp +++ b/src/core/file_sys/savedata_factory.cpp @@ -108,26 +108,16 @@ SaveDataFactory::SaveDataFactory(Core::System& system_, VirtualDir save_director SaveDataFactory::~SaveDataFactory() = default; -ResultVal<VirtualDir> SaveDataFactory::Create(SaveDataSpaceId space, - const SaveDataAttribute& meta) const { +VirtualDir SaveDataFactory::Create(SaveDataSpaceId space, const SaveDataAttribute& meta) const { PrintSaveDataAttributeWarnings(meta); const auto save_directory = GetFullPath(system, dir, space, meta.type, meta.title_id, meta.user_id, meta.save_id); - auto out = dir->CreateDirectoryRelative(save_directory); - - // Return an error if the save data doesn't actually exist. - if (out == nullptr) { - // TODO(DarkLordZach): Find out correct error code. - return ResultUnknown; - } - - return out; + return dir->CreateDirectoryRelative(save_directory); } -ResultVal<VirtualDir> SaveDataFactory::Open(SaveDataSpaceId space, - const SaveDataAttribute& meta) const { +VirtualDir SaveDataFactory::Open(SaveDataSpaceId space, const SaveDataAttribute& meta) const { const auto save_directory = GetFullPath(system, dir, space, meta.type, meta.title_id, meta.user_id, meta.save_id); @@ -138,12 +128,6 @@ ResultVal<VirtualDir> SaveDataFactory::Open(SaveDataSpaceId space, return Create(space, meta); } - // Return an error if the save data doesn't actually exist. - if (out == nullptr) { - // TODO(Subv): Find out correct error code. - return ResultUnknown; - } - return out; } diff --git a/src/core/file_sys/savedata_factory.h b/src/core/file_sys/savedata_factory.h index d3633ef03..45c7c81fb 100644 --- a/src/core/file_sys/savedata_factory.h +++ b/src/core/file_sys/savedata_factory.h @@ -89,8 +89,8 @@ public: explicit SaveDataFactory(Core::System& system_, VirtualDir save_directory_); ~SaveDataFactory(); - ResultVal<VirtualDir> Create(SaveDataSpaceId space, const SaveDataAttribute& meta) const; - ResultVal<VirtualDir> Open(SaveDataSpaceId space, const SaveDataAttribute& meta) const; + VirtualDir Create(SaveDataSpaceId space, const SaveDataAttribute& meta) const; + VirtualDir Open(SaveDataSpaceId space, const SaveDataAttribute& meta) const; VirtualDir GetSaveDataSpaceDirectory(SaveDataSpaceId space) const; diff --git a/src/core/file_sys/sdmc_factory.cpp b/src/core/file_sys/sdmc_factory.cpp index 1df022c9e..d5158cd64 100644 --- a/src/core/file_sys/sdmc_factory.cpp +++ b/src/core/file_sys/sdmc_factory.cpp @@ -23,7 +23,7 @@ SDMCFactory::SDMCFactory(VirtualDir sd_dir_, VirtualDir sd_mod_dir_) SDMCFactory::~SDMCFactory() = default; -ResultVal<VirtualDir> SDMCFactory::Open() const { +VirtualDir SDMCFactory::Open() const { return sd_dir; } diff --git a/src/core/file_sys/sdmc_factory.h b/src/core/file_sys/sdmc_factory.h index 3aebfb25e..a445fdb16 100644 --- a/src/core/file_sys/sdmc_factory.h +++ b/src/core/file_sys/sdmc_factory.h @@ -18,7 +18,7 @@ public: explicit SDMCFactory(VirtualDir sd_dir_, VirtualDir sd_mod_dir_); ~SDMCFactory(); - ResultVal<VirtualDir> Open() const; + VirtualDir Open() const; VirtualDir GetSDMCModificationLoadRoot(u64 title_id) const; VirtualDir GetSDMCContentDirectory() const; diff --git a/src/core/file_sys/submission_package.cpp b/src/core/file_sys/submission_package.cpp index c90e6e372..e1e89ce2d 100644 --- a/src/core/file_sys/submission_package.cpp +++ b/src/core/file_sys/submission_package.cpp @@ -249,7 +249,7 @@ void NSP::ReadNCAs(const std::vector<VirtualFile>& files) { } const auto nca = std::make_shared<NCA>(outer_file); - if (nca->GetStatus() != Loader::ResultStatus::Success) { + if (nca->GetStatus() != Loader::ResultStatus::Success || nca->GetSubdirectories().empty()) { program_status[nca->GetTitleId()] = nca->GetStatus(); continue; } @@ -280,7 +280,7 @@ void NSP::ReadNCAs(const std::vector<VirtualFile>& files) { continue; } - auto next_nca = std::make_shared<NCA>(std::move(next_file), nullptr, 0); + auto next_nca = std::make_shared<NCA>(std::move(next_file)); if (next_nca->GetType() == NCAContentType::Program) { program_status[next_nca->GetTitleId()] = next_nca->GetStatus(); diff --git a/src/core/hid/hid_types.h b/src/core/hid/hid_types.h index 6b35f448c..00beb40dd 100644 --- a/src/core/hid/hid_types.h +++ b/src/core/hid/hid_types.h @@ -289,6 +289,19 @@ enum class GyroscopeZeroDriftMode : u32 { Tight = 2, }; +// This is nn::settings::system::TouchScreenMode +enum class TouchScreenMode : u32 { + Stylus = 0, + Standard = 1, +}; + +// This is nn::hid::TouchScreenModeForNx +enum class TouchScreenModeForNx : u8 { + UseSystemSetting, + Finger, + Heat2, +}; + // This is nn::hid::NpadStyleTag struct NpadStyleTag { union { @@ -334,6 +347,14 @@ struct TouchState { }; static_assert(sizeof(TouchState) == 0x28, "Touchstate is an invalid size"); +// This is nn::hid::TouchScreenConfigurationForNx +struct TouchScreenConfigurationForNx { + TouchScreenModeForNx mode{TouchScreenModeForNx::UseSystemSetting}; + INSERT_PADDING_BYTES(0xF); +}; +static_assert(sizeof(TouchScreenConfigurationForNx) == 0x10, + "TouchScreenConfigurationForNx is an invalid size"); + struct NpadColor { u8 r{}; u8 g{}; @@ -662,6 +683,11 @@ struct MouseState { }; static_assert(sizeof(MouseState) == 0x28, "MouseState is an invalid size"); +struct UniquePadId { + u64 id; +}; +static_assert(sizeof(UniquePadId) == 0x8, "UniquePadId is an invalid size"); + /// Converts a NpadIdType to an array index. constexpr size_t NpadIdTypeToIndex(NpadIdType npad_id_type) { switch (npad_id_type) { diff --git a/src/core/hle/kernel/k_hardware_timer.h b/src/core/hle/kernel/k_hardware_timer.h index 00bef6ea1..27f43cd19 100644 --- a/src/core/hle/kernel/k_hardware_timer.h +++ b/src/core/hle/kernel/k_hardware_timer.h @@ -19,13 +19,7 @@ public: void Initialize(); void Finalize(); - s64 GetCount() const { - return GetTick(); - } - - void RegisterTask(KTimerTask* task, s64 time_from_now) { - this->RegisterAbsoluteTask(task, GetTick() + time_from_now); - } + s64 GetTick() const; void RegisterAbsoluteTask(KTimerTask* task, s64 task_time) { KScopedDisableDispatch dd{m_kernel}; @@ -42,7 +36,6 @@ private: void EnableInterrupt(s64 wakeup_time); void DisableInterrupt(); bool GetInterruptEnabled(); - s64 GetTick() const; void DoTask(); private: diff --git a/src/core/hle/kernel/k_page_table.cpp b/src/core/hle/kernel/k_page_table.cpp index 02b5cada4..9bfc85b34 100644 --- a/src/core/hle/kernel/k_page_table.cpp +++ b/src/core/hle/kernel/k_page_table.cpp @@ -768,7 +768,7 @@ Result KPageTable::UnmapProcessMemory(KProcessAddress dst_addr, size_t size, m_memory_block_slab_manager, num_allocator_blocks); R_TRY(allocator_result); - CASCADE_CODE(Operate(dst_addr, num_pages, KMemoryPermission::None, OperationType::Unmap)); + R_TRY(Operate(dst_addr, num_pages, KMemoryPermission::None, OperationType::Unmap)); // Apply the memory block update. m_memory_block_manager.Update(std::addressof(allocator), dst_addr, num_pages, diff --git a/src/core/hle/kernel/k_resource_limit.cpp b/src/core/hle/kernel/k_resource_limit.cpp index fcee26a29..d8a63aaf8 100644 --- a/src/core/hle/kernel/k_resource_limit.cpp +++ b/src/core/hle/kernel/k_resource_limit.cpp @@ -5,6 +5,7 @@ #include "common/overflow.h" #include "core/core.h" #include "core/core_timing.h" +#include "core/hle/kernel/k_hardware_timer.h" #include "core/hle/kernel/k_resource_limit.h" #include "core/hle/kernel/svc_results.h" @@ -15,9 +16,7 @@ KResourceLimit::KResourceLimit(KernelCore& kernel) : KAutoObjectWithSlabHeapAndContainer{kernel}, m_lock{m_kernel}, m_cond_var{m_kernel} {} KResourceLimit::~KResourceLimit() = default; -void KResourceLimit::Initialize(const Core::Timing::CoreTiming* core_timing) { - m_core_timing = core_timing; -} +void KResourceLimit::Initialize() {} void KResourceLimit::Finalize() {} @@ -86,7 +85,7 @@ Result KResourceLimit::SetLimitValue(LimitableResource which, s64 value) { } bool KResourceLimit::Reserve(LimitableResource which, s64 value) { - return Reserve(which, value, m_core_timing->GetGlobalTimeNs().count() + DefaultTimeout); + return Reserve(which, value, m_kernel.HardwareTimer().GetTick() + DefaultTimeout); } bool KResourceLimit::Reserve(LimitableResource which, s64 value, s64 timeout) { @@ -117,7 +116,7 @@ bool KResourceLimit::Reserve(LimitableResource which, s64 value, s64 timeout) { } if (m_current_hints[index] + value <= m_limit_values[index] && - (timeout < 0 || m_core_timing->GetGlobalTimeNs().count() < timeout)) { + (timeout < 0 || m_kernel.HardwareTimer().GetTick() < timeout)) { m_waiter_count++; m_cond_var.Wait(std::addressof(m_lock), timeout, false); m_waiter_count--; @@ -154,7 +153,7 @@ void KResourceLimit::Release(LimitableResource which, s64 value, s64 hint) { KResourceLimit* CreateResourceLimitForProcess(Core::System& system, s64 physical_memory_size) { auto* resource_limit = KResourceLimit::Create(system.Kernel()); - resource_limit->Initialize(std::addressof(system.CoreTiming())); + resource_limit->Initialize(); // Initialize default resource limit values. // TODO(bunnei): These values are the system defaults, the limits for service processes are diff --git a/src/core/hle/kernel/k_resource_limit.h b/src/core/hle/kernel/k_resource_limit.h index 15e69af56..b733ec8f8 100644 --- a/src/core/hle/kernel/k_resource_limit.h +++ b/src/core/hle/kernel/k_resource_limit.h @@ -31,7 +31,7 @@ public: explicit KResourceLimit(KernelCore& kernel); ~KResourceLimit() override; - void Initialize(const Core::Timing::CoreTiming* core_timing); + void Initialize(); void Finalize() override; s64 GetLimitValue(LimitableResource which) const; @@ -57,7 +57,6 @@ private: mutable KLightLock m_lock; s32 m_waiter_count{}; KLightConditionVariable m_cond_var; - const Core::Timing::CoreTiming* m_core_timing{}; }; KResourceLimit* CreateResourceLimitForProcess(Core::System& system, s64 physical_memory_size); diff --git a/src/core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h b/src/core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h index c485022f5..b62415da7 100644 --- a/src/core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h +++ b/src/core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h @@ -28,7 +28,7 @@ public: ~KScopedSchedulerLockAndSleep() { // Register the sleep. if (m_timeout_tick > 0) { - m_timer->RegisterTask(m_thread, m_timeout_tick); + m_timer->RegisterAbsoluteTask(m_thread, m_timeout_tick); } // Unlock the scheduler. diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index ebe7582c6..a1134b7e2 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp @@ -231,7 +231,7 @@ struct KernelCore::Impl { void InitializeSystemResourceLimit(KernelCore& kernel, const Core::Timing::CoreTiming& core_timing) { system_resource_limit = KResourceLimit::Create(system.Kernel()); - system_resource_limit->Initialize(&core_timing); + system_resource_limit->Initialize(); KResourceLimit::Register(kernel, system_resource_limit); const auto sizes{memory_layout->GetTotalAndKernelMemorySizes()}; diff --git a/src/core/hle/kernel/svc/svc_address_arbiter.cpp b/src/core/hle/kernel/svc/svc_address_arbiter.cpp index 04cc5ea64..90ee43521 100644 --- a/src/core/hle/kernel/svc/svc_address_arbiter.cpp +++ b/src/core/hle/kernel/svc/svc_address_arbiter.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "core/core.h" +#include "core/hle/kernel/k_hardware_timer.h" #include "core/hle/kernel/k_memory_layout.h" #include "core/hle/kernel/k_process.h" #include "core/hle/kernel/kernel.h" @@ -52,7 +53,7 @@ Result WaitForAddress(Core::System& system, u64 address, ArbitrationType arb_typ if (timeout_ns > 0) { const s64 offset_tick(timeout_ns); if (offset_tick > 0) { - timeout = offset_tick + 2; + timeout = system.Kernel().HardwareTimer().GetTick() + offset_tick + 2; if (timeout <= 0) { timeout = std::numeric_limits<s64>::max(); } diff --git a/src/core/hle/kernel/svc/svc_condition_variable.cpp b/src/core/hle/kernel/svc/svc_condition_variable.cpp index ca120d67e..bb678e6c5 100644 --- a/src/core/hle/kernel/svc/svc_condition_variable.cpp +++ b/src/core/hle/kernel/svc/svc_condition_variable.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "core/core.h" +#include "core/hle/kernel/k_hardware_timer.h" #include "core/hle/kernel/k_memory_layout.h" #include "core/hle/kernel/k_process.h" #include "core/hle/kernel/kernel.h" @@ -25,7 +26,7 @@ Result WaitProcessWideKeyAtomic(Core::System& system, u64 address, u64 cv_key, u if (timeout_ns > 0) { const s64 offset_tick(timeout_ns); if (offset_tick > 0) { - timeout = offset_tick + 2; + timeout = system.Kernel().HardwareTimer().GetTick() + offset_tick + 2; if (timeout <= 0) { timeout = std::numeric_limits<s64>::max(); } diff --git a/src/core/hle/kernel/svc/svc_ipc.cpp b/src/core/hle/kernel/svc/svc_ipc.cpp index 373ae7c8d..6b5e1cb8d 100644 --- a/src/core/hle/kernel/svc/svc_ipc.cpp +++ b/src/core/hle/kernel/svc/svc_ipc.cpp @@ -5,6 +5,7 @@ #include "common/scratch_buffer.h" #include "core/core.h" #include "core/hle/kernel/k_client_session.h" +#include "core/hle/kernel/k_hardware_timer.h" #include "core/hle/kernel/k_process.h" #include "core/hle/kernel/k_server_session.h" #include "core/hle/kernel/svc.h" @@ -82,12 +83,29 @@ Result ReplyAndReceive(Core::System& system, s32* out_index, uint64_t handles_ad R_TRY(session->SendReply()); } + // Convert the timeout from nanoseconds to ticks. + // NOTE: Nintendo does not use this conversion logic in WaitSynchronization... + s64 timeout; + if (timeout_ns > 0) { + const s64 offset_tick(timeout_ns); + if (offset_tick > 0) { + timeout = kernel.HardwareTimer().GetTick() + offset_tick + 2; + if (timeout <= 0) { + timeout = std::numeric_limits<s64>::max(); + } + } else { + timeout = std::numeric_limits<s64>::max(); + } + } else { + timeout = timeout_ns; + } + // Wait for a message. while (true) { // Wait for an object. s32 index; Result result = KSynchronizationObject::Wait(kernel, std::addressof(index), objs.data(), - num_handles, timeout_ns); + num_handles, timeout); if (result == ResultTimedOut) { R_RETURN(result); } diff --git a/src/core/hle/kernel/svc/svc_resource_limit.cpp b/src/core/hle/kernel/svc/svc_resource_limit.cpp index 732bc017e..c8e820b6a 100644 --- a/src/core/hle/kernel/svc/svc_resource_limit.cpp +++ b/src/core/hle/kernel/svc/svc_resource_limit.cpp @@ -21,7 +21,7 @@ Result CreateResourceLimit(Core::System& system, Handle* out_handle) { SCOPE_EXIT({ resource_limit->Close(); }); // Initialize the resource limit. - resource_limit->Initialize(std::addressof(system.CoreTiming())); + resource_limit->Initialize(); // Register the limit. KResourceLimit::Register(kernel, resource_limit); diff --git a/src/core/hle/kernel/svc/svc_synchronization.cpp b/src/core/hle/kernel/svc/svc_synchronization.cpp index 366e8ed4a..8ebc1bd1c 100644 --- a/src/core/hle/kernel/svc/svc_synchronization.cpp +++ b/src/core/hle/kernel/svc/svc_synchronization.cpp @@ -4,6 +4,7 @@ #include "common/scope_exit.h" #include "common/scratch_buffer.h" #include "core/core.h" +#include "core/hle/kernel/k_hardware_timer.h" #include "core/hle/kernel/k_process.h" #include "core/hle/kernel/k_readable_event.h" #include "core/hle/kernel/svc.h" @@ -83,9 +84,20 @@ Result WaitSynchronization(Core::System& system, int32_t* out_index, u64 user_ha } }); + // Convert the timeout from nanoseconds to ticks. + s64 timeout; + if (timeout_ns > 0) { + u64 ticks = kernel.HardwareTimer().GetTick(); + ticks += timeout_ns; + ticks += 2; + + timeout = ticks; + } else { + timeout = timeout_ns; + } + // Wait on the objects. - Result res = - KSynchronizationObject::Wait(kernel, out_index, objs.data(), num_handles, timeout_ns); + Result res = KSynchronizationObject::Wait(kernel, out_index, objs.data(), num_handles, timeout); R_SUCCEED_IF(res == ResultSessionClosed); R_RETURN(res); diff --git a/src/core/hle/kernel/svc/svc_thread.cpp b/src/core/hle/kernel/svc/svc_thread.cpp index 92bcea72b..933b82e30 100644 --- a/src/core/hle/kernel/svc/svc_thread.cpp +++ b/src/core/hle/kernel/svc/svc_thread.cpp @@ -4,6 +4,7 @@ #include "common/scope_exit.h" #include "core/core.h" #include "core/core_timing.h" +#include "core/hle/kernel/k_hardware_timer.h" #include "core/hle/kernel/k_process.h" #include "core/hle/kernel/k_scoped_resource_reservation.h" #include "core/hle/kernel/k_thread.h" @@ -42,9 +43,9 @@ Result CreateThread(Core::System& system, Handle* out_handle, u64 entry_point, u R_UNLESS(process.CheckThreadPriority(priority), ResultInvalidPriority); // Reserve a new thread from the process resource limit (waiting up to 100ms). - KScopedResourceReservation thread_reservation( - std::addressof(process), LimitableResource::ThreadCountMax, 1, - system.CoreTiming().GetGlobalTimeNs().count() + 100000000); + KScopedResourceReservation thread_reservation(std::addressof(process), + LimitableResource::ThreadCountMax, 1, + kernel.HardwareTimer().GetTick() + 100000000); R_UNLESS(thread_reservation.Succeeded(), ResultLimitReached); // Create the thread. @@ -102,20 +103,31 @@ void ExitThread(Core::System& system) { } /// Sleep the current thread -void SleepThread(Core::System& system, s64 nanoseconds) { +void SleepThread(Core::System& system, s64 ns) { auto& kernel = system.Kernel(); - const auto yield_type = static_cast<Svc::YieldType>(nanoseconds); + const auto yield_type = static_cast<Svc::YieldType>(ns); - LOG_TRACE(Kernel_SVC, "called nanoseconds={}", nanoseconds); + LOG_TRACE(Kernel_SVC, "called nanoseconds={}", ns); // When the input tick is positive, sleep. - if (nanoseconds > 0) { + if (ns > 0) { // Convert the timeout from nanoseconds to ticks. // NOTE: Nintendo does not use this conversion logic in WaitSynchronization... + s64 timeout; + + const s64 offset_tick(ns); + if (offset_tick > 0) { + timeout = kernel.HardwareTimer().GetTick() + offset_tick + 2; + if (timeout <= 0) { + timeout = std::numeric_limits<s64>::max(); + } + } else { + timeout = std::numeric_limits<s64>::max(); + } // Sleep. // NOTE: Nintendo does not check the result of this sleep. - static_cast<void>(GetCurrentThread(kernel).Sleep(nanoseconds)); + static_cast<void>(GetCurrentThread(kernel).Sleep(timeout)); } else if (yield_type == Svc::YieldType::WithoutCoreMigration) { KScheduler::YieldWithoutCoreMigration(kernel); } else if (yield_type == Svc::YieldType::WithCoreMigration) { @@ -124,7 +136,6 @@ void SleepThread(Core::System& system, s64 nanoseconds) { KScheduler::YieldToAnyThread(kernel); } else { // Nintendo does nothing at all if an otherwise invalid value is passed. - ASSERT_MSG(false, "Unimplemented sleep yield type '{:016X}'!", nanoseconds); } } diff --git a/src/core/hle/result.h b/src/core/hle/result.h index 240f06689..92a1439eb 100644 --- a/src/core/hle/result.h +++ b/src/core/hle/result.h @@ -283,159 +283,6 @@ private: u32 description_end; }; -/** - * This is an optional value type. It holds a `Result` and, if that code is ResultSuccess, it - * also holds a result of type `T`. If the code is an error code (not ResultSuccess), then trying - * to access the inner value with operator* is undefined behavior and will assert with Unwrap(). - * Users of this class must be cognizant to check the status of the ResultVal with operator bool(), - * Code(), Succeeded() or Failed() prior to accessing the inner value. - * - * An example of how it could be used: - * \code - * ResultVal<int> Frobnicate(float strength) { - * if (strength < 0.f || strength > 1.0f) { - * // Can't frobnicate too weakly or too strongly - * return Result{ErrorModule::Common, 1}; - * } else { - * // Frobnicated! Give caller a cookie - * return 42; - * } - * } - * \endcode - * - * \code - * auto frob_result = Frobnicate(0.75f); - * if (frob_result) { - * // Frobbed ok - * printf("My cookie is %d\n", *frob_result); - * } else { - * printf("Guess I overdid it. :( Error code: %ux\n", frob_result.Code().raw); - * } - * \endcode - */ -template <typename T> -class ResultVal { -public: - constexpr ResultVal() : expected{} {} - - constexpr ResultVal(Result code) : expected{Common::Unexpected(code)} {} - - constexpr ResultVal(ResultRange range) : expected{Common::Unexpected(range)} {} - - template <typename U> - constexpr ResultVal(U&& val) : expected{std::forward<U>(val)} {} - - template <typename... Args> - constexpr ResultVal(Args&&... args) : expected{std::in_place, std::forward<Args>(args)...} {} - - ~ResultVal() = default; - - constexpr ResultVal(const ResultVal&) = default; - constexpr ResultVal(ResultVal&&) = default; - - ResultVal& operator=(const ResultVal&) = default; - ResultVal& operator=(ResultVal&&) = default; - - [[nodiscard]] constexpr explicit operator bool() const noexcept { - return expected.has_value(); - } - - [[nodiscard]] constexpr Result Code() const { - return expected.has_value() ? ResultSuccess : expected.error(); - } - - [[nodiscard]] constexpr bool Succeeded() const { - return expected.has_value(); - } - - [[nodiscard]] constexpr bool Failed() const { - return !expected.has_value(); - } - - [[nodiscard]] constexpr T* operator->() { - return std::addressof(expected.value()); - } - - [[nodiscard]] constexpr const T* operator->() const { - return std::addressof(expected.value()); - } - - [[nodiscard]] constexpr T& operator*() & { - return *expected; - } - - [[nodiscard]] constexpr const T& operator*() const& { - return *expected; - } - - [[nodiscard]] constexpr T&& operator*() && { - return *expected; - } - - [[nodiscard]] constexpr const T&& operator*() const&& { - return *expected; - } - - [[nodiscard]] constexpr T& Unwrap() & { - ASSERT_MSG(Succeeded(), "Tried to Unwrap empty ResultVal"); - return expected.value(); - } - - [[nodiscard]] constexpr const T& Unwrap() const& { - ASSERT_MSG(Succeeded(), "Tried to Unwrap empty ResultVal"); - return expected.value(); - } - - [[nodiscard]] constexpr T&& Unwrap() && { - ASSERT_MSG(Succeeded(), "Tried to Unwrap empty ResultVal"); - return std::move(expected.value()); - } - - [[nodiscard]] constexpr const T&& Unwrap() const&& { - ASSERT_MSG(Succeeded(), "Tried to Unwrap empty ResultVal"); - return std::move(expected.value()); - } - - template <typename U> - [[nodiscard]] constexpr T ValueOr(U&& v) const& { - return expected.value_or(v); - } - - template <typename U> - [[nodiscard]] constexpr T ValueOr(U&& v) && { - return expected.value_or(v); - } - -private: - // TODO (Morph): Replace this with C++23 std::expected. - Common::Expected<T, Result> expected; -}; - -/** - * Check for the success of `source` (which must evaluate to a ResultVal). If it succeeds, unwraps - * the contained value and assigns it to `target`, which can be either an l-value expression or a - * variable declaration. If it fails the return code is returned from the current function. Thus it - * can be used to cascade errors out, achieving something akin to exception handling. - */ -#define CASCADE_RESULT(target, source) \ - auto CONCAT2(check_result_L, __LINE__) = source; \ - if (CONCAT2(check_result_L, __LINE__).Failed()) { \ - return CONCAT2(check_result_L, __LINE__).Code(); \ - } \ - target = std::move(*CONCAT2(check_result_L, __LINE__)) - -/** - * Analogous to CASCADE_RESULT, but for a bare Result. The code will be propagated if - * non-success, or discarded otherwise. - */ -#define CASCADE_CODE(source) \ - do { \ - auto CONCAT2(check_result_L, __LINE__) = source; \ - if (CONCAT2(check_result_L, __LINE__).IsError()) { \ - return CONCAT2(check_result_L, __LINE__); \ - } \ - } while (false) - #define R_SUCCEEDED(res) (static_cast<Result>(res).IsSuccess()) #define R_FAILED(res) (static_cast<Result>(res).IsFailure()) diff --git a/src/core/hle/service/acc/acc.cpp b/src/core/hle/service/acc/acc.cpp index 2632cd3ef..b971401e6 100644 --- a/src/core/hle/service/acc/acc.cpp +++ b/src/core/hle/service/acc/acc.cpp @@ -765,15 +765,16 @@ Result Module::Interface::InitializeApplicationInfoBase() { // TODO(ogniK): This should be changed to reflect the target process for when we have multiple // processes emulated. As we don't actually have pid support we should assume we're just using // our own process - const auto launch_property = - system.GetARPManager().GetLaunchProperty(system.GetApplicationProcessProgramID()); + Glue::ApplicationLaunchProperty launch_property{}; + const auto result = system.GetARPManager().GetLaunchProperty( + &launch_property, system.GetApplicationProcessProgramID()); - if (launch_property.Failed()) { + if (result != ResultSuccess) { LOG_ERROR(Service_ACC, "Failed to get launch property"); return Account::ResultInvalidApplication; } - switch (launch_property->base_game_storage_id) { + switch (launch_property.base_game_storage_id) { case FileSys::StorageId::GameCard: application_info.application_type = ApplicationType::GameCard; break; @@ -785,7 +786,7 @@ Result Module::Interface::InitializeApplicationInfoBase() { break; default: LOG_ERROR(Service_ACC, "Invalid game storage ID! storage_id={}", - launch_property->base_game_storage_id); + launch_property.base_game_storage_id); return Account::ResultInvalidApplication; } diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index 585675718..8d057b3a8 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -1578,11 +1578,13 @@ void IApplicationFunctions::EnsureSaveData(HLERequestContext& ctx) { attribute.title_id = system.GetApplicationProcessProgramID(); attribute.user_id = user_id; attribute.type = FileSys::SaveDataType::SaveData; + + FileSys::VirtualDir save_data{}; const auto res = system.GetFileSystemController().CreateSaveData( - FileSys::SaveDataSpaceId::NandUser, attribute); + &save_data, FileSys::SaveDataSpaceId::NandUser, attribute); IPC::ResponseBuilder rb{ctx, 4}; - rb.Push(res.Code()); + rb.Push(res); rb.Push<u64>(0); } @@ -1667,26 +1669,30 @@ void IApplicationFunctions::GetDesiredLanguage(HLERequestContext& ctx) { auto app_man = ns_am2->GetApplicationManagerInterface(); // Get desired application language - const auto res_lang = app_man->GetApplicationDesiredLanguage(supported_languages); - if (res_lang.Failed()) { + u8 desired_language{}; + const auto res_lang = + app_man->GetApplicationDesiredLanguage(&desired_language, supported_languages); + if (res_lang != ResultSuccess) { IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(res_lang.Code()); + rb.Push(res_lang); return; } // Convert to settings language code. - const auto res_code = app_man->ConvertApplicationLanguageToLanguageCode(*res_lang); - if (res_code.Failed()) { + u64 language_code{}; + const auto res_code = + app_man->ConvertApplicationLanguageToLanguageCode(&language_code, desired_language); + if (res_code != ResultSuccess) { IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(res_code.Code()); + rb.Push(res_code); return; } - LOG_DEBUG(Service_AM, "got desired_language={:016X}", *res_code); + LOG_DEBUG(Service_AM, "got desired_language={:016X}", language_code); IPC::ResponseBuilder rb{ctx, 4}; rb.Push(ResultSuccess); - rb.Push(*res_code); + rb.Push(language_code); } void IApplicationFunctions::IsGamePlayRecordingSupported(HLERequestContext& ctx) { diff --git a/src/core/hle/service/am/applets/applet_web_browser.cpp b/src/core/hle/service/am/applets/applet_web_browser.cpp index 2accf7898..1c9a1dc29 100644 --- a/src/core/hle/service/am/applets/applet_web_browser.cpp +++ b/src/core/hle/service/am/applets/applet_web_browser.cpp @@ -139,7 +139,7 @@ FileSys::VirtualFile GetOfflineRomFS(Core::System& system, u64 title_id, const FileSys::PatchManager pm{title_id, system.GetFileSystemController(), system.GetContentProvider()}; - return pm.PatchRomFS(nca->GetRomFS(), nca->GetBaseIVFCOffset(), nca_type); + return pm.PatchRomFS(nca.get(), nca->GetRomFS(), nca_type); } } diff --git a/src/core/hle/service/audio/audctl.cpp b/src/core/hle/service/audio/audctl.cpp index 7ad93be6b..66dd64fd1 100644 --- a/src/core/hle/service/audio/audctl.cpp +++ b/src/core/hle/service/audio/audctl.cpp @@ -22,13 +22,13 @@ AudCtl::AudCtl(Core::System& system_) : ServiceFramework{system_, "audctl"} { {9, nullptr, "GetAudioOutputMode"}, {10, nullptr, "SetAudioOutputMode"}, {11, nullptr, "SetForceMutePolicy"}, - {12, nullptr, "GetForceMutePolicy"}, - {13, nullptr, "GetOutputModeSetting"}, + {12, &AudCtl::GetForceMutePolicy, "GetForceMutePolicy"}, + {13, &AudCtl::GetOutputModeSetting, "GetOutputModeSetting"}, {14, nullptr, "SetOutputModeSetting"}, {15, nullptr, "SetOutputTarget"}, {16, nullptr, "SetInputTargetForceEnabled"}, {17, nullptr, "SetHeadphoneOutputLevelMode"}, - {18, nullptr, "GetHeadphoneOutputLevelMode"}, + {18, &AudCtl::GetHeadphoneOutputLevelMode, "GetHeadphoneOutputLevelMode"}, {19, nullptr, "AcquireAudioVolumeUpdateEventForPlayReport"}, {20, nullptr, "AcquireAudioOutputDeviceUpdateEventForPlayReport"}, {21, nullptr, "GetAudioOutputTargetForPlayReport"}, @@ -41,7 +41,7 @@ AudCtl::AudCtl(Core::System& system_) : ServiceFramework{system_, "audctl"} { {28, nullptr, "GetAudioOutputChannelCountForPlayReport"}, {29, nullptr, "BindAudioOutputChannelCountUpdateEventForPlayReport"}, {30, nullptr, "SetSpeakerAutoMuteEnabled"}, - {31, nullptr, "IsSpeakerAutoMuteEnabled"}, + {31, &AudCtl::IsSpeakerAutoMuteEnabled, "IsSpeakerAutoMuteEnabled"}, {32, nullptr, "GetActiveOutputTarget"}, {33, nullptr, "GetTargetDeviceInfo"}, {34, nullptr, "AcquireTargetNotification"}, @@ -96,4 +96,42 @@ void AudCtl::GetTargetVolumeMax(HLERequestContext& ctx) { rb.Push(target_max_volume); } +void AudCtl::GetForceMutePolicy(HLERequestContext& ctx) { + LOG_WARNING(Audio, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.PushEnum(ForceMutePolicy::Disable); +} + +void AudCtl::GetOutputModeSetting(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto value = rp.Pop<u32>(); + + LOG_WARNING(Audio, "(STUBBED) called, value={}", value); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.PushEnum(AudioOutputMode::PcmAuto); +} + +void AudCtl::GetHeadphoneOutputLevelMode(HLERequestContext& ctx) { + LOG_WARNING(Audio, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.PushEnum(HeadphoneOutputLevelMode::Normal); +} + +void AudCtl::IsSpeakerAutoMuteEnabled(HLERequestContext& ctx) { + const bool is_speaker_auto_mute_enabled = false; + + LOG_WARNING(Audio, "(STUBBED) called, is_speaker_auto_mute_enabled={}", + is_speaker_auto_mute_enabled); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push<u8>(is_speaker_auto_mute_enabled); +} + } // namespace Service::Audio diff --git a/src/core/hle/service/audio/audctl.h b/src/core/hle/service/audio/audctl.h index 8e31ac237..d57abb383 100644 --- a/src/core/hle/service/audio/audctl.h +++ b/src/core/hle/service/audio/audctl.h @@ -17,8 +17,30 @@ public: ~AudCtl() override; private: + enum class AudioOutputMode { + Invalid, + Pcm1ch, + Pcm2ch, + Pcm6ch, + PcmAuto, + }; + + enum class ForceMutePolicy { + Disable, + SpeakerMuteOnHeadphoneUnplugged, + }; + + enum class HeadphoneOutputLevelMode { + Normal, + HighPower, + }; + void GetTargetVolumeMin(HLERequestContext& ctx); void GetTargetVolumeMax(HLERequestContext& ctx); + void GetForceMutePolicy(HLERequestContext& ctx); + void GetOutputModeSetting(HLERequestContext& ctx); + void GetHeadphoneOutputLevelMode(HLERequestContext& ctx); + void IsSpeakerAutoMuteEnabled(HLERequestContext& ctx); }; } // namespace Service::Audio diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp index dfcdd3ada..4c1ea1a5b 100644 --- a/src/core/hle/service/filesystem/filesystem.cpp +++ b/src/core/hle/service/filesystem/filesystem.cpp @@ -57,8 +57,8 @@ Result VfsDirectoryServiceWrapper::CreateFile(const std::string& path_, u64 size return FileSys::ERROR_PATH_NOT_FOUND; } - const auto entry_type = GetEntryType(path); - if (entry_type.Code() == ResultSuccess) { + FileSys::EntryType entry_type{}; + if (GetEntryType(&entry_type, path) == ResultSuccess) { return FileSys::ERROR_PATH_ALREADY_EXISTS; } @@ -210,8 +210,8 @@ Result VfsDirectoryServiceWrapper::RenameDirectory(const std::string& src_path_, return ResultUnknown; } -ResultVal<FileSys::VirtualFile> VfsDirectoryServiceWrapper::OpenFile(const std::string& path_, - FileSys::Mode mode) const { +Result VfsDirectoryServiceWrapper::OpenFile(FileSys::VirtualFile* out_file, + const std::string& path_, FileSys::Mode mode) const { const std::string path(Common::FS::SanitizePath(path_)); std::string_view npath = path; while (!npath.empty() && (npath[0] == '/' || npath[0] == '\\')) { @@ -224,50 +224,68 @@ ResultVal<FileSys::VirtualFile> VfsDirectoryServiceWrapper::OpenFile(const std:: } if (mode == FileSys::Mode::Append) { - return std::make_shared<FileSys::OffsetVfsFile>(file, 0, file->GetSize()); + *out_file = std::make_shared<FileSys::OffsetVfsFile>(file, 0, file->GetSize()); + } else { + *out_file = file; } - return file; + return ResultSuccess; } -ResultVal<FileSys::VirtualDir> VfsDirectoryServiceWrapper::OpenDirectory(const std::string& path_) { +Result VfsDirectoryServiceWrapper::OpenDirectory(FileSys::VirtualDir* out_directory, + const std::string& path_) { std::string path(Common::FS::SanitizePath(path_)); auto dir = GetDirectoryRelativeWrapped(backing, path); if (dir == nullptr) { // TODO(DarkLordZach): Find a better error code for this return FileSys::ERROR_PATH_NOT_FOUND; } - return dir; + *out_directory = dir; + return ResultSuccess; } -ResultVal<FileSys::EntryType> VfsDirectoryServiceWrapper::GetEntryType( - const std::string& path_) const { +Result VfsDirectoryServiceWrapper::GetEntryType(FileSys::EntryType* out_entry_type, + const std::string& path_) const { std::string path(Common::FS::SanitizePath(path_)); auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(path)); - if (dir == nullptr) + if (dir == nullptr) { return FileSys::ERROR_PATH_NOT_FOUND; + } + auto filename = Common::FS::GetFilename(path); // TODO(Subv): Some games use the '/' path, find out what this means. - if (filename.empty()) - return FileSys::EntryType::Directory; + if (filename.empty()) { + *out_entry_type = FileSys::EntryType::Directory; + return ResultSuccess; + } + + if (dir->GetFile(filename) != nullptr) { + *out_entry_type = FileSys::EntryType::File; + return ResultSuccess; + } + + if (dir->GetSubdirectory(filename) != nullptr) { + *out_entry_type = FileSys::EntryType::Directory; + return ResultSuccess; + } - if (dir->GetFile(filename) != nullptr) - return FileSys::EntryType::File; - if (dir->GetSubdirectory(filename) != nullptr) - return FileSys::EntryType::Directory; return FileSys::ERROR_PATH_NOT_FOUND; } -ResultVal<FileSys::FileTimeStampRaw> VfsDirectoryServiceWrapper::GetFileTimeStampRaw( - const std::string& path) const { +Result VfsDirectoryServiceWrapper::GetFileTimeStampRaw( + FileSys::FileTimeStampRaw* out_file_time_stamp_raw, const std::string& path) const { auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(path)); if (dir == nullptr) { return FileSys::ERROR_PATH_NOT_FOUND; } - if (GetEntryType(path).Failed()) { + + FileSys::EntryType entry_type; + if (GetEntryType(&entry_type, path) != ResultSuccess) { return FileSys::ERROR_PATH_NOT_FOUND; } - return dir->GetFileTimeStamp(Common::FS::GetFilename(path)); + + *out_file_time_stamp_raw = dir->GetFileTimeStamp(Common::FS::GetFilename(path)); + return ResultSuccess; } FileSystemController::FileSystemController(Core::System& system_) : system{system_} {} @@ -310,57 +328,59 @@ void FileSystemController::SetPackedUpdate(FileSys::VirtualFile update_raw) { romfs_factory->SetPackedUpdate(std::move(update_raw)); } -ResultVal<FileSys::VirtualFile> FileSystemController::OpenRomFSCurrentProcess() const { +FileSys::VirtualFile FileSystemController::OpenRomFSCurrentProcess() const { LOG_TRACE(Service_FS, "Opening RomFS for current process"); if (romfs_factory == nullptr) { - // TODO(bunnei): Find a better error code for this - return ResultUnknown; + return nullptr; } return romfs_factory->OpenCurrentProcess(system.GetApplicationProcessProgramID()); } -ResultVal<FileSys::VirtualFile> FileSystemController::OpenPatchedRomFS( - u64 title_id, FileSys::ContentRecordType type) const { +FileSys::VirtualFile FileSystemController::OpenPatchedRomFS(u64 title_id, + FileSys::ContentRecordType type) const { LOG_TRACE(Service_FS, "Opening patched RomFS for title_id={:016X}", title_id); if (romfs_factory == nullptr) { - // TODO: Find a better error code for this - return ResultUnknown; + return nullptr; } return romfs_factory->OpenPatchedRomFS(title_id, type); } -ResultVal<FileSys::VirtualFile> FileSystemController::OpenPatchedRomFSWithProgramIndex( +FileSys::VirtualFile FileSystemController::OpenPatchedRomFSWithProgramIndex( u64 title_id, u8 program_index, FileSys::ContentRecordType type) const { LOG_TRACE(Service_FS, "Opening patched RomFS for title_id={:016X}, program_index={}", title_id, program_index); if (romfs_factory == nullptr) { - // TODO: Find a better error code for this - return ResultUnknown; + return nullptr; } return romfs_factory->OpenPatchedRomFSWithProgramIndex(title_id, program_index, type); } -ResultVal<FileSys::VirtualFile> FileSystemController::OpenRomFS( - u64 title_id, FileSys::StorageId storage_id, FileSys::ContentRecordType type) const { +FileSys::VirtualFile FileSystemController::OpenRomFS(u64 title_id, FileSys::StorageId storage_id, + FileSys::ContentRecordType type) const { LOG_TRACE(Service_FS, "Opening RomFS for title_id={:016X}, storage_id={:02X}, type={:02X}", title_id, storage_id, type); if (romfs_factory == nullptr) { - // TODO(bunnei): Find a better error code for this - return ResultUnknown; + return nullptr; } return romfs_factory->Open(title_id, storage_id, type); } -ResultVal<FileSys::VirtualDir> FileSystemController::CreateSaveData( - FileSys::SaveDataSpaceId space, const FileSys::SaveDataAttribute& save_struct) const { +std::shared_ptr<FileSys::NCA> FileSystemController::OpenBaseNca( + u64 title_id, FileSys::StorageId storage_id, FileSys::ContentRecordType type) const { + return romfs_factory->GetEntry(title_id, storage_id, type); +} + +Result FileSystemController::CreateSaveData(FileSys::VirtualDir* out_save_data, + FileSys::SaveDataSpaceId space, + const FileSys::SaveDataAttribute& save_struct) const { LOG_TRACE(Service_FS, "Creating Save Data for space_id={:01X}, save_struct={}", space, save_struct.DebugInfo()); @@ -368,11 +388,18 @@ ResultVal<FileSys::VirtualDir> FileSystemController::CreateSaveData( return FileSys::ERROR_ENTITY_NOT_FOUND; } - return save_data_factory->Create(space, save_struct); + auto save_data = save_data_factory->Create(space, save_struct); + if (save_data == nullptr) { + return FileSys::ERROR_ENTITY_NOT_FOUND; + } + + *out_save_data = save_data; + return ResultSuccess; } -ResultVal<FileSys::VirtualDir> FileSystemController::OpenSaveData( - FileSys::SaveDataSpaceId space, const FileSys::SaveDataAttribute& attribute) const { +Result FileSystemController::OpenSaveData(FileSys::VirtualDir* out_save_data, + FileSys::SaveDataSpaceId space, + const FileSys::SaveDataAttribute& attribute) const { LOG_TRACE(Service_FS, "Opening Save Data for space_id={:01X}, save_struct={}", space, attribute.DebugInfo()); @@ -380,32 +407,50 @@ ResultVal<FileSys::VirtualDir> FileSystemController::OpenSaveData( return FileSys::ERROR_ENTITY_NOT_FOUND; } - return save_data_factory->Open(space, attribute); + auto save_data = save_data_factory->Open(space, attribute); + if (save_data == nullptr) { + return FileSys::ERROR_ENTITY_NOT_FOUND; + } + + *out_save_data = save_data; + return ResultSuccess; } -ResultVal<FileSys::VirtualDir> FileSystemController::OpenSaveDataSpace( - FileSys::SaveDataSpaceId space) const { +Result FileSystemController::OpenSaveDataSpace(FileSys::VirtualDir* out_save_data_space, + FileSys::SaveDataSpaceId space) const { LOG_TRACE(Service_FS, "Opening Save Data Space for space_id={:01X}", space); if (save_data_factory == nullptr) { return FileSys::ERROR_ENTITY_NOT_FOUND; } - return save_data_factory->GetSaveDataSpaceDirectory(space); + auto save_data_space = save_data_factory->GetSaveDataSpaceDirectory(space); + if (save_data_space == nullptr) { + return FileSys::ERROR_ENTITY_NOT_FOUND; + } + + *out_save_data_space = save_data_space; + return ResultSuccess; } -ResultVal<FileSys::VirtualDir> FileSystemController::OpenSDMC() const { +Result FileSystemController::OpenSDMC(FileSys::VirtualDir* out_sdmc) const { LOG_TRACE(Service_FS, "Opening SDMC"); if (sdmc_factory == nullptr) { return FileSys::ERROR_SD_CARD_NOT_FOUND; } - return sdmc_factory->Open(); + auto sdmc = sdmc_factory->Open(); + if (sdmc == nullptr) { + return FileSys::ERROR_SD_CARD_NOT_FOUND; + } + + *out_sdmc = sdmc; + return ResultSuccess; } -ResultVal<FileSys::VirtualDir> FileSystemController::OpenBISPartition( - FileSys::BisPartitionId id) const { +Result FileSystemController::OpenBISPartition(FileSys::VirtualDir* out_bis_partition, + FileSys::BisPartitionId id) const { LOG_TRACE(Service_FS, "Opening BIS Partition with id={:08X}", id); if (bis_factory == nullptr) { @@ -417,11 +462,12 @@ ResultVal<FileSys::VirtualDir> FileSystemController::OpenBISPartition( return FileSys::ERROR_INVALID_ARGUMENT; } - return part; + *out_bis_partition = part; + return ResultSuccess; } -ResultVal<FileSys::VirtualFile> FileSystemController::OpenBISPartitionStorage( - FileSys::BisPartitionId id) const { +Result FileSystemController::OpenBISPartitionStorage( + FileSys::VirtualFile* out_bis_partition_storage, FileSys::BisPartitionId id) const { LOG_TRACE(Service_FS, "Opening BIS Partition Storage with id={:08X}", id); if (bis_factory == nullptr) { @@ -433,7 +479,8 @@ ResultVal<FileSys::VirtualFile> FileSystemController::OpenBISPartitionStorage( return FileSys::ERROR_INVALID_ARGUMENT; } - return part; + *out_bis_partition_storage = part; + return ResultSuccess; } u64 FileSystemController::GetFreeSpaceSize(FileSys::StorageId id) const { diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h index a5c1c9d3e..e7e7c4c28 100644 --- a/src/core/hle/service/filesystem/filesystem.h +++ b/src/core/hle/service/filesystem/filesystem.h @@ -15,6 +15,7 @@ class System; namespace FileSys { class BISFactory; +class NCA; class RegisteredCache; class RegisteredCacheUnion; class PlaceholderCache; @@ -64,21 +65,26 @@ public: Result RegisterBIS(std::unique_ptr<FileSys::BISFactory>&& factory); void SetPackedUpdate(FileSys::VirtualFile update_raw); - ResultVal<FileSys::VirtualFile> OpenRomFSCurrentProcess() const; - ResultVal<FileSys::VirtualFile> OpenPatchedRomFS(u64 title_id, - FileSys::ContentRecordType type) const; - ResultVal<FileSys::VirtualFile> OpenPatchedRomFSWithProgramIndex( - u64 title_id, u8 program_index, FileSys::ContentRecordType type) const; - ResultVal<FileSys::VirtualFile> OpenRomFS(u64 title_id, FileSys::StorageId storage_id, + FileSys::VirtualFile OpenRomFSCurrentProcess() const; + FileSys::VirtualFile OpenPatchedRomFS(u64 title_id, FileSys::ContentRecordType type) const; + FileSys::VirtualFile OpenPatchedRomFSWithProgramIndex(u64 title_id, u8 program_index, + FileSys::ContentRecordType type) const; + FileSys::VirtualFile OpenRomFS(u64 title_id, FileSys::StorageId storage_id, + FileSys::ContentRecordType type) const; + std::shared_ptr<FileSys::NCA> OpenBaseNca(u64 title_id, FileSys::StorageId storage_id, FileSys::ContentRecordType type) const; - ResultVal<FileSys::VirtualDir> CreateSaveData( - FileSys::SaveDataSpaceId space, const FileSys::SaveDataAttribute& save_struct) const; - ResultVal<FileSys::VirtualDir> OpenSaveData( - FileSys::SaveDataSpaceId space, const FileSys::SaveDataAttribute& save_struct) const; - ResultVal<FileSys::VirtualDir> OpenSaveDataSpace(FileSys::SaveDataSpaceId space) const; - ResultVal<FileSys::VirtualDir> OpenSDMC() const; - ResultVal<FileSys::VirtualDir> OpenBISPartition(FileSys::BisPartitionId id) const; - ResultVal<FileSys::VirtualFile> OpenBISPartitionStorage(FileSys::BisPartitionId id) const; + + Result CreateSaveData(FileSys::VirtualDir* out_save_data, FileSys::SaveDataSpaceId space, + const FileSys::SaveDataAttribute& save_struct) const; + Result OpenSaveData(FileSys::VirtualDir* out_save_data, FileSys::SaveDataSpaceId space, + const FileSys::SaveDataAttribute& save_struct) const; + Result OpenSaveDataSpace(FileSys::VirtualDir* out_save_data_space, + FileSys::SaveDataSpaceId space) const; + Result OpenSDMC(FileSys::VirtualDir* out_sdmc) const; + Result OpenBISPartition(FileSys::VirtualDir* out_bis_partition, + FileSys::BisPartitionId id) const; + Result OpenBISPartitionStorage(FileSys::VirtualFile* out_bis_partition_storage, + FileSys::BisPartitionId id) const; u64 GetFreeSpaceSize(FileSys::StorageId id) const; u64 GetTotalSpaceSize(FileSys::StorageId id) const; @@ -224,26 +230,28 @@ public: * @param mode Mode to open the file with * @return Opened file, or error code */ - ResultVal<FileSys::VirtualFile> OpenFile(const std::string& path, FileSys::Mode mode) const; + Result OpenFile(FileSys::VirtualFile* out_file, const std::string& path, + FileSys::Mode mode) const; /** * Open a directory specified by its path * @param path Path relative to the archive * @return Opened directory, or error code */ - ResultVal<FileSys::VirtualDir> OpenDirectory(const std::string& path); + Result OpenDirectory(FileSys::VirtualDir* out_directory, const std::string& path); /** * Get the type of the specified path * @return The type of the specified path or error code */ - ResultVal<FileSys::EntryType> GetEntryType(const std::string& path) const; + Result GetEntryType(FileSys::EntryType* out_entry_type, const std::string& path) const; /** * Get the timestamp of the specified path * @return The timestamp of the specified path or error code */ - ResultVal<FileSys::FileTimeStampRaw> GetFileTimeStampRaw(const std::string& path) const; + Result GetFileTimeStampRaw(FileSys::FileTimeStampRaw* out_time_stamp_raw, + const std::string& path) const; private: FileSys::VirtualDir backing; diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp index 427dbc8b3..6e4d26b1e 100644 --- a/src/core/hle/service/filesystem/fsp_srv.cpp +++ b/src/core/hle/service/filesystem/fsp_srv.cpp @@ -419,14 +419,15 @@ public: LOG_DEBUG(Service_FS, "called. file={}, mode={}", name, mode); - auto result = backend.OpenFile(name, mode); - if (result.Failed()) { + FileSys::VirtualFile vfs_file{}; + auto result = backend.OpenFile(&vfs_file, name, mode); + if (result != ResultSuccess) { IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(result.Code()); + rb.Push(result); return; } - auto file = std::make_shared<IFile>(system, result.Unwrap()); + auto file = std::make_shared<IFile>(system, vfs_file); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); @@ -444,14 +445,15 @@ public: LOG_DEBUG(Service_FS, "called. directory={}, filter={}", name, filter_flags); - auto result = backend.OpenDirectory(name); - if (result.Failed()) { + FileSys::VirtualDir vfs_dir{}; + auto result = backend.OpenDirectory(&vfs_dir, name); + if (result != ResultSuccess) { IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(result.Code()); + rb.Push(result); return; } - auto directory = std::make_shared<IDirectory>(system, result.Unwrap()); + auto directory = std::make_shared<IDirectory>(system, vfs_dir); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); @@ -464,16 +466,17 @@ public: LOG_DEBUG(Service_FS, "called. file={}", name); - auto result = backend.GetEntryType(name); - if (result.Failed()) { + FileSys::EntryType vfs_entry_type{}; + auto result = backend.GetEntryType(&vfs_entry_type, name); + if (result != ResultSuccess) { IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(result.Code()); + rb.Push(result); return; } IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); - rb.Push<u32>(static_cast<u32>(*result)); + rb.Push<u32>(static_cast<u32>(vfs_entry_type)); } void Commit(HLERequestContext& ctx) { @@ -505,16 +508,17 @@ public: LOG_WARNING(Service_FS, "(Partial Implementation) called. file={}", name); - auto result = backend.GetFileTimeStampRaw(name); - if (result.Failed()) { + FileSys::FileTimeStampRaw vfs_timestamp{}; + auto result = backend.GetFileTimeStampRaw(&vfs_timestamp, name); + if (result != ResultSuccess) { IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(result.Code()); + rb.Push(result); return; } IPC::ResponseBuilder rb{ctx, 10}; rb.Push(ResultSuccess); - rb.PushRaw(*result); + rb.PushRaw(vfs_timestamp); } private: @@ -572,14 +576,15 @@ private: } void FindAllSaves(FileSys::SaveDataSpaceId space) { - const auto save_root = fsc.OpenSaveDataSpace(space); + FileSys::VirtualDir save_root{}; + const auto result = fsc.OpenSaveDataSpace(&save_root, space); - if (save_root.Failed() || *save_root == nullptr) { + if (result != ResultSuccess || save_root == nullptr) { LOG_ERROR(Service_FS, "The save root for the space_id={:02X} was invalid!", space); return; } - for (const auto& type : (*save_root)->GetSubdirectories()) { + for (const auto& type : save_root->GetSubdirectories()) { if (type->GetName() == "save") { for (const auto& save_id : type->GetSubdirectories()) { for (const auto& user_id : save_id->GetSubdirectories()) { @@ -837,9 +842,11 @@ void FSP_SRV::OpenFileSystemWithPatch(HLERequestContext& ctx) { void FSP_SRV::OpenSdCardFileSystem(HLERequestContext& ctx) { LOG_DEBUG(Service_FS, "called"); - auto filesystem = - std::make_shared<IFileSystem>(system, fsc.OpenSDMC().Unwrap(), - SizeGetter::FromStorageId(fsc, FileSys::StorageId::SdCard)); + FileSys::VirtualDir sdmc_dir{}; + fsc.OpenSDMC(&sdmc_dir); + + auto filesystem = std::make_shared<IFileSystem>( + system, sdmc_dir, SizeGetter::FromStorageId(fsc, FileSys::StorageId::SdCard)); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); @@ -856,7 +863,8 @@ void FSP_SRV::CreateSaveDataFileSystem(HLERequestContext& ctx) { LOG_DEBUG(Service_FS, "called save_struct = {}, uid = {:016X}{:016X}", save_struct.DebugInfo(), uid[1], uid[0]); - fsc.CreateSaveData(FileSys::SaveDataSpaceId::NandUser, save_struct); + FileSys::VirtualDir save_data_dir{}; + fsc.CreateSaveData(&save_data_dir, FileSys::SaveDataSpaceId::NandUser, save_struct); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); @@ -874,8 +882,9 @@ void FSP_SRV::OpenSaveDataFileSystem(HLERequestContext& ctx) { LOG_INFO(Service_FS, "called."); - auto dir = fsc.OpenSaveData(parameters.space_id, parameters.attribute); - if (dir.Failed()) { + FileSys::VirtualDir dir{}; + auto result = fsc.OpenSaveData(&dir, parameters.space_id, parameters.attribute); + if (result != ResultSuccess) { IPC::ResponseBuilder rb{ctx, 2, 0, 0}; rb.Push(FileSys::ERROR_ENTITY_NOT_FOUND); return; @@ -899,8 +908,8 @@ void FSP_SRV::OpenSaveDataFileSystem(HLERequestContext& ctx) { ASSERT(false); } - auto filesystem = std::make_shared<IFileSystem>(system, std::move(dir.Unwrap()), - SizeGetter::FromStorageId(fsc, id)); + auto filesystem = + std::make_shared<IFileSystem>(system, std::move(dir), SizeGetter::FromStorageId(fsc, id)); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); @@ -970,7 +979,7 @@ void FSP_SRV::OpenDataStorageByCurrentProcess(HLERequestContext& ctx) { if (!romfs) { auto current_romfs = fsc.OpenRomFSCurrentProcess(); - if (current_romfs.Failed()) { + if (!current_romfs) { // TODO (bunnei): Find the right error code to use here LOG_CRITICAL(Service_FS, "no file system interface available!"); IPC::ResponseBuilder rb{ctx, 2}; @@ -978,7 +987,7 @@ void FSP_SRV::OpenDataStorageByCurrentProcess(HLERequestContext& ctx) { return; } - romfs = current_romfs.Unwrap(); + romfs = current_romfs; } auto storage = std::make_shared<IStorage>(system, romfs); @@ -999,7 +1008,7 @@ void FSP_SRV::OpenDataStorageByDataId(HLERequestContext& ctx) { auto data = fsc.OpenRomFS(title_id, storage_id, FileSys::ContentRecordType::Data); - if (data.Failed()) { + if (!data) { const auto archive = FileSys::SystemArchive::SynthesizeSystemArchive(title_id); if (archive != nullptr) { @@ -1020,8 +1029,9 @@ void FSP_SRV::OpenDataStorageByDataId(HLERequestContext& ctx) { const FileSys::PatchManager pm{title_id, fsc, content_provider}; + auto base = fsc.OpenBaseNca(title_id, storage_id, FileSys::ContentRecordType::Data); auto storage = std::make_shared<IStorage>( - system, pm.PatchRomFS(std::move(data.Unwrap()), 0, FileSys::ContentRecordType::Data)); + system, pm.PatchRomFS(base.get(), std::move(data), FileSys::ContentRecordType::Data)); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); @@ -1051,7 +1061,7 @@ void FSP_SRV::OpenDataStorageWithProgramIndex(HLERequestContext& ctx) { fsc.OpenPatchedRomFSWithProgramIndex(system.GetApplicationProcessProgramID(), program_index, FileSys::ContentRecordType::Program); - if (patched_romfs.Failed()) { + if (!patched_romfs) { // TODO: Find the right error code to use here LOG_ERROR(Service_FS, "could not open storage with program_index={}", program_index); @@ -1060,7 +1070,7 @@ void FSP_SRV::OpenDataStorageWithProgramIndex(HLERequestContext& ctx) { return; } - auto storage = std::make_shared<IStorage>(system, std::move(patched_romfs.Unwrap())); + auto storage = std::make_shared<IStorage>(system, std::move(patched_romfs)); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); diff --git a/src/core/hle/service/glue/arp.cpp b/src/core/hle/service/glue/arp.cpp index ed6fcb5f6..6f1151b03 100644 --- a/src/core/hle/service/glue/arp.cpp +++ b/src/core/hle/service/glue/arp.cpp @@ -65,18 +65,19 @@ void ARP_R::GetApplicationLaunchProperty(HLERequestContext& ctx) { return; } - const auto res = manager.GetLaunchProperty(*title_id); + ApplicationLaunchProperty launch_property{}; + const auto res = manager.GetLaunchProperty(&launch_property, *title_id); - if (res.Failed()) { + if (res != ResultSuccess) { LOG_ERROR(Service_ARP, "Failed to get launch property!"); IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(res.Code()); + rb.Push(res); return; } IPC::ResponseBuilder rb{ctx, 6}; rb.Push(ResultSuccess); - rb.PushRaw(*res); + rb.PushRaw(launch_property); } void ARP_R::GetApplicationLaunchPropertyWithApplicationId(HLERequestContext& ctx) { @@ -85,18 +86,19 @@ void ARP_R::GetApplicationLaunchPropertyWithApplicationId(HLERequestContext& ctx LOG_DEBUG(Service_ARP, "called, title_id={:016X}", title_id); - const auto res = manager.GetLaunchProperty(title_id); + ApplicationLaunchProperty launch_property{}; + const auto res = manager.GetLaunchProperty(&launch_property, title_id); - if (res.Failed()) { + if (res != ResultSuccess) { LOG_ERROR(Service_ARP, "Failed to get launch property!"); IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(res.Code()); + rb.Push(res); return; } IPC::ResponseBuilder rb{ctx, 6}; rb.Push(ResultSuccess); - rb.PushRaw(*res); + rb.PushRaw(launch_property); } void ARP_R::GetApplicationControlProperty(HLERequestContext& ctx) { @@ -113,16 +115,17 @@ void ARP_R::GetApplicationControlProperty(HLERequestContext& ctx) { return; } - const auto res = manager.GetControlProperty(*title_id); + std::vector<u8> nacp_data; + const auto res = manager.GetControlProperty(&nacp_data, *title_id); - if (res.Failed()) { + if (res != ResultSuccess) { LOG_ERROR(Service_ARP, "Failed to get control property!"); IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(res.Code()); + rb.Push(res); return; } - ctx.WriteBuffer(*res); + ctx.WriteBuffer(nacp_data); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); @@ -134,16 +137,17 @@ void ARP_R::GetApplicationControlPropertyWithApplicationId(HLERequestContext& ct LOG_DEBUG(Service_ARP, "called, title_id={:016X}", title_id); - const auto res = manager.GetControlProperty(title_id); + std::vector<u8> nacp_data; + const auto res = manager.GetControlProperty(&nacp_data, title_id); - if (res.Failed()) { + if (res != ResultSuccess) { LOG_ERROR(Service_ARP, "Failed to get control property!"); IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(res.Code()); + rb.Push(res); return; } - ctx.WriteBuffer(*res); + ctx.WriteBuffer(nacp_data); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); diff --git a/src/core/hle/service/glue/glue_manager.cpp b/src/core/hle/service/glue/glue_manager.cpp index 4bf67921b..22f001704 100644 --- a/src/core/hle/service/glue/glue_manager.cpp +++ b/src/core/hle/service/glue/glue_manager.cpp @@ -15,7 +15,8 @@ ARPManager::ARPManager() = default; ARPManager::~ARPManager() = default; -ResultVal<ApplicationLaunchProperty> ARPManager::GetLaunchProperty(u64 title_id) const { +Result ARPManager::GetLaunchProperty(ApplicationLaunchProperty* out_launch_property, + u64 title_id) const { if (title_id == 0) { return Glue::ResultInvalidProcessId; } @@ -25,10 +26,11 @@ ResultVal<ApplicationLaunchProperty> ARPManager::GetLaunchProperty(u64 title_id) return Glue::ResultProcessIdNotRegistered; } - return iter->second.launch; + *out_launch_property = iter->second.launch; + return ResultSuccess; } -ResultVal<std::vector<u8>> ARPManager::GetControlProperty(u64 title_id) const { +Result ARPManager::GetControlProperty(std::vector<u8>* out_control_property, u64 title_id) const { if (title_id == 0) { return Glue::ResultInvalidProcessId; } @@ -38,7 +40,8 @@ ResultVal<std::vector<u8>> ARPManager::GetControlProperty(u64 title_id) const { return Glue::ResultProcessIdNotRegistered; } - return iter->second.control; + *out_control_property = iter->second.control; + return ResultSuccess; } Result ARPManager::Register(u64 title_id, ApplicationLaunchProperty launch, diff --git a/src/core/hle/service/glue/glue_manager.h b/src/core/hle/service/glue/glue_manager.h index 1cf53d9d9..216aa34c1 100644 --- a/src/core/hle/service/glue/glue_manager.h +++ b/src/core/hle/service/glue/glue_manager.h @@ -32,12 +32,12 @@ public: // Returns the ApplicationLaunchProperty corresponding to the provided title ID if it was // previously registered, otherwise ResultProcessIdNotRegistered if it was never registered or // ResultInvalidProcessId if the title ID is 0. - ResultVal<ApplicationLaunchProperty> GetLaunchProperty(u64 title_id) const; + Result GetLaunchProperty(ApplicationLaunchProperty* out_launch_property, u64 title_id) const; // Returns a vector of the raw bytes of NACP data (necessarily 0x4000 in size) corresponding to // the provided title ID if it was previously registered, otherwise ResultProcessIdNotRegistered // if it was never registered or ResultInvalidProcessId if the title ID is 0. - ResultVal<std::vector<u8>> GetControlProperty(u64 title_id) const; + Result GetControlProperty(std::vector<u8>* out_control_property, u64 title_id) const; // Adds a new entry to the internal database with the provided parameters, returning // ResultProcessIdNotRegistered if attempting to re-register a title ID without an intermediate diff --git a/src/core/hle/service/hid/controllers/touchscreen.h b/src/core/hle/service/hid/controllers/touchscreen.h index e57a3a80e..dd00921fd 100644 --- a/src/core/hle/service/hid/controllers/touchscreen.h +++ b/src/core/hle/service/hid/controllers/touchscreen.h @@ -16,22 +16,6 @@ class EmulatedConsole; namespace Service::HID { class Controller_Touchscreen final : public ControllerBase { public: - // This is nn::hid::TouchScreenModeForNx - enum class TouchScreenModeForNx : u8 { - UseSystemSetting, - Finger, - Heat2, - }; - - // This is nn::hid::TouchScreenConfigurationForNx - struct TouchScreenConfigurationForNx { - TouchScreenModeForNx mode{TouchScreenModeForNx::UseSystemSetting}; - INSERT_PADDING_BYTES_NOINIT(0x7); - INSERT_PADDING_BYTES_NOINIT(0xF); // Reserved - }; - static_assert(sizeof(TouchScreenConfigurationForNx) == 0x17, - "TouchScreenConfigurationForNx is an invalid size"); - explicit Controller_Touchscreen(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_); ~Controller_Touchscreen() override; diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index 2bf1d8a27..fd466db7b 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -2368,7 +2368,7 @@ void Hid::GetNpadCommunicationMode(HLERequestContext& ctx) { void Hid::SetTouchScreenConfiguration(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto touchscreen_mode{rp.PopRaw<Controller_Touchscreen::TouchScreenConfigurationForNx>()}; + const auto touchscreen_mode{rp.PopRaw<Core::HID::TouchScreenConfigurationForNx>()}; const auto applet_resource_user_id{rp.Pop<u64>()}; LOG_WARNING(Service_HID, "(STUBBED) called, touchscreen_mode={}, applet_resource_user_id={}", @@ -2543,7 +2543,8 @@ public: class HidSys final : public ServiceFramework<HidSys> { public: - explicit HidSys(Core::System& system_) : ServiceFramework{system_, "hid:sys"} { + explicit HidSys(Core::System& system_) + : ServiceFramework{system_, "hid:sys"}, service_context{system_, "hid:sys"} { // clang-format off static const FunctionInfo functions[] = { {31, nullptr, "SendKeyboardLockKeyEvent"}, @@ -2568,7 +2569,7 @@ public: {303, &HidSys::ApplyNpadSystemCommonPolicy, "ApplyNpadSystemCommonPolicy"}, {304, nullptr, "EnableAssigningSingleOnSlSrPress"}, {305, nullptr, "DisableAssigningSingleOnSlSrPress"}, - {306, nullptr, "GetLastActiveNpad"}, + {306, &HidSys::GetLastActiveNpad, "GetLastActiveNpad"}, {307, nullptr, "GetNpadSystemExtStyle"}, {308, nullptr, "ApplyNpadSystemCommonPolicyFull"}, {309, nullptr, "GetNpadFullKeyGripColor"}, @@ -2624,7 +2625,7 @@ public: {700, nullptr, "ActivateUniquePad"}, {702, nullptr, "AcquireUniquePadConnectionEventHandle"}, {703, nullptr, "GetUniquePadIds"}, - {751, nullptr, "AcquireJoyDetachOnBluetoothOffEventHandle"}, + {751, &HidSys::AcquireJoyDetachOnBluetoothOffEventHandle, "AcquireJoyDetachOnBluetoothOffEventHandle"}, {800, nullptr, "ListSixAxisSensorHandles"}, {801, nullptr, "IsSixAxisSensorUserCalibrationSupported"}, {802, nullptr, "ResetSixAxisSensorCalibrationValues"}, @@ -2650,7 +2651,7 @@ public: {830, nullptr, "SetNotificationLedPattern"}, {831, nullptr, "SetNotificationLedPatternWithTimeout"}, {832, nullptr, "PrepareHidsForNotificationWake"}, - {850, nullptr, "IsUsbFullKeyControllerEnabled"}, + {850, &HidSys::IsUsbFullKeyControllerEnabled, "IsUsbFullKeyControllerEnabled"}, {851, nullptr, "EnableUsbFullKeyController"}, {852, nullptr, "IsUsbConnected"}, {870, nullptr, "IsHandheldButtonPressedOnConsoleMode"}, @@ -2682,7 +2683,7 @@ public: {1150, nullptr, "SetTouchScreenMagnification"}, {1151, nullptr, "GetTouchScreenFirmwareVersion"}, {1152, nullptr, "SetTouchScreenDefaultConfiguration"}, - {1153, nullptr, "GetTouchScreenDefaultConfiguration"}, + {1153, &HidSys::GetTouchScreenDefaultConfiguration, "GetTouchScreenDefaultConfiguration"}, {1154, nullptr, "IsFirmwareAvailableForNotification"}, {1155, nullptr, "SetForceHandheldStyleVibration"}, {1156, nullptr, "SendConnectionTriggerWithoutTimeoutEvent"}, @@ -2749,6 +2750,8 @@ public: // clang-format on RegisterHandlers(functions); + + joy_detach_event = service_context.CreateEvent("HidSys::JoyDetachEvent"); } private: @@ -2760,17 +2763,66 @@ private: rb.Push(ResultSuccess); } + void GetLastActiveNpad(HLERequestContext& ctx) { + LOG_DEBUG(Service_HID, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.PushEnum(Core::HID::NpadIdType::Handheld); + } + void GetUniquePadsFromNpad(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto npad_id_type{rp.PopEnum<Core::HID::NpadIdType>()}; - const s64 total_entries = 0; LOG_WARNING(Service_HID, "(STUBBED) called, npad_id_type={}", npad_id_type); + const std::vector<Core::HID::UniquePadId> unique_pads{}; + + ctx.WriteBuffer(unique_pads); + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); - rb.Push(total_entries); + rb.Push(static_cast<u32>(unique_pads.size())); } + + void AcquireJoyDetachOnBluetoothOffEventHandle(HLERequestContext& ctx) { + LOG_INFO(Service_AM, "called"); + + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(ResultSuccess); + rb.PushCopyObjects(joy_detach_event->GetReadableEvent()); + } + + void IsUsbFullKeyControllerEnabled(HLERequestContext& ctx) { + const bool is_enabled = false; + + LOG_WARNING(Service_HID, "(STUBBED) called, is_enabled={}", is_enabled); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(is_enabled); + } + + void GetTouchScreenDefaultConfiguration(HLERequestContext& ctx) { + LOG_WARNING(Service_HID, "(STUBBED) called"); + + Core::HID::TouchScreenConfigurationForNx touchscreen_config{ + .mode = Core::HID::TouchScreenModeForNx::Finger, + }; + + if (touchscreen_config.mode != Core::HID::TouchScreenModeForNx::Heat2 && + touchscreen_config.mode != Core::HID::TouchScreenModeForNx::Finger) { + touchscreen_config.mode = Core::HID::TouchScreenModeForNx::UseSystemSetting; + } + + IPC::ResponseBuilder rb{ctx, 6}; + rb.Push(ResultSuccess); + rb.PushRaw(touchscreen_config); + } + + Kernel::KEvent* joy_detach_event; + KernelHelpers::ServiceContext service_context; }; void LoopProcess(Core::System& system) { diff --git a/src/core/hle/service/ldr/ldr.cpp b/src/core/hle/service/ldr/ldr.cpp index 055c0a2db..c73035c77 100644 --- a/src/core/hle/service/ldr/ldr.cpp +++ b/src/core/hle/service/ldr/ldr.cpp @@ -357,7 +357,8 @@ public: return ResultSuccess; } - ResultVal<VAddr> MapProcessCodeMemory(Kernel::KProcess* process, VAddr base_addr, u64 size) { + Result MapProcessCodeMemory(VAddr* out_map_location, Kernel::KProcess* process, VAddr base_addr, + u64 size) { auto& page_table{process->GetPageTable()}; VAddr addr{}; @@ -372,20 +373,21 @@ public: R_TRY(result); if (ValidateRegionForMap(page_table, addr, size)) { - return addr; + *out_map_location = addr; + return ResultSuccess; } } return ERROR_INSUFFICIENT_ADDRESS_SPACE; } - ResultVal<VAddr> MapNro(Kernel::KProcess* process, VAddr nro_addr, std::size_t nro_size, - VAddr bss_addr, std::size_t bss_size, std::size_t size) { + Result MapNro(VAddr* out_map_location, Kernel::KProcess* process, VAddr nro_addr, + std::size_t nro_size, VAddr bss_addr, std::size_t bss_size, std::size_t size) { for (std::size_t retry = 0; retry < MAXIMUM_MAP_RETRIES; retry++) { auto& page_table{process->GetPageTable()}; VAddr addr{}; - CASCADE_RESULT(addr, MapProcessCodeMemory(process, nro_addr, nro_size)); + R_TRY(MapProcessCodeMemory(&addr, process, nro_addr, nro_size)); if (bss_size) { auto block_guard = detail::ScopeExit([&] { @@ -411,7 +413,8 @@ public: } if (ValidateRegionForMap(page_table, addr, size)) { - return addr; + *out_map_location = addr; + return ResultSuccess; } } @@ -437,9 +440,9 @@ public: CopyCode(nro_addr + nro_header.segment_headers[DATA_INDEX].memory_offset, data_start, nro_header.segment_headers[DATA_INDEX].memory_size); - CASCADE_CODE(process->GetPageTable().SetProcessMemoryPermission( + R_TRY(process->GetPageTable().SetProcessMemoryPermission( text_start, ro_start - text_start, Kernel::Svc::MemoryPermission::ReadExecute)); - CASCADE_CODE(process->GetPageTable().SetProcessMemoryPermission( + R_TRY(process->GetPageTable().SetProcessMemoryPermission( ro_start, data_start - ro_start, Kernel::Svc::MemoryPermission::Read)); return process->GetPageTable().SetProcessMemoryPermission( @@ -542,31 +545,32 @@ public: } // Map memory for the NRO - const auto map_result{MapNro(system.ApplicationProcess(), nro_address, nro_size, - bss_address, bss_size, nro_size + bss_size)}; - if (map_result.Failed()) { + VAddr map_location{}; + const auto map_result{MapNro(&map_location, system.ApplicationProcess(), nro_address, + nro_size, bss_address, bss_size, nro_size + bss_size)}; + if (map_result != ResultSuccess) { IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(map_result.Code()); + rb.Push(map_result); } // Load the NRO into the mapped memory if (const auto result{ - LoadNro(system.ApplicationProcess(), header, nro_address, *map_result)}; + LoadNro(system.ApplicationProcess(), header, nro_address, map_location)}; result.IsError()) { IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(map_result.Code()); + rb.Push(result); } // Track the loaded NRO - nro.insert_or_assign(*map_result, - NROInfo{hash, *map_result, nro_size, bss_address, bss_size, + nro.insert_or_assign(map_location, + NROInfo{hash, map_location, nro_size, bss_address, bss_size, header.segment_headers[TEXT_INDEX].memory_size, header.segment_headers[RO_INDEX].memory_size, header.segment_headers[DATA_INDEX].memory_size, nro_address}); IPC::ResponseBuilder rb{ctx, 4}; rb.Push(ResultSuccess); - rb.Push(*map_result); + rb.Push(map_location); } Result UnmapNro(const NROInfo& info) { @@ -574,19 +578,19 @@ public: auto& page_table{system.ApplicationProcess()->GetPageTable()}; if (info.bss_size != 0) { - CASCADE_CODE(page_table.UnmapCodeMemory( + R_TRY(page_table.UnmapCodeMemory( info.nro_address + info.text_size + info.ro_size + info.data_size, info.bss_address, info.bss_size, Kernel::KPageTable::ICacheInvalidationStrategy::InvalidateRange)); } - CASCADE_CODE(page_table.UnmapCodeMemory( + R_TRY(page_table.UnmapCodeMemory( info.nro_address + info.text_size + info.ro_size, info.src_addr + info.text_size + info.ro_size, info.data_size, Kernel::KPageTable::ICacheInvalidationStrategy::InvalidateRange)); - CASCADE_CODE(page_table.UnmapCodeMemory( + R_TRY(page_table.UnmapCodeMemory( info.nro_address + info.text_size, info.src_addr + info.text_size, info.ro_size, Kernel::KPageTable::ICacheInvalidationStrategy::InvalidateRange)); - CASCADE_CODE(page_table.UnmapCodeMemory( + R_TRY(page_table.UnmapCodeMemory( info.nro_address, info.src_addr, info.text_size, Kernel::KPageTable::ICacheInvalidationStrategy::InvalidateRange)); return ResultSuccess; diff --git a/src/core/hle/service/mii/mii.cpp b/src/core/hle/service/mii/mii.cpp index 5c7adf97d..65c11a2f3 100644 --- a/src/core/hle/service/mii/mii.cpp +++ b/src/core/hle/service/mii/mii.cpp @@ -101,20 +101,14 @@ private: LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag); - const auto result{manager.GetDefault(source_flag)}; - if (result.Failed()) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(result.Code()); - return; - } - - if (result->size() > 0) { - ctx.WriteBuffer(SerializeArray(*result)); + const auto default_miis{manager.GetDefault(source_flag)}; + if (default_miis.size() > 0) { + ctx.WriteBuffer(SerializeArray(default_miis)); } IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); - rb.Push<u32>(static_cast<u32>(result->size())); + rb.Push<u32>(static_cast<u32>(default_miis.size())); } void Get1(HLERequestContext& ctx) { @@ -123,15 +117,10 @@ private: LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag); - const auto result{manager.GetDefault(source_flag)}; - if (result.Failed()) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(result.Code()); - return; - } + const auto default_miis{manager.GetDefault(source_flag)}; std::vector<CharInfo> values; - for (const auto& element : *result) { + for (const auto& element : default_miis) { values.emplace_back(element.info); } @@ -139,7 +128,7 @@ private: IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); - rb.Push<u32>(static_cast<u32>(result->size())); + rb.Push<u32>(static_cast<u32>(default_miis.size())); } void UpdateLatest(HLERequestContext& ctx) { @@ -149,16 +138,17 @@ private: LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag); - const auto result{manager.UpdateLatest(info, source_flag)}; - if (result.Failed()) { + CharInfo new_char_info{}; + const auto result{manager.UpdateLatest(&new_char_info, info, source_flag)}; + if (result != ResultSuccess) { IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(result.Code()); + rb.Push(result); return; } IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; rb.Push(ResultSuccess); - rb.PushRaw<CharInfo>(*result); + rb.PushRaw<CharInfo>(new_char_info); } void BuildRandom(HLERequestContext& ctx) { diff --git a/src/core/hle/service/mii/mii_manager.cpp b/src/core/hle/service/mii/mii_manager.cpp index c920650f5..46125d473 100644 --- a/src/core/hle/service/mii/mii_manager.cpp +++ b/src/core/hle/service/mii/mii_manager.cpp @@ -409,8 +409,7 @@ u32 MiiManager::GetCount(SourceFlag source_flag) const { return static_cast<u32>(count); } -ResultVal<CharInfo> MiiManager::UpdateLatest([[maybe_unused]] const CharInfo& info, - SourceFlag source_flag) { +Result MiiManager::UpdateLatest(CharInfo* out_info, const CharInfo& info, SourceFlag source_flag) { if ((source_flag & SourceFlag::Database) == SourceFlag::None) { return ERROR_CANNOT_FIND_ENTRY; } @@ -672,7 +671,7 @@ bool MiiManager::ValidateV3Info(const Ver3StoreData& mii_v3) const { return is_valid; } -ResultVal<std::vector<MiiInfoElement>> MiiManager::GetDefault(SourceFlag source_flag) { +std::vector<MiiInfoElement> MiiManager::GetDefault(SourceFlag source_flag) { std::vector<MiiInfoElement> result; if ((source_flag & SourceFlag::Default) == SourceFlag::None) { diff --git a/src/core/hle/service/mii/mii_manager.h b/src/core/hle/service/mii/mii_manager.h index 5525fcd1c..45c2be3c8 100644 --- a/src/core/hle/service/mii/mii_manager.h +++ b/src/core/hle/service/mii/mii_manager.h @@ -19,12 +19,12 @@ public: bool CheckAndResetUpdateCounter(SourceFlag source_flag, u64& current_update_counter); bool IsFullDatabase() const; u32 GetCount(SourceFlag source_flag) const; - ResultVal<CharInfo> UpdateLatest(const CharInfo& info, SourceFlag source_flag); + Result UpdateLatest(CharInfo* out_info, const CharInfo& info, SourceFlag source_flag); CharInfo BuildRandom(Age age, Gender gender, Race race); CharInfo BuildDefault(std::size_t index); CharInfo ConvertV3ToCharInfo(const Ver3StoreData& mii_v3) const; bool ValidateV3Info(const Ver3StoreData& mii_v3) const; - ResultVal<std::vector<MiiInfoElement>> GetDefault(SourceFlag source_flag); + std::vector<MiiInfoElement> GetDefault(SourceFlag source_flag); Result GetIndex(const CharInfo& info, u32& index); // This is nn::mii::detail::Ver::StoreDataRaw::BuildFromStoreData diff --git a/src/core/hle/service/ns/ns.cpp b/src/core/hle/service/ns/ns.cpp index 91c5a2182..6e0baf0be 100644 --- a/src/core/hle/service/ns/ns.cpp +++ b/src/core/hle/service/ns/ns.cpp @@ -392,19 +392,20 @@ void IApplicationManagerInterface::GetApplicationDesiredLanguage(HLERequestConte IPC::RequestParser rp{ctx}; const auto supported_languages = rp.Pop<u32>(); - const auto res = GetApplicationDesiredLanguage(supported_languages); - if (res.Succeeded()) { + u8 desired_language{}; + const auto res = GetApplicationDesiredLanguage(&desired_language, supported_languages); + if (res == ResultSuccess) { IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); - rb.Push<u32>(*res); + rb.Push<u32>(desired_language); } else { IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(res.Code()); + rb.Push(res); } } -ResultVal<u8> IApplicationManagerInterface::GetApplicationDesiredLanguage( - const u32 supported_languages) { +Result IApplicationManagerInterface::GetApplicationDesiredLanguage(u8* out_desired_language, + const u32 supported_languages) { LOG_DEBUG(Service_NS, "called with supported_languages={:08X}", supported_languages); // Get language code from settings @@ -430,7 +431,8 @@ ResultVal<u8> IApplicationManagerInterface::GetApplicationDesiredLanguage( for (const auto lang : *priority_list) { const auto supported_flag = GetSupportedLanguageFlag(lang); if (supported_languages == 0 || (supported_languages & supported_flag) == supported_flag) { - return static_cast<u8>(lang); + *out_desired_language = static_cast<u8>(lang); + return ResultSuccess; } } @@ -444,19 +446,20 @@ void IApplicationManagerInterface::ConvertApplicationLanguageToLanguageCode( IPC::RequestParser rp{ctx}; const auto application_language = rp.Pop<u8>(); - const auto res = ConvertApplicationLanguageToLanguageCode(application_language); - if (res.Succeeded()) { + u64 language_code{}; + const auto res = ConvertApplicationLanguageToLanguageCode(&language_code, application_language); + if (res == ResultSuccess) { IPC::ResponseBuilder rb{ctx, 4}; rb.Push(ResultSuccess); - rb.Push(*res); + rb.Push(language_code); } else { IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(res.Code()); + rb.Push(res); } } -ResultVal<u64> IApplicationManagerInterface::ConvertApplicationLanguageToLanguageCode( - u8 application_language) { +Result IApplicationManagerInterface::ConvertApplicationLanguageToLanguageCode( + u64* out_language_code, u8 application_language) { const auto language_code = ConvertToLanguageCode(static_cast<ApplicationLanguage>(application_language)); if (language_code == std::nullopt) { @@ -464,7 +467,8 @@ ResultVal<u64> IApplicationManagerInterface::ConvertApplicationLanguageToLanguag return Service::NS::ResultApplicationLanguageNotFound; } - return static_cast<u64>(*language_code); + *out_language_code = static_cast<u64>(*language_code); + return ResultSuccess; } IApplicationVersionInterface::IApplicationVersionInterface(Core::System& system_) @@ -618,12 +622,13 @@ void IReadOnlyApplicationControlDataInterface::GetApplicationControlData(HLERequ static_assert(sizeof(RequestParameters) == 0x10, "RequestParameters has incorrect size."); IPC::RequestParser rp{ctx}; + std::vector<u8> nacp_data{}; const auto parameters{rp.PopRaw<RequestParameters>()}; - const auto nacp_data{system.GetARPManager().GetControlProperty(parameters.application_id)}; - const auto result = nacp_data ? ResultSuccess : ResultUnknown; + const auto result = + system.GetARPManager().GetControlProperty(&nacp_data, parameters.application_id); - if (nacp_data) { - ctx.WriteBuffer(nacp_data->data(), nacp_data->size()); + if (result == ResultSuccess) { + ctx.WriteBuffer(nacp_data.data(), nacp_data.size()); } IPC::ResponseBuilder rb{ctx, 2}; diff --git a/src/core/hle/service/ns/ns.h b/src/core/hle/service/ns/ns.h index 203388e1f..175dad780 100644 --- a/src/core/hle/service/ns/ns.h +++ b/src/core/hle/service/ns/ns.h @@ -28,8 +28,9 @@ public: explicit IApplicationManagerInterface(Core::System& system_); ~IApplicationManagerInterface() override; - ResultVal<u8> GetApplicationDesiredLanguage(u32 supported_languages); - ResultVal<u64> ConvertApplicationLanguageToLanguageCode(u8 application_language); + Result GetApplicationDesiredLanguage(u8* out_desired_language, u32 supported_languages); + Result ConvertApplicationLanguageToLanguageCode(u64* out_language_code, + u8 application_language); private: void GetApplicationControlData(HLERequestContext& ctx); diff --git a/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp b/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp index b16f9933f..dc6917d5d 100644 --- a/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp +++ b/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp @@ -449,6 +449,7 @@ Status BufferQueueProducer::QueueBuffer(s32 slot, const QueueBufferInput& input, case NativeWindowScalingMode::ScaleToWindow: case NativeWindowScalingMode::ScaleCrop: case NativeWindowScalingMode::NoScaleCrop: + case NativeWindowScalingMode::PreserveAspectRatio: break; default: LOG_ERROR(Service_Nvnflinger, "unknown scaling mode {}", scaling_mode); diff --git a/src/core/hle/service/nvnflinger/nvnflinger.cpp b/src/core/hle/service/nvnflinger/nvnflinger.cpp index 5f55cd31e..21f31f7a0 100644 --- a/src/core/hle/service/nvnflinger/nvnflinger.cpp +++ b/src/core/hle/service/nvnflinger/nvnflinger.cpp @@ -183,7 +183,7 @@ std::optional<u32> Nvnflinger::FindBufferQueueId(u64 display_id, u64 layer_id) { return layer->GetBinderId(); } -ResultVal<Kernel::KReadableEvent*> Nvnflinger::FindVsyncEvent(u64 display_id) { +Result Nvnflinger::FindVsyncEvent(Kernel::KReadableEvent** out_vsync_event, u64 display_id) { const auto lock_guard = Lock(); auto* const display = FindDisplay(display_id); @@ -191,7 +191,7 @@ ResultVal<Kernel::KReadableEvent*> Nvnflinger::FindVsyncEvent(u64 display_id) { return VI::ResultNotFound; } - return display->GetVSyncEvent(); + return display->GetVSyncEvent(out_vsync_event); } VI::Display* Nvnflinger::FindDisplay(u64 display_id) { diff --git a/src/core/hle/service/nvnflinger/nvnflinger.h b/src/core/hle/service/nvnflinger/nvnflinger.h index ef236303a..f478c2bc6 100644 --- a/src/core/hle/service/nvnflinger/nvnflinger.h +++ b/src/core/hle/service/nvnflinger/nvnflinger.h @@ -82,7 +82,7 @@ public: /// /// If an invalid display ID is provided, then VI::ResultNotFound is returned. /// If the vsync event has already been retrieved, then VI::ResultPermissionDenied is returned. - [[nodiscard]] ResultVal<Kernel::KReadableEvent*> FindVsyncEvent(u64 display_id); + [[nodiscard]] Result FindVsyncEvent(Kernel::KReadableEvent** out_vsync_event, u64 display_id); /// Performs a composition request to the emulated nvidia GPU and triggers the vsync events when /// finished. diff --git a/src/core/hle/service/nvnflinger/window.h b/src/core/hle/service/nvnflinger/window.h index 61cca5b01..36d6cde3d 100644 --- a/src/core/hle/service/nvnflinger/window.h +++ b/src/core/hle/service/nvnflinger/window.h @@ -41,6 +41,7 @@ enum class NativeWindowScalingMode : s32 { ScaleToWindow = 1, ScaleCrop = 2, NoScaleCrop = 3, + PreserveAspectRatio = 4, }; /// Transform parameter for QueueBuffer diff --git a/src/core/hle/service/olsc/olsc.cpp b/src/core/hle/service/olsc/olsc.cpp index 14ba67b4c..889f27c31 100644 --- a/src/core/hle/service/olsc/olsc.cpp +++ b/src/core/hle/service/olsc/olsc.cpp @@ -8,15 +8,16 @@ namespace Service::OLSC { -class OLSC final : public ServiceFramework<OLSC> { +class IOlscServiceForApplication final : public ServiceFramework<IOlscServiceForApplication> { public: - explicit OLSC(Core::System& system_) : ServiceFramework{system_, "olsc:u"} { + explicit IOlscServiceForApplication(Core::System& system_) + : ServiceFramework{system_, "olsc:u"} { // clang-format off static const FunctionInfo functions[] = { - {0, &OLSC::Initialize, "Initialize"}, + {0, &IOlscServiceForApplication::Initialize, "Initialize"}, {10, nullptr, "VerifySaveDataBackupLicenseAsync"}, - {13, &OLSC::GetSaveDataBackupSetting, "GetSaveDataBackupSetting"}, - {14, &OLSC::SetSaveDataBackupSettingEnabled, "SetSaveDataBackupSettingEnabled"}, + {13, &IOlscServiceForApplication::GetSaveDataBackupSetting, "GetSaveDataBackupSetting"}, + {14, &IOlscServiceForApplication::SetSaveDataBackupSettingEnabled, "SetSaveDataBackupSettingEnabled"}, {15, nullptr, "SetCustomData"}, {16, nullptr, "DeleteSaveDataBackupSetting"}, {18, nullptr, "GetSaveDataBackupInfoCache"}, @@ -72,10 +73,155 @@ private: bool initialized{}; }; +class INativeHandleHolder final : public ServiceFramework<INativeHandleHolder> { +public: + explicit INativeHandleHolder(Core::System& system_) + : ServiceFramework{system_, "INativeHandleHolder"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, nullptr, "GetNativeHandle"}, + }; + // clang-format on + + RegisterHandlers(functions); + } +}; + +class ITransferTaskListController final : public ServiceFramework<ITransferTaskListController> { +public: + explicit ITransferTaskListController(Core::System& system_) + : ServiceFramework{system_, "ITransferTaskListController"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, nullptr, "Unknown0"}, + {1, nullptr, "Unknown1"}, + {2, nullptr, "Unknown2"}, + {3, nullptr, "Unknown3"}, + {4, nullptr, "Unknown4"}, + {5, &ITransferTaskListController::GetNativeHandleHolder , "GetNativeHandleHolder"}, + {6, nullptr, "Unknown6"}, + {7, nullptr, "Unknown7"}, + {8, nullptr, "GetRemoteStorageController"}, + {9, &ITransferTaskListController::GetNativeHandleHolder, "GetNativeHandleHolder2"}, + {10, nullptr, "Unknown10"}, + {11, nullptr, "Unknown11"}, + {12, nullptr, "Unknown12"}, + {13, nullptr, "Unknown13"}, + {14, nullptr, "Unknown14"}, + {15, nullptr, "Unknown15"}, + {16, nullptr, "Unknown16"}, + {17, nullptr, "Unknown17"}, + {18, nullptr, "Unknown18"}, + {19, nullptr, "Unknown19"}, + {20, nullptr, "Unknown20"}, + {21, nullptr, "Unknown21"}, + {22, nullptr, "Unknown22"}, + {23, nullptr, "Unknown23"}, + {24, nullptr, "Unknown24"}, + {25, nullptr, "Unknown25"}, + }; + // clang-format on + + RegisterHandlers(functions); + } + +private: + void GetNativeHandleHolder(HLERequestContext& ctx) { + LOG_INFO(Service_OLSC, "called"); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(ResultSuccess); + rb.PushIpcInterface<INativeHandleHolder>(system); + } +}; + +class IOlscServiceForSystemService final : public ServiceFramework<IOlscServiceForSystemService> { +public: + explicit IOlscServiceForSystemService(Core::System& system_) + : ServiceFramework{system_, "olsc:s"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, &IOlscServiceForSystemService::OpenTransferTaskListController, "OpenTransferTaskListController"}, + {1, nullptr, "OpenRemoteStorageController"}, + {2, nullptr, "OpenDaemonController"}, + {10, nullptr, "Unknown10"}, + {11, nullptr, "Unknown11"}, + {12, nullptr, "Unknown12"}, + {13, nullptr, "Unknown13"}, + {100, nullptr, "ListLastTransferTaskErrorInfo"}, + {101, nullptr, "GetLastErrorInfoCount"}, + {102, nullptr, "RemoveLastErrorInfoOld"}, + {103, nullptr, "GetLastErrorInfo"}, + {104, nullptr, "GetLastErrorEventHolder"}, + {105, nullptr, "GetLastTransferTaskErrorInfo"}, + {200, nullptr, "GetDataTransferPolicyInfo"}, + {201, nullptr, "RemoveDataTransferPolicyInfo"}, + {202, nullptr, "UpdateDataTransferPolicyOld"}, + {203, nullptr, "UpdateDataTransferPolicy"}, + {204, nullptr, "CleanupDataTransferPolicyInfo"}, + {205, nullptr, "RequestDataTransferPolicy"}, + {300, nullptr, "GetAutoTransferSeriesInfo"}, + {301, nullptr, "UpdateAutoTransferSeriesInfo"}, + {400, nullptr, "CleanupSaveDataArchiveInfoType1"}, + {900, nullptr, "CleanupTransferTask"}, + {902, nullptr, "CleanupSeriesInfoType0"}, + {903, nullptr, "CleanupSaveDataArchiveInfoType0"}, + {904, nullptr, "CleanupApplicationAutoTransferSetting"}, + {905, nullptr, "CleanupErrorHistory"}, + {906, nullptr, "SetLastError"}, + {907, nullptr, "AddSaveDataArchiveInfoType0"}, + {908, nullptr, "RemoveSeriesInfoType0"}, + {909, nullptr, "GetSeriesInfoType0"}, + {910, nullptr, "RemoveLastErrorInfo"}, + {911, nullptr, "CleanupSeriesInfoType1"}, + {912, nullptr, "RemoveSeriesInfoType1"}, + {913, nullptr, "GetSeriesInfoType1"}, + {1000, nullptr, "UpdateIssueOld"}, + {1010, nullptr, "Unknown1010"}, + {1011, nullptr, "ListIssueInfoOld"}, + {1012, nullptr, "GetIssueOld"}, + {1013, nullptr, "GetIssue2Old"}, + {1014, nullptr, "GetIssue3Old"}, + {1020, nullptr, "RepairIssueOld"}, + {1021, nullptr, "RepairIssueWithUserIdOld"}, + {1022, nullptr, "RepairIssue2Old"}, + {1023, nullptr, "RepairIssue3Old"}, + {1024, nullptr, "Unknown1024"}, + {1100, nullptr, "UpdateIssue"}, + {1110, nullptr, "Unknown1110"}, + {1111, nullptr, "ListIssueInfo"}, + {1112, nullptr, "GetIssue"}, + {1113, nullptr, "GetIssue2"}, + {1114, nullptr, "GetIssue3"}, + {1120, nullptr, "RepairIssue"}, + {1121, nullptr, "RepairIssueWithUserId"}, + {1122, nullptr, "RepairIssue2"}, + {1123, nullptr, "RepairIssue3"}, + {1124, nullptr, "Unknown1124"}, + }; + // clang-format on + + RegisterHandlers(functions); + } + +private: + void OpenTransferTaskListController(HLERequestContext& ctx) { + LOG_INFO(Service_OLSC, "called"); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(ResultSuccess); + rb.PushIpcInterface<ITransferTaskListController>(system); + } +}; + void LoopProcess(Core::System& system) { auto server_manager = std::make_unique<ServerManager>(system); - server_manager->RegisterNamedService("olsc:u", std::make_shared<OLSC>(system)); + server_manager->RegisterNamedService("olsc:u", + std::make_shared<IOlscServiceForApplication>(system)); + server_manager->RegisterNamedService("olsc:s", + std::make_shared<IOlscServiceForSystemService>(system)); + ServerManager::RunServer(std::move(server_manager)); } diff --git a/src/core/hle/service/pctl/pctl_module.cpp b/src/core/hle/service/pctl/pctl_module.cpp index f966c5c8b..5db1703d1 100644 --- a/src/core/hle/service/pctl/pctl_module.cpp +++ b/src/core/hle/service/pctl/pctl_module.cpp @@ -6,6 +6,7 @@ #include "core/file_sys/control_metadata.h" #include "core/file_sys/patch_manager.h" #include "core/hle/service/ipc_helpers.h" +#include "core/hle/service/kernel_helpers.h" #include "core/hle/service/pctl/pctl.h" #include "core/hle/service/pctl/pctl_module.h" #include "core/hle/service/server_manager.h" @@ -24,7 +25,8 @@ constexpr Result ResultNoRestrictionEnabled{ErrorModule::PCTL, 181}; class IParentalControlService final : public ServiceFramework<IParentalControlService> { public: explicit IParentalControlService(Core::System& system_, Capability capability_) - : ServiceFramework{system_, "IParentalControlService"}, capability{capability_} { + : ServiceFramework{system_, "IParentalControlService"}, capability{capability_}, + service_context{system_, "IParentalControlService"} { // clang-format off static const FunctionInfo functions[] = { {1, &IParentalControlService::Initialize, "Initialize"}, @@ -33,7 +35,7 @@ public: {1003, nullptr, "ConfirmResumeApplicationPermission"}, {1004, nullptr, "ConfirmSnsPostPermission"}, {1005, nullptr, "ConfirmSystemSettingsPermission"}, - {1006, nullptr, "IsRestrictionTemporaryUnlocked"}, + {1006, &IParentalControlService::IsRestrictionTemporaryUnlocked, "IsRestrictionTemporaryUnlocked"}, {1007, nullptr, "RevertRestrictionTemporaryUnlocked"}, {1008, nullptr, "EnterRestrictedSystemSettings"}, {1009, nullptr, "LeaveRestrictedSystemSettings"}, @@ -47,14 +49,14 @@ public: {1017, &IParentalControlService::EndFreeCommunication, "EndFreeCommunication"}, {1018, &IParentalControlService::IsFreeCommunicationAvailable, "IsFreeCommunicationAvailable"}, {1031, &IParentalControlService::IsRestrictionEnabled, "IsRestrictionEnabled"}, - {1032, nullptr, "GetSafetyLevel"}, + {1032, &IParentalControlService::GetSafetyLevel, "GetSafetyLevel"}, {1033, nullptr, "SetSafetyLevel"}, {1034, nullptr, "GetSafetyLevelSettings"}, - {1035, nullptr, "GetCurrentSettings"}, + {1035, &IParentalControlService::GetCurrentSettings, "GetCurrentSettings"}, {1036, nullptr, "SetCustomSafetyLevelSettings"}, {1037, nullptr, "GetDefaultRatingOrganization"}, {1038, nullptr, "SetDefaultRatingOrganization"}, - {1039, nullptr, "GetFreeCommunicationApplicationListCount"}, + {1039, &IParentalControlService::GetFreeCommunicationApplicationListCount, "GetFreeCommunicationApplicationListCount"}, {1042, nullptr, "AddToFreeCommunicationApplicationList"}, {1043, nullptr, "DeleteSettings"}, {1044, nullptr, "GetFreeCommunicationApplicationList"}, @@ -76,7 +78,7 @@ public: {1206, nullptr, "GetPinCodeLength"}, {1207, nullptr, "GetPinCodeChangedEvent"}, {1208, nullptr, "GetPinCode"}, - {1403, nullptr, "IsPairingActive"}, + {1403, &IParentalControlService::IsPairingActive, "IsPairingActive"}, {1406, nullptr, "GetSettingsLastUpdated"}, {1411, nullptr, "GetPairingAccountInfo"}, {1421, nullptr, "GetAccountNickname"}, @@ -84,18 +86,18 @@ public: {1425, nullptr, "RequestPostEvents"}, {1426, nullptr, "GetPostEventInterval"}, {1427, nullptr, "SetPostEventInterval"}, - {1432, nullptr, "GetSynchronizationEvent"}, + {1432, &IParentalControlService::GetSynchronizationEvent, "GetSynchronizationEvent"}, {1451, nullptr, "StartPlayTimer"}, {1452, nullptr, "StopPlayTimer"}, {1453, nullptr, "IsPlayTimerEnabled"}, {1454, nullptr, "GetPlayTimerRemainingTime"}, {1455, nullptr, "IsRestrictedByPlayTimer"}, - {1456, nullptr, "GetPlayTimerSettings"}, - {1457, nullptr, "GetPlayTimerEventToRequestSuspension"}, - {1458, nullptr, "IsPlayTimerAlarmDisabled"}, + {1456, &IParentalControlService::GetPlayTimerSettings, "GetPlayTimerSettings"}, + {1457, &IParentalControlService::GetPlayTimerEventToRequestSuspension, "GetPlayTimerEventToRequestSuspension"}, + {1458, &IParentalControlService::IsPlayTimerAlarmDisabled, "IsPlayTimerAlarmDisabled"}, {1471, nullptr, "NotifyWrongPinCodeInputManyTimes"}, {1472, nullptr, "CancelNetworkRequest"}, - {1473, nullptr, "GetUnlinkedEvent"}, + {1473, &IParentalControlService::GetUnlinkedEvent, "GetUnlinkedEvent"}, {1474, nullptr, "ClearUnlinkedEvent"}, {1601, nullptr, "DisableAllFeatures"}, {1602, nullptr, "PostEnableAllFeatures"}, @@ -131,6 +133,12 @@ public: }; // clang-format on RegisterHandlers(functions); + + synchronization_event = + service_context.CreateEvent("IParentalControlService::SynchronizationEvent"); + unlinked_event = service_context.CreateEvent("IParentalControlService::UnlinkedEvent"); + request_suspension_event = + service_context.CreateEvent("IParentalControlService::RequestSuspensionEvent"); } private: @@ -228,6 +236,17 @@ private: states.free_communication = true; } + void IsRestrictionTemporaryUnlocked(HLERequestContext& ctx) { + const bool is_temporary_unlocked = false; + + LOG_WARNING(Service_PCTL, "(STUBBED) called, is_temporary_unlocked={}", + is_temporary_unlocked); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push<u8>(is_temporary_unlocked); + } + void ConfirmStereoVisionPermission(HLERequestContext& ctx) { LOG_DEBUG(Service_PCTL, "called"); states.stereo_vision = true; @@ -268,6 +287,34 @@ private: rb.Push(pin_code[0] != '\0'); } + void GetSafetyLevel(HLERequestContext& ctx) { + const u32 safety_level = 0; + + LOG_WARNING(Service_PCTL, "(STUBBED) called, safety_level={}", safety_level); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(safety_level); + } + + void GetCurrentSettings(HLERequestContext& ctx) { + LOG_INFO(Service_PCTL, "called"); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.PushRaw(restriction_settings); + } + + void GetFreeCommunicationApplicationListCount(HLERequestContext& ctx) { + const u32 count = 4; + + LOG_WARNING(Service_PCTL, "(STUBBED) called, count={}", count); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(count); + } + void ConfirmStereoVisionRestrictionConfigurable(HLERequestContext& ctx) { LOG_DEBUG(Service_PCTL, "called"); @@ -300,6 +347,61 @@ private: } } + void IsPairingActive(HLERequestContext& ctx) { + const bool is_pairing_active = false; + + LOG_WARNING(Service_PCTL, "(STUBBED) called, is_pairing_active={}", is_pairing_active); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push<u8>(is_pairing_active); + } + + void GetSynchronizationEvent(HLERequestContext& ctx) { + LOG_INFO(Service_PCTL, "called"); + + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(ResultSuccess); + rb.PushCopyObjects(synchronization_event->GetReadableEvent()); + } + + void GetPlayTimerSettings(HLERequestContext& ctx) { + LOG_WARNING(Service_PCTL, "(STUBBED) called"); + + const PlayTimerSettings timer_settings{}; + + IPC::ResponseBuilder rb{ctx, 15}; + rb.Push(ResultSuccess); + rb.PushRaw(timer_settings); + } + + void GetPlayTimerEventToRequestSuspension(HLERequestContext& ctx) { + LOG_INFO(Service_PCTL, "called"); + + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(ResultSuccess); + rb.PushCopyObjects(request_suspension_event->GetReadableEvent()); + } + + void IsPlayTimerAlarmDisabled(HLERequestContext& ctx) { + const bool is_play_timer_alarm_disabled = false; + + LOG_INFO(Service_PCTL, "called, is_play_timer_alarm_disabled={}", + is_play_timer_alarm_disabled); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push<u8>(is_play_timer_alarm_disabled); + } + + void GetUnlinkedEvent(HLERequestContext& ctx) { + LOG_INFO(Service_PCTL, "called"); + + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(ResultSuccess); + rb.PushCopyObjects(unlinked_event->GetReadableEvent()); + } + void SetStereoVisionRestriction(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto can_use = rp.Pop<bool>(); @@ -364,10 +466,30 @@ private: bool disabled{}; }; + // This is nn::pctl::RestrictionSettings + struct RestrictionSettings { + u8 rating_age; + bool sns_post_restriction; + bool free_communication_restriction; + }; + static_assert(sizeof(RestrictionSettings) == 0x3, "RestrictionSettings has incorrect size."); + + // This is nn::pctl::PlayTimerSettings + struct PlayTimerSettings { + std::array<u32, 13> settings; + }; + static_assert(sizeof(PlayTimerSettings) == 0x34, "PlayTimerSettings has incorrect size."); + States states{}; ParentalControlSettings settings{}; + RestrictionSettings restriction_settings{}; std::array<char, 8> pin_code{}; Capability capability{}; + + Kernel::KEvent* synchronization_event; + Kernel::KEvent* unlinked_event; + Kernel::KEvent* request_suspension_event; + KernelHelpers::ServiceContext service_context; }; void Module::Interface::CreateService(HLERequestContext& ctx) { diff --git a/src/core/hle/service/server_manager.cpp b/src/core/hle/service/server_manager.cpp index d1e99b184..e2e399534 100644 --- a/src/core/hle/service/server_manager.cpp +++ b/src/core/hle/service/server_manager.cpp @@ -102,16 +102,17 @@ Result ServerManager::RegisterNamedService(const std::string& service_name, m_system.ServiceManager().RegisterService(service_name, max_sessions, handler))); // Get the registered port. - auto port = m_system.ServiceManager().GetServicePort(service_name); - ASSERT(port.Succeeded()); + Kernel::KPort* port{}; + ASSERT( + R_SUCCEEDED(m_system.ServiceManager().GetServicePort(std::addressof(port), service_name))); // Open a new reference to the server port. - (*port)->GetServerPort().Open(); + port->GetServerPort().Open(); // Begin tracking the server port. { std::scoped_lock ll{m_list_mutex}; - m_ports.emplace(std::addressof((*port)->GetServerPort()), std::move(handler)); + m_ports.emplace(std::addressof(port->GetServerPort()), std::move(handler)); } // Signal the wakeup event. diff --git a/src/core/hle/service/sm/sm.cpp b/src/core/hle/service/sm/sm.cpp index 1608fa24c..9ab718e0a 100644 --- a/src/core/hle/service/sm/sm.cpp +++ b/src/core/hle/service/sm/sm.cpp @@ -52,8 +52,7 @@ static Result ValidateServiceName(const std::string& name) { Result ServiceManager::RegisterService(std::string name, u32 max_sessions, SessionRequestHandlerPtr handler) { - - CASCADE_CODE(ValidateServiceName(name)); + R_TRY(ValidateServiceName(name)); std::scoped_lock lk{lock}; if (registered_services.find(name) != registered_services.end()) { @@ -77,7 +76,7 @@ Result ServiceManager::RegisterService(std::string name, u32 max_sessions, } Result ServiceManager::UnregisterService(const std::string& name) { - CASCADE_CODE(ValidateServiceName(name)); + R_TRY(ValidateServiceName(name)); std::scoped_lock lk{lock}; const auto iter = registered_services.find(name); @@ -92,8 +91,8 @@ Result ServiceManager::UnregisterService(const std::string& name) { return ResultSuccess; } -ResultVal<Kernel::KPort*> ServiceManager::GetServicePort(const std::string& name) { - CASCADE_CODE(ValidateServiceName(name)); +Result ServiceManager::GetServicePort(Kernel::KPort** out_port, const std::string& name) { + R_TRY(ValidateServiceName(name)); std::scoped_lock lk{lock}; auto it = service_ports.find(name); @@ -102,7 +101,8 @@ ResultVal<Kernel::KPort*> ServiceManager::GetServicePort(const std::string& name return Service::SM::ResultNotRegistered; } - return it->second; + *out_port = it->second; + return ResultSuccess; } /** @@ -122,32 +122,34 @@ void SM::Initialize(HLERequestContext& ctx) { } void SM::GetService(HLERequestContext& ctx) { - auto result = GetServiceImpl(ctx); + Kernel::KClientSession* client_session{}; + auto result = GetServiceImpl(&client_session, ctx); if (ctx.GetIsDeferred()) { // Don't overwrite the command buffer. return; } - if (result.Succeeded()) { + if (result == ResultSuccess) { IPC::ResponseBuilder rb{ctx, 2, 0, 1, IPC::ResponseBuilder::Flags::AlwaysMoveHandles}; - rb.Push(result.Code()); - rb.PushMoveObjects(result.Unwrap()); + rb.Push(result); + rb.PushMoveObjects(client_session); } else { IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(result.Code()); + rb.Push(result); } } void SM::GetServiceTipc(HLERequestContext& ctx) { - auto result = GetServiceImpl(ctx); + Kernel::KClientSession* client_session{}; + auto result = GetServiceImpl(&client_session, ctx); if (ctx.GetIsDeferred()) { // Don't overwrite the command buffer. return; } IPC::ResponseBuilder rb{ctx, 2, 0, 1, IPC::ResponseBuilder::Flags::AlwaysMoveHandles}; - rb.Push(result.Code()); - rb.PushMoveObjects(result.Succeeded() ? result.Unwrap() : nullptr); + rb.Push(result); + rb.PushMoveObjects(result == ResultSuccess ? client_session : nullptr); } static std::string PopServiceName(IPC::RequestParser& rp) { @@ -161,7 +163,7 @@ static std::string PopServiceName(IPC::RequestParser& rp) { return result; } -ResultVal<Kernel::KClientSession*> SM::GetServiceImpl(HLERequestContext& ctx) { +Result SM::GetServiceImpl(Kernel::KClientSession** out_client_session, HLERequestContext& ctx) { if (!ctx.GetManager()->GetIsInitializedForSm()) { return Service::SM::ResultInvalidClient; } @@ -170,18 +172,18 @@ ResultVal<Kernel::KClientSession*> SM::GetServiceImpl(HLERequestContext& ctx) { std::string name(PopServiceName(rp)); // Find the named port. - auto port_result = service_manager.GetServicePort(name); - if (port_result.Code() == Service::SM::ResultInvalidServiceName) { + Kernel::KPort* port{}; + auto port_result = service_manager.GetServicePort(&port, name); + if (port_result == Service::SM::ResultInvalidServiceName) { LOG_ERROR(Service_SM, "Invalid service name '{}'", name); return Service::SM::ResultInvalidServiceName; } - if (port_result.Failed()) { + if (port_result != ResultSuccess) { LOG_INFO(Service_SM, "Waiting for service {} to become available", name); ctx.SetIsDeferred(); return Service::SM::ResultNotRegistered; } - auto& port = port_result.Unwrap(); // Create a new session. Kernel::KClientSession* session{}; @@ -192,7 +194,8 @@ ResultVal<Kernel::KClientSession*> SM::GetServiceImpl(HLERequestContext& ctx) { LOG_DEBUG(Service_SM, "called service={} -> session={}", name, session->GetId()); - return session; + *out_client_session = session; + return ResultSuccess; } void SM::RegisterService(HLERequestContext& ctx) { diff --git a/src/core/hle/service/sm/sm.h b/src/core/hle/service/sm/sm.h index 6697f4007..14bfaf8c2 100644 --- a/src/core/hle/service/sm/sm.h +++ b/src/core/hle/service/sm/sm.h @@ -42,7 +42,7 @@ private: void RegisterService(HLERequestContext& ctx); void UnregisterService(HLERequestContext& ctx); - ResultVal<Kernel::KClientSession*> GetServiceImpl(HLERequestContext& ctx); + Result GetServiceImpl(Kernel::KClientSession** out_client_session, HLERequestContext& ctx); ServiceManager& service_manager; Kernel::KernelCore& kernel; @@ -55,7 +55,7 @@ public: Result RegisterService(std::string name, u32 max_sessions, SessionRequestHandlerPtr handler); Result UnregisterService(const std::string& name); - ResultVal<Kernel::KPort*> GetServicePort(const std::string& name); + Result GetServicePort(Kernel::KPort** out_port, const std::string& name); template <Common::DerivedFrom<SessionRequestHandler> T> std::shared_ptr<T> GetService(const std::string& service_name) const { diff --git a/src/core/hle/service/sockets/nsd.cpp b/src/core/hle/service/sockets/nsd.cpp index 5dfcaabb1..bac21752a 100644 --- a/src/core/hle/service/sockets/nsd.cpp +++ b/src/core/hle/service/sockets/nsd.cpp @@ -54,7 +54,7 @@ NSD::NSD(Core::System& system_, const char* name) : ServiceFramework{system_, na RegisterHandlers(functions); } -static ResultVal<std::string> ResolveImpl(const std::string& fqdn_in) { +static std::string ResolveImpl(const std::string& fqdn_in) { // The real implementation makes various substitutions. // For now we just return the string as-is, which is good enough when not // connecting to real Nintendo servers. @@ -64,13 +64,10 @@ static ResultVal<std::string> ResolveImpl(const std::string& fqdn_in) { static Result ResolveCommon(const std::string& fqdn_in, std::array<char, 0x100>& fqdn_out) { const auto res = ResolveImpl(fqdn_in); - if (res.Failed()) { - return res.Code(); - } - if (res->size() >= fqdn_out.size()) { + if (res.size() >= fqdn_out.size()) { return ResultOverflow; } - std::memcpy(fqdn_out.data(), res->c_str(), res->size() + 1); + std::memcpy(fqdn_out.data(), res.c_str(), res.size() + 1); return ResultSuccess; } diff --git a/src/core/hle/service/sockets/sockets.h b/src/core/hle/service/sockets/sockets.h index 77426c46e..f86af01a4 100644 --- a/src/core/hle/service/sockets/sockets.h +++ b/src/core/hle/service/sockets/sockets.h @@ -18,7 +18,9 @@ enum class Errno : u32 { AGAIN = 11, INVAL = 22, MFILE = 24, + PIPE = 32, MSGSIZE = 90, + CONNABORTED = 103, CONNRESET = 104, NOTCONN = 107, TIMEDOUT = 110, diff --git a/src/core/hle/service/sockets/sockets_translate.cpp b/src/core/hle/service/sockets/sockets_translate.cpp index c1187209f..aed05250c 100644 --- a/src/core/hle/service/sockets/sockets_translate.cpp +++ b/src/core/hle/service/sockets/sockets_translate.cpp @@ -23,10 +23,14 @@ Errno Translate(Network::Errno value) { return Errno::INVAL; case Network::Errno::MFILE: return Errno::MFILE; + case Network::Errno::PIPE: + return Errno::PIPE; case Network::Errno::NOTCONN: return Errno::NOTCONN; case Network::Errno::TIMEDOUT: return Errno::TIMEDOUT; + case Network::Errno::CONNABORTED: + return Errno::CONNABORTED; case Network::Errno::CONNRESET: return Errno::CONNRESET; case Network::Errno::INPROGRESS: diff --git a/src/core/hle/service/spl/spl_module.cpp b/src/core/hle/service/spl/spl_module.cpp index cd631b2ea..549e6f4fa 100644 --- a/src/core/hle/service/spl/spl_module.cpp +++ b/src/core/hle/service/spl/spl_module.cpp @@ -30,10 +30,10 @@ void Module::Interface::GetConfig(HLERequestContext& ctx) { // This should call svcCallSecureMonitor with the appropriate args. // Since we do not have it implemented yet, we will use this for now. - const auto smc_result = GetConfigImpl(config_item); - const auto result_code = smc_result.Code(); + u64 smc_result{}; + const auto result_code = GetConfigImpl(&smc_result, config_item); - if (smc_result.Failed()) { + if (result_code != ResultSuccess) { LOG_ERROR(Service_SPL, "called, config_item={}, result_code={}", config_item, result_code.raw); @@ -42,11 +42,11 @@ void Module::Interface::GetConfig(HLERequestContext& ctx) { } LOG_DEBUG(Service_SPL, "called, config_item={}, result_code={}, smc_result={}", config_item, - result_code.raw, *smc_result); + result_code.raw, smc_result); IPC::ResponseBuilder rb{ctx, 4}; rb.Push(result_code); - rb.Push(*smc_result); + rb.Push(smc_result); } void Module::Interface::ModularExponentiate(HLERequestContext& ctx) { @@ -99,7 +99,7 @@ void Module::Interface::GetBootReason(HLERequestContext& ctx) { rb.Push(ResultSecureMonitorNotImplemented); } -ResultVal<u64> Module::Interface::GetConfigImpl(ConfigItem config_item) const { +Result Module::Interface::GetConfigImpl(u64* out_config, ConfigItem config_item) const { switch (config_item) { case ConfigItem::DisableProgramVerification: case ConfigItem::DramId: @@ -121,40 +121,50 @@ ResultVal<u64> Module::Interface::GetConfigImpl(ConfigItem config_item) const { return ResultSecureMonitorNotImplemented; case ConfigItem::ExosphereApiVersion: // Get information about the current exosphere version. - return (u64{HLE::ApiVersion::ATMOSPHERE_RELEASE_VERSION_MAJOR} << 56) | - (u64{HLE::ApiVersion::ATMOSPHERE_RELEASE_VERSION_MINOR} << 48) | - (u64{HLE::ApiVersion::ATMOSPHERE_RELEASE_VERSION_MICRO} << 40) | - (static_cast<u64>(HLE::ApiVersion::GetTargetFirmware())); + *out_config = (u64{HLE::ApiVersion::ATMOSPHERE_RELEASE_VERSION_MAJOR} << 56) | + (u64{HLE::ApiVersion::ATMOSPHERE_RELEASE_VERSION_MINOR} << 48) | + (u64{HLE::ApiVersion::ATMOSPHERE_RELEASE_VERSION_MICRO} << 40) | + (static_cast<u64>(HLE::ApiVersion::GetTargetFirmware())); + return ResultSuccess; case ConfigItem::ExosphereNeedsReboot: // We are executing, so we aren't in the process of rebooting. - return u64{0}; + *out_config = u64{0}; + return ResultSuccess; case ConfigItem::ExosphereNeedsShutdown: // We are executing, so we aren't in the process of shutting down. - return u64{0}; + *out_config = u64{0}; + return ResultSuccess; case ConfigItem::ExosphereGitCommitHash: // Get information about the current exosphere git commit hash. - return u64{0}; + *out_config = u64{0}; + return ResultSuccess; case ConfigItem::ExosphereHasRcmBugPatch: // Get information about whether this unit has the RCM bug patched. - return u64{0}; + *out_config = u64{0}; + return ResultSuccess; case ConfigItem::ExosphereBlankProdInfo: // Get whether this unit should simulate a "blanked" PRODINFO. - return u64{0}; + *out_config = u64{0}; + return ResultSuccess; case ConfigItem::ExosphereAllowCalWrites: // Get whether this unit should allow writing to the calibration partition. - return u64{0}; + *out_config = u64{0}; + return ResultSuccess; case ConfigItem::ExosphereEmummcType: // Get what kind of emummc this unit has active. - return u64{0}; + *out_config = u64{0}; + return ResultSuccess; case ConfigItem::ExospherePayloadAddress: // Gets the physical address of the reboot payload buffer, if one exists. return ResultSecureMonitorNotInitialized; case ConfigItem::ExosphereLogConfiguration: // Get the log configuration. - return u64{0}; + *out_config = u64{0}; + return ResultSuccess; case ConfigItem::ExosphereForceEnableUsb30: // Get whether usb 3.0 should be force-enabled. - return u64{0}; + *out_config = u64{0}; + return ResultSuccess; default: return ResultSecureMonitorInvalidArgument; } diff --git a/src/core/hle/service/spl/spl_module.h b/src/core/hle/service/spl/spl_module.h index e074e115d..06dcffa6c 100644 --- a/src/core/hle/service/spl/spl_module.h +++ b/src/core/hle/service/spl/spl_module.h @@ -35,7 +35,7 @@ public: std::shared_ptr<Module> module; private: - ResultVal<u64> GetConfigImpl(ConfigItem config_item) const; + Result GetConfigImpl(u64* out_config, ConfigItem config_item) const; std::mt19937 rng; }; diff --git a/src/core/hle/service/ssl/ssl.cpp b/src/core/hle/service/ssl/ssl.cpp index 9c96f9763..2cba9e5c9 100644 --- a/src/core/hle/service/ssl/ssl.cpp +++ b/src/core/hle/service/ssl/ssl.cpp @@ -4,6 +4,7 @@ #include "common/string_util.h" #include "core/core.h" +#include "core/hle/result.h" #include "core/hle/service/ipc_helpers.h" #include "core/hle/service/server_manager.h" #include "core/hle/service/service.h" @@ -141,12 +142,12 @@ private: bool did_set_host_name = false; bool did_handshake = false; - ResultVal<s32> SetSocketDescriptorImpl(s32 fd) { + Result SetSocketDescriptorImpl(s32* out_fd, s32 fd) { LOG_DEBUG(Service_SSL, "called, fd={}", fd); ASSERT(!did_handshake); auto bsd = system.ServiceManager().GetService<Service::Sockets::BSD>("bsd:u"); ASSERT_OR_EXECUTE(bsd, { return ResultInternalError; }); - s32 ret_fd; + // Based on https://switchbrew.org/wiki/SSL_services#SetSocketDescriptor if (do_not_close_socket) { auto res = bsd->DuplicateSocketImpl(fd); @@ -156,9 +157,9 @@ private: } fd = *res; fd_to_close = fd; - ret_fd = fd; + *out_fd = fd; } else { - ret_fd = -1; + *out_fd = -1; } std::optional<std::shared_ptr<Network::SocketBase>> sock = bsd->GetSocket(fd); if (!sock.has_value()) { @@ -167,7 +168,7 @@ private: } socket = std::move(*sock); backend->SetSocket(socket); - return ret_fd; + return ResultSuccess; } Result SetHostNameImpl(const std::string& hostname) { @@ -247,34 +248,36 @@ private: return ret; } - ResultVal<std::vector<u8>> ReadImpl(size_t size) { + Result ReadImpl(std::vector<u8>* out_data, size_t size) { ASSERT_OR_EXECUTE(did_handshake, { return ResultInternalError; }); - std::vector<u8> res(size); - ResultVal<size_t> actual = backend->Read(res); - if (actual.Failed()) { - return actual.Code(); + size_t actual_size{}; + Result res = backend->Read(&actual_size, *out_data); + if (res != ResultSuccess) { + return res; } - res.resize(*actual); + out_data->resize(actual_size); return res; } - ResultVal<size_t> WriteImpl(std::span<const u8> data) { + Result WriteImpl(size_t* out_size, std::span<const u8> data) { ASSERT_OR_EXECUTE(did_handshake, { return ResultInternalError; }); - return backend->Write(data); + return backend->Write(out_size, data); } - ResultVal<s32> PendingImpl() { + Result PendingImpl(s32* out_pending) { LOG_WARNING(Service_SSL, "(STUBBED) called."); - return 0; + *out_pending = 0; + return ResultSuccess; } void SetSocketDescriptor(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const s32 fd = rp.Pop<s32>(); - const ResultVal<s32> res = SetSocketDescriptorImpl(fd); + const s32 in_fd = rp.Pop<s32>(); + s32 out_fd{-1}; + const Result res = SetSocketDescriptorImpl(&out_fd, in_fd); IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(res.Code()); - rb.Push<s32>(res.ValueOr(-1)); + rb.Push(res); + rb.Push<s32>(out_fd); } void SetHostName(HLERequestContext& ctx) { @@ -313,14 +316,15 @@ private: }; static_assert(sizeof(OutputParameters) == 0x8); - const Result res = DoHandshakeImpl(); + Result res = DoHandshakeImpl(); OutputParameters out{}; if (res == ResultSuccess) { - auto certs = backend->GetServerCerts(); - if (certs.Succeeded()) { - const std::vector<u8> certs_buf = SerializeServerCerts(*certs); + std::vector<std::vector<u8>> certs; + res = backend->GetServerCerts(&certs); + if (res == ResultSuccess) { + const std::vector<u8> certs_buf = SerializeServerCerts(certs); ctx.WriteBuffer(certs_buf); - out.certs_count = static_cast<u32>(certs->size()); + out.certs_count = static_cast<u32>(certs.size()); out.certs_size = static_cast<u32>(certs_buf.size()); } } @@ -330,29 +334,32 @@ private: } void Read(HLERequestContext& ctx) { - const ResultVal<std::vector<u8>> res = ReadImpl(ctx.GetWriteBufferSize()); + std::vector<u8> output_bytes; + const Result res = ReadImpl(&output_bytes, ctx.GetWriteBufferSize()); IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(res.Code()); - if (res.Succeeded()) { - rb.Push(static_cast<u32>(res->size())); - ctx.WriteBuffer(*res); + rb.Push(res); + if (res == ResultSuccess) { + rb.Push(static_cast<u32>(output_bytes.size())); + ctx.WriteBuffer(output_bytes); } else { rb.Push(static_cast<u32>(0)); } } void Write(HLERequestContext& ctx) { - const ResultVal<size_t> res = WriteImpl(ctx.ReadBuffer()); + size_t write_size{0}; + const Result res = WriteImpl(&write_size, ctx.ReadBuffer()); IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(res.Code()); - rb.Push(static_cast<u32>(res.ValueOr(0))); + rb.Push(res); + rb.Push(static_cast<u32>(write_size)); } void Pending(HLERequestContext& ctx) { - const ResultVal<s32> res = PendingImpl(); + s32 pending_size{0}; + const Result res = PendingImpl(&pending_size); IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(res.Code()); - rb.Push<s32>(res.ValueOr(0)); + rb.Push(res); + rb.Push<s32>(pending_size); } void SetSessionCacheMode(HLERequestContext& ctx) { @@ -438,13 +445,14 @@ private: void CreateConnection(HLERequestContext& ctx) { LOG_WARNING(Service_SSL, "called"); - auto backend_res = CreateSSLConnectionBackend(); + std::unique_ptr<SSLConnectionBackend> backend; + const Result res = CreateSSLConnectionBackend(&backend); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - rb.Push(backend_res.Code()); - if (backend_res.Succeeded()) { + rb.Push(res); + if (res == ResultSuccess) { rb.PushIpcInterface<ISslConnection>(system, ssl_version, shared_data, - std::move(*backend_res)); + std::move(backend)); } } diff --git a/src/core/hle/service/ssl/ssl_backend.h b/src/core/hle/service/ssl/ssl_backend.h index 409f4367c..a2ec8e694 100644 --- a/src/core/hle/service/ssl/ssl_backend.h +++ b/src/core/hle/service/ssl/ssl_backend.h @@ -35,11 +35,11 @@ public: virtual void SetSocket(std::shared_ptr<Network::SocketBase> socket) = 0; virtual Result SetHostName(const std::string& hostname) = 0; virtual Result DoHandshake() = 0; - virtual ResultVal<size_t> Read(std::span<u8> data) = 0; - virtual ResultVal<size_t> Write(std::span<const u8> data) = 0; - virtual ResultVal<std::vector<std::vector<u8>>> GetServerCerts() = 0; + virtual Result Read(size_t* out_size, std::span<u8> data) = 0; + virtual Result Write(size_t* out_size, std::span<const u8> data) = 0; + virtual Result GetServerCerts(std::vector<std::vector<u8>>* out_certs) = 0; }; -ResultVal<std::unique_ptr<SSLConnectionBackend>> CreateSSLConnectionBackend(); +Result CreateSSLConnectionBackend(std::unique_ptr<SSLConnectionBackend>* out_backend); } // namespace Service::SSL diff --git a/src/core/hle/service/ssl/ssl_backend_none.cpp b/src/core/hle/service/ssl/ssl_backend_none.cpp index 2f4f23c42..a7fafd0a3 100644 --- a/src/core/hle/service/ssl/ssl_backend_none.cpp +++ b/src/core/hle/service/ssl/ssl_backend_none.cpp @@ -7,7 +7,7 @@ namespace Service::SSL { -ResultVal<std::unique_ptr<SSLConnectionBackend>> CreateSSLConnectionBackend() { +Result CreateSSLConnectionBackend(std::unique_ptr<SSLConnectionBackend>* out_backend) { LOG_ERROR(Service_SSL, "Can't create SSL connection because no SSL backend is available on this platform"); return ResultInternalError; diff --git a/src/core/hle/service/ssl/ssl_backend_openssl.cpp b/src/core/hle/service/ssl/ssl_backend_openssl.cpp index 6ca869dbf..b2dd37cd4 100644 --- a/src/core/hle/service/ssl/ssl_backend_openssl.cpp +++ b/src/core/hle/service/ssl/ssl_backend_openssl.cpp @@ -105,31 +105,30 @@ public: return ResultInternalError; } } - return HandleReturn("SSL_do_handshake", 0, ret).Code(); + return HandleReturn("SSL_do_handshake", 0, ret); } - ResultVal<size_t> Read(std::span<u8> data) override { - size_t actual; - const int ret = SSL_read_ex(ssl, data.data(), data.size(), &actual); - return HandleReturn("SSL_read_ex", actual, ret); + Result Read(size_t* out_size, std::span<u8> data) override { + const int ret = SSL_read_ex(ssl, data.data(), data.size(), out_size); + return HandleReturn("SSL_read_ex", out_size, ret); } - ResultVal<size_t> Write(std::span<const u8> data) override { - size_t actual; - const int ret = SSL_write_ex(ssl, data.data(), data.size(), &actual); - return HandleReturn("SSL_write_ex", actual, ret); + Result Write(size_t* out_size, std::span<const u8> data) override { + const int ret = SSL_write_ex(ssl, data.data(), data.size(), out_size); + return HandleReturn("SSL_write_ex", out_size, ret); } - ResultVal<size_t> HandleReturn(const char* what, size_t actual, int ret) { + Result HandleReturn(const char* what, size_t* actual, int ret) { const int ssl_err = SSL_get_error(ssl, ret); CheckOpenSSLErrors(); switch (ssl_err) { case SSL_ERROR_NONE: - return actual; + return ResultSuccess; case SSL_ERROR_ZERO_RETURN: LOG_DEBUG(Service_SSL, "{} => SSL_ERROR_ZERO_RETURN", what); // DoHandshake special-cases this, but for Read and Write: - return size_t(0); + *actual = 0; + return ResultSuccess; case SSL_ERROR_WANT_READ: LOG_DEBUG(Service_SSL, "{} => SSL_ERROR_WANT_READ", what); return ResultWouldBlock; @@ -139,20 +138,20 @@ public: default: if (ssl_err == SSL_ERROR_SYSCALL && got_read_eof) { LOG_DEBUG(Service_SSL, "{} => SSL_ERROR_SYSCALL because server hung up", what); - return size_t(0); + *actual = 0; + return ResultSuccess; } LOG_ERROR(Service_SSL, "{} => other SSL_get_error return value {}", what, ssl_err); return ResultInternalError; } } - ResultVal<std::vector<std::vector<u8>>> GetServerCerts() override { + Result GetServerCerts(std::vector<std::vector<u8>>* out_certs) override { STACK_OF(X509)* chain = SSL_get_peer_cert_chain(ssl); if (!chain) { LOG_ERROR(Service_SSL, "SSL_get_peer_cert_chain returned nullptr"); return ResultInternalError; } - std::vector<std::vector<u8>> ret; int count = sk_X509_num(chain); ASSERT(count >= 0); for (int i = 0; i < count; i++) { @@ -161,10 +160,10 @@ public: unsigned char* buf = nullptr; int len = i2d_X509(x509, &buf); ASSERT_OR_EXECUTE(len >= 0 && buf, { continue; }); - ret.emplace_back(buf, buf + len); + out_certs->emplace_back(buf, buf + len); OPENSSL_free(buf); } - return ret; + return ResultSuccess; } ~SSLConnectionBackendOpenSSL() { @@ -253,13 +252,13 @@ public: std::shared_ptr<Network::SocketBase> socket; }; -ResultVal<std::unique_ptr<SSLConnectionBackend>> CreateSSLConnectionBackend() { +Result CreateSSLConnectionBackend(std::unique_ptr<SSLConnectionBackend>* out_backend) { auto conn = std::make_unique<SSLConnectionBackendOpenSSL>(); - const Result res = conn->Init(); - if (res.IsFailure()) { - return res; - } - return conn; + + R_TRY(conn->Init()); + + *out_backend = std::move(conn); + return ResultSuccess; } namespace { diff --git a/src/core/hle/service/ssl/ssl_backend_schannel.cpp b/src/core/hle/service/ssl/ssl_backend_schannel.cpp index d8074339a..bda12b761 100644 --- a/src/core/hle/service/ssl/ssl_backend_schannel.cpp +++ b/src/core/hle/service/ssl/ssl_backend_schannel.cpp @@ -299,21 +299,22 @@ public: return ResultSuccess; } - ResultVal<size_t> Read(std::span<u8> data) override { + Result Read(size_t* out_size, std::span<u8> data) override { + *out_size = 0; if (handshake_state != HandshakeState::Connected) { LOG_ERROR(Service_SSL, "Called Read but we did not successfully handshake"); return ResultInternalError; } if (data.size() == 0 || got_read_eof) { - return size_t(0); + return ResultSuccess; } while (1) { if (!cleartext_read_buf.empty()) { - const size_t read_size = std::min(cleartext_read_buf.size(), data.size()); - std::memcpy(data.data(), cleartext_read_buf.data(), read_size); + *out_size = std::min(cleartext_read_buf.size(), data.size()); + std::memcpy(data.data(), cleartext_read_buf.data(), *out_size); cleartext_read_buf.erase(cleartext_read_buf.begin(), - cleartext_read_buf.begin() + read_size); - return read_size; + cleartext_read_buf.begin() + *out_size); + return ResultSuccess; } if (!ciphertext_read_buf.empty()) { SecBuffer empty{ @@ -366,7 +367,8 @@ public: case SEC_I_CONTEXT_EXPIRED: // Server hung up by sending close_notify. got_read_eof = true; - return size_t(0); + *out_size = 0; + return ResultSuccess; default: LOG_ERROR(Service_SSL, "DecryptMessage failed: {}", Common::NativeErrorToString(ret)); @@ -379,18 +381,21 @@ public: } if (ciphertext_read_buf.empty()) { got_read_eof = true; - return size_t(0); + *out_size = 0; + return ResultSuccess; } } } - ResultVal<size_t> Write(std::span<const u8> data) override { + Result Write(size_t* out_size, std::span<const u8> data) override { + *out_size = 0; + if (handshake_state != HandshakeState::Connected) { LOG_ERROR(Service_SSL, "Called Write but we did not successfully handshake"); return ResultInternalError; } if (data.size() == 0) { - return size_t(0); + return ResultSuccess; } data = data.subspan(0, std::min<size_t>(data.size(), stream_sizes.cbMaximumMessage)); if (!cleartext_write_buf.empty()) { @@ -402,7 +407,7 @@ public: LOG_ERROR(Service_SSL, "Called Write but buffer does not match previous buffer"); return ResultInternalError; } - return WriteAlreadyEncryptedData(); + return WriteAlreadyEncryptedData(out_size); } else { cleartext_write_buf.assign(data.begin(), data.end()); } @@ -448,21 +453,21 @@ public: tmp_data_buf.end()); ciphertext_write_buf.insert(ciphertext_write_buf.end(), trailer_buf.begin(), trailer_buf.end()); - return WriteAlreadyEncryptedData(); + return WriteAlreadyEncryptedData(out_size); } - ResultVal<size_t> WriteAlreadyEncryptedData() { + Result WriteAlreadyEncryptedData(size_t* out_size) { const Result r = FlushCiphertextWriteBuf(); if (r != ResultSuccess) { return r; } // write buf is empty - const size_t cleartext_bytes_written = cleartext_write_buf.size(); + *out_size = cleartext_write_buf.size(); cleartext_write_buf.clear(); - return cleartext_bytes_written; + return ResultSuccess; } - ResultVal<std::vector<std::vector<u8>>> GetServerCerts() override { + Result GetServerCerts(std::vector<std::vector<u8>>* out_certs) override { PCCERT_CONTEXT returned_cert = nullptr; const SECURITY_STATUS ret = QueryContextAttributes(&ctxt, SECPKG_ATTR_REMOTE_CERT_CONTEXT, &returned_cert); @@ -473,16 +478,15 @@ public: return ResultInternalError; } PCCERT_CONTEXT some_cert = nullptr; - std::vector<std::vector<u8>> certs; while ((some_cert = CertEnumCertificatesInStore(returned_cert->hCertStore, some_cert))) { - certs.emplace_back(static_cast<u8*>(some_cert->pbCertEncoded), - static_cast<u8*>(some_cert->pbCertEncoded) + - some_cert->cbCertEncoded); + out_certs->emplace_back(static_cast<u8*>(some_cert->pbCertEncoded), + static_cast<u8*>(some_cert->pbCertEncoded) + + some_cert->cbCertEncoded); } - std::reverse(certs.begin(), - certs.end()); // Windows returns certs in reverse order from what we want + std::reverse(out_certs->begin(), + out_certs->end()); // Windows returns certs in reverse order from what we want CertFreeCertificateContext(returned_cert); - return certs; + return ResultSuccess; } ~SSLConnectionBackendSchannel() { @@ -532,13 +536,13 @@ public: size_t read_buf_fill_size = 0; }; -ResultVal<std::unique_ptr<SSLConnectionBackend>> CreateSSLConnectionBackend() { +Result CreateSSLConnectionBackend(std::unique_ptr<SSLConnectionBackend>* out_backend) { auto conn = std::make_unique<SSLConnectionBackendSchannel>(); - const Result res = conn->Init(); - if (res.IsFailure()) { - return res; - } - return conn; + + R_TRY(conn->Init()); + + *out_backend = std::move(conn); + return ResultSuccess; } } // namespace Service::SSL diff --git a/src/core/hle/service/ssl/ssl_backend_securetransport.cpp b/src/core/hle/service/ssl/ssl_backend_securetransport.cpp index b3083cbad..c48914f64 100644 --- a/src/core/hle/service/ssl/ssl_backend_securetransport.cpp +++ b/src/core/hle/service/ssl/ssl_backend_securetransport.cpp @@ -100,27 +100,23 @@ public: Result DoHandshake() override { OSStatus status = SSLHandshake(context); - return HandleReturn("SSLHandshake", 0, status).Code(); + return HandleReturn("SSLHandshake", 0, status); } - ResultVal<size_t> Read(std::span<u8> data) override { - size_t actual; - OSStatus status = SSLRead(context, data.data(), data.size(), &actual); - ; - return HandleReturn("SSLRead", actual, status); + Result Read(size_t* out_size, std::span<u8> data) override { + OSStatus status = SSLRead(context, data.data(), data.size(), out_size); + return HandleReturn("SSLRead", out_size, status); } - ResultVal<size_t> Write(std::span<const u8> data) override { - size_t actual; - OSStatus status = SSLWrite(context, data.data(), data.size(), &actual); - ; - return HandleReturn("SSLWrite", actual, status); + Result Write(size_t* out_size, std::span<const u8> data) override { + OSStatus status = SSLWrite(context, data.data(), data.size(), out_size); + return HandleReturn("SSLWrite", out_size, status); } - ResultVal<size_t> HandleReturn(const char* what, size_t actual, OSStatus status) { + Result HandleReturn(const char* what, size_t* actual, OSStatus status) { switch (status) { case 0: - return actual; + return ResultSuccess; case errSSLWouldBlock: return ResultWouldBlock; default: { @@ -136,22 +132,21 @@ public: } } - ResultVal<std::vector<std::vector<u8>>> GetServerCerts() override { + Result GetServerCerts(std::vector<std::vector<u8>>* out_certs) override { CFReleaser<SecTrustRef> trust; OSStatus status = SSLCopyPeerTrust(context, &trust.ptr); if (status) { LOG_ERROR(Service_SSL, "SSLCopyPeerTrust failed: {}", OSStatusToString(status)); return ResultInternalError; } - std::vector<std::vector<u8>> ret; for (CFIndex i = 0, count = SecTrustGetCertificateCount(trust); i < count; i++) { SecCertificateRef cert = SecTrustGetCertificateAtIndex(trust, i); CFReleaser<CFDataRef> data(SecCertificateCopyData(cert)); ASSERT_OR_EXECUTE(data, { return ResultInternalError; }); const u8* ptr = CFDataGetBytePtr(data); - ret.emplace_back(ptr, ptr + CFDataGetLength(data)); + out_certs->emplace_back(ptr, ptr + CFDataGetLength(data)); } - return ret; + return ResultSuccess; } static OSStatus ReadCallback(SSLConnectionRef connection, void* data, size_t* dataLength) { @@ -210,13 +205,13 @@ private: std::shared_ptr<Network::SocketBase> socket; }; -ResultVal<std::unique_ptr<SSLConnectionBackend>> CreateSSLConnectionBackend() { +Result CreateSSLConnectionBackend(std::unique_ptr<SSLConnectionBackend>* out_backend) { auto conn = std::make_unique<SSLConnectionBackendSecureTransport>(); - const Result res = conn->Init(); - if (res.IsFailure()) { - return res; - } - return conn; + + R_TRY(conn->Init()); + + *out_backend = std::move(conn); + return ResultSuccess; } } // namespace Service::SSL diff --git a/src/core/hle/service/vi/display/vi_display.cpp b/src/core/hle/service/vi/display/vi_display.cpp index 69af2868a..f0b5eff8a 100644 --- a/src/core/hle/service/vi/display/vi_display.cpp +++ b/src/core/hle/service/vi/display/vi_display.cpp @@ -58,14 +58,15 @@ const Layer& Display::GetLayer(std::size_t index) const { return *layers.at(index); } -ResultVal<Kernel::KReadableEvent*> Display::GetVSyncEvent() { +Result Display::GetVSyncEvent(Kernel::KReadableEvent** out_vsync_event) { if (got_vsync_event) { return ResultPermissionDenied; } got_vsync_event = true; - return GetVSyncEventUnchecked(); + *out_vsync_event = GetVSyncEventUnchecked(); + return ResultSuccess; } Kernel::KReadableEvent* Display::GetVSyncEventUnchecked() { diff --git a/src/core/hle/service/vi/display/vi_display.h b/src/core/hle/service/vi/display/vi_display.h index 3f31d1f32..101cbce20 100644 --- a/src/core/hle/service/vi/display/vi_display.h +++ b/src/core/hle/service/vi/display/vi_display.h @@ -85,7 +85,7 @@ public: * @returns The internal Vsync event if it has not yet been retrieved, * VI::ResultPermissionDenied otherwise. */ - [[nodiscard]] ResultVal<Kernel::KReadableEvent*> GetVSyncEvent(); + [[nodiscard]] Result GetVSyncEvent(Kernel::KReadableEvent** out_vsync_event); /// Gets the internal vsync event. Kernel::KReadableEvent* GetVSyncEventUnchecked(); diff --git a/src/core/hle/service/vi/vi.cpp b/src/core/hle/service/vi/vi.cpp index 1b193f00c..6bb02393c 100644 --- a/src/core/hle/service/vi/vi.cpp +++ b/src/core/hle/service/vi/vi.cpp @@ -683,9 +683,9 @@ private: LOG_DEBUG(Service_VI, "called. display_id={}", display_id); - const auto vsync_event = nv_flinger.FindVsyncEvent(display_id); - if (vsync_event.Failed()) { - const auto result = vsync_event.Code(); + Kernel::KReadableEvent* vsync_event{}; + const auto result = nv_flinger.FindVsyncEvent(&vsync_event, display_id); + if (result != ResultSuccess) { if (result == ResultNotFound) { LOG_ERROR(Service_VI, "Vsync event was not found for display_id={}", display_id); } @@ -697,7 +697,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(ResultSuccess); - rb.PushCopyObjects(*vsync_event); + rb.PushCopyObjects(vsync_event); } void ConvertScalingMode(HLERequestContext& ctx) { @@ -705,15 +705,16 @@ private: const auto mode = rp.PopEnum<NintendoScaleMode>(); LOG_DEBUG(Service_VI, "called mode={}", mode); - const auto converted_mode = ConvertScalingModeImpl(mode); + ConvertedScaleMode converted_mode{}; + const auto result = ConvertScalingModeImpl(&converted_mode, mode); - if (converted_mode.Succeeded()) { + if (result == ResultSuccess) { IPC::ResponseBuilder rb{ctx, 4}; rb.Push(ResultSuccess); - rb.PushEnum(*converted_mode); + rb.PushEnum(converted_mode); } else { IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(converted_mode.Code()); + rb.Push(result); } } @@ -760,18 +761,24 @@ private: rb.Push(alignment); } - static ResultVal<ConvertedScaleMode> ConvertScalingModeImpl(NintendoScaleMode mode) { + static Result ConvertScalingModeImpl(ConvertedScaleMode* out_scaling_mode, + NintendoScaleMode mode) { switch (mode) { case NintendoScaleMode::None: - return ConvertedScaleMode::None; + *out_scaling_mode = ConvertedScaleMode::None; + return ResultSuccess; case NintendoScaleMode::Freeze: - return ConvertedScaleMode::Freeze; + *out_scaling_mode = ConvertedScaleMode::Freeze; + return ResultSuccess; case NintendoScaleMode::ScaleToWindow: - return ConvertedScaleMode::ScaleToWindow; + *out_scaling_mode = ConvertedScaleMode::ScaleToWindow; + return ResultSuccess; case NintendoScaleMode::ScaleAndCrop: - return ConvertedScaleMode::ScaleAndCrop; + *out_scaling_mode = ConvertedScaleMode::ScaleAndCrop; + return ResultSuccess; case NintendoScaleMode::PreserveAspectRatio: - return ConvertedScaleMode::PreserveAspectRatio; + *out_scaling_mode = ConvertedScaleMode::PreserveAspectRatio; + return ResultSuccess; default: LOG_ERROR(Service_VI, "Invalid scaling mode specified, mode={}", mode); return ResultOperationFailed; diff --git a/src/core/internal_network/network.cpp b/src/core/internal_network/network.cpp index 28f89c599..5d28300e6 100644 --- a/src/core/internal_network/network.cpp +++ b/src/core/internal_network/network.cpp @@ -39,6 +39,11 @@ namespace Network { namespace { +enum class CallType { + Send, + Other, +}; + #ifdef _WIN32 using socklen_t = int; @@ -96,7 +101,7 @@ bool EnableNonBlock(SOCKET fd, bool enable) { return ioctlsocket(fd, FIONBIO, &value) != SOCKET_ERROR; } -Errno TranslateNativeError(int e) { +Errno TranslateNativeError(int e, CallType call_type = CallType::Other) { switch (e) { case 0: return Errno::SUCCESS; @@ -112,6 +117,14 @@ Errno TranslateNativeError(int e) { return Errno::AGAIN; case WSAECONNREFUSED: return Errno::CONNREFUSED; + case WSAECONNABORTED: + if (call_type == CallType::Send) { + // Winsock yields WSAECONNABORTED from `send` in situations where Unix + // systems, and actual Switches, yield EPIPE. + return Errno::PIPE; + } else { + return Errno::CONNABORTED; + } case WSAECONNRESET: return Errno::CONNRESET; case WSAEHOSTUNREACH: @@ -198,7 +211,7 @@ bool EnableNonBlock(int fd, bool enable) { return fcntl(fd, F_SETFL, flags) == 0; } -Errno TranslateNativeError(int e) { +Errno TranslateNativeError(int e, CallType call_type = CallType::Other) { switch (e) { case 0: return Errno::SUCCESS; @@ -208,6 +221,10 @@ Errno TranslateNativeError(int e) { return Errno::INVAL; case EMFILE: return Errno::MFILE; + case EPIPE: + return Errno::PIPE; + case ECONNABORTED: + return Errno::CONNABORTED; case ENOTCONN: return Errno::NOTCONN; case EAGAIN: @@ -236,13 +253,13 @@ Errno TranslateNativeError(int e) { #endif -Errno GetAndLogLastError() { +Errno GetAndLogLastError(CallType call_type = CallType::Other) { #ifdef _WIN32 int e = WSAGetLastError(); #else int e = errno; #endif - const Errno err = TranslateNativeError(e); + const Errno err = TranslateNativeError(e, call_type); if (err == Errno::AGAIN || err == Errno::TIMEDOUT || err == Errno::INPROGRESS) { // These happen during normal operation, so only log them at debug level. LOG_DEBUG(Network, "Socket operation error: {}", Common::NativeErrorToString(e)); @@ -476,7 +493,13 @@ NetworkInstance::~NetworkInstance() { std::optional<IPv4Address> GetHostIPv4Address() { const auto network_interface = Network::GetSelectedNetworkInterface(); if (!network_interface.has_value()) { - LOG_DEBUG(Network, "GetSelectedNetworkInterface returned no interface"); + // Only print the error once to avoid log spam + static bool print_error = true; + if (print_error) { + LOG_ERROR(Network, "GetSelectedNetworkInterface returned no interface"); + print_error = false; + } + return {}; } @@ -725,13 +748,17 @@ std::pair<s32, Errno> Socket::Send(std::span<const u8> message, int flags) { ASSERT(message.size() < static_cast<size_t>(std::numeric_limits<int>::max())); ASSERT(flags == 0); + int native_flags = 0; +#if YUZU_UNIX + native_flags |= MSG_NOSIGNAL; // do not send us SIGPIPE +#endif const auto result = send(fd, reinterpret_cast<const char*>(message.data()), - static_cast<int>(message.size()), 0); + static_cast<int>(message.size()), native_flags); if (result != SOCKET_ERROR) { return {static_cast<s32>(result), Errno::SUCCESS}; } - return {-1, GetAndLogLastError()}; + return {-1, GetAndLogLastError(CallType::Send)}; } std::pair<s32, Errno> Socket::SendTo(u32 flags, std::span<const u8> message, @@ -753,7 +780,7 @@ std::pair<s32, Errno> Socket::SendTo(u32 flags, std::span<const u8> message, return {static_cast<s32>(result), Errno::SUCCESS}; } - return {-1, GetAndLogLastError()}; + return {-1, GetAndLogLastError(CallType::Send)}; } Errno Socket::Close() { diff --git a/src/core/internal_network/network.h b/src/core/internal_network/network.h index badcb8369..c7e20ae34 100644 --- a/src/core/internal_network/network.h +++ b/src/core/internal_network/network.h @@ -33,10 +33,12 @@ enum class Errno { BADF, INVAL, MFILE, + PIPE, NOTCONN, AGAIN, CONNREFUSED, CONNRESET, + CONNABORTED, HOSTUNREACH, NETDOWN, NETUNREACH, diff --git a/src/core/internal_network/network_interface.cpp b/src/core/internal_network/network_interface.cpp index 4c909a6d3..7c37f660b 100644 --- a/src/core/internal_network/network_interface.cpp +++ b/src/core/internal_network/network_interface.cpp @@ -200,7 +200,14 @@ std::optional<NetworkInterface> GetSelectedNetworkInterface() { }); if (res == network_interfaces.end()) { - LOG_DEBUG(Network, "Couldn't find selected interface \"{}\"", selected_network_interface); + // Only print the error once to avoid log spam + static bool print_error = true; + if (print_error) { + LOG_ERROR(Network, "Couldn't find selected interface \"{}\"", + selected_network_interface); + print_error = false; + } + return std::nullopt; } diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp index f24474ed8..07c65dc1a 100644 --- a/src/core/loader/loader.cpp +++ b/src/core/loader/loader.cpp @@ -135,7 +135,7 @@ constexpr std::array<const char*, 66> RESULT_MESSAGES{ "The titlekey and/or titlekek is incorrect or the section header is invalid.", "The XCI file is missing a Program-type NCA.", "The NCA file is not an application.", - "The ExeFS partition could not be found.", + "The Program-type NCA contains no executable. An update may be required.", "The XCI file has a bad header.", "The XCI file is missing a partition.", "The file could not be found or does not exist.", @@ -169,7 +169,7 @@ constexpr std::array<const char*, 66> RESULT_MESSAGES{ "The BKTR-type NCA has a bad Subsection block.", "The BKTR-type NCA has a bad Relocation bucket.", "The BKTR-type NCA has a bad Subsection bucket.", - "The BKTR-type NCA is missing the base RomFS.", + "Game updates cannot be loaded directly. Load the base game instead.", "The NSP or XCI does not contain an update in addition to the base game.", "The KIP file has a bad header.", "The KIP BLZ decompression of the section failed unexpectedly.", diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h index 7a2a52fd4..721eb8e8c 100644 --- a/src/core/loader/loader.h +++ b/src/core/loader/loader.h @@ -79,8 +79,6 @@ enum class ResultStatus : u16 { ErrorBadPFSHeader, ErrorIncorrectPFSFileSize, ErrorBadNCAHeader, - ErrorCompressedNCA, - ErrorSparseNCA, ErrorMissingProductionKeyFile, ErrorMissingHeaderKey, ErrorIncorrectHeaderKey, @@ -276,16 +274,6 @@ public: } /** - * Gets the difference between the start of the IVFC header and the start of level 6 (RomFS) - * data. Needed for BKTR patching. - * - * @return IVFC offset for RomFS. - */ - virtual u64 ReadRomFSIVFCOffset() const { - return 0; - } - - /** * Get the title of the application * * @param[out] title Reference to store the application title into diff --git a/src/core/loader/nax.cpp b/src/core/loader/nax.cpp index cf35b1249..3b7b005ff 100644 --- a/src/core/loader/nax.cpp +++ b/src/core/loader/nax.cpp @@ -76,10 +76,6 @@ ResultStatus AppLoader_NAX::ReadRomFS(FileSys::VirtualFile& dir) { return nca_loader->ReadRomFS(dir); } -u64 AppLoader_NAX::ReadRomFSIVFCOffset() const { - return nca_loader->ReadRomFSIVFCOffset(); -} - ResultStatus AppLoader_NAX::ReadProgramId(u64& out_program_id) { return nca_loader->ReadProgramId(out_program_id); } diff --git a/src/core/loader/nax.h b/src/core/loader/nax.h index d7f70db43..81df2bbcd 100644 --- a/src/core/loader/nax.h +++ b/src/core/loader/nax.h @@ -39,7 +39,6 @@ public: LoadResult Load(Kernel::KProcess& process, Core::System& system) override; ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; - u64 ReadRomFSIVFCOffset() const override; ResultStatus ReadProgramId(u64& out_program_id) override; ResultStatus ReadBanner(std::vector<u8>& buffer) override; diff --git a/src/core/loader/nca.cpp b/src/core/loader/nca.cpp index 513af194d..09d40e695 100644 --- a/src/core/loader/nca.cpp +++ b/src/core/loader/nca.cpp @@ -5,6 +5,8 @@ #include "core/core.h" #include "core/file_sys/content_archive.h" +#include "core/file_sys/nca_metadata.h" +#include "core/file_sys/registered_cache.h" #include "core/file_sys/romfs_factory.h" #include "core/hle/kernel/k_process.h" #include "core/hle/service/filesystem/filesystem.h" @@ -43,9 +45,23 @@ AppLoader_NCA::LoadResult AppLoader_NCA::Load(Kernel::KProcess& process, Core::S return {ResultStatus::ErrorNCANotProgram, {}}; } - const auto exefs = nca->GetExeFS(); + auto exefs = nca->GetExeFS(); if (exefs == nullptr) { - return {ResultStatus::ErrorNoExeFS, {}}; + LOG_INFO(Loader, "No ExeFS found in NCA, looking for ExeFS from update"); + + // This NCA may be a sparse base of an installed title. + // Try to fetch the ExeFS from the installed update. + const auto& installed = system.GetContentProvider(); + const auto update_nca = installed.GetEntry(FileSys::GetUpdateTitleID(nca->GetTitleId()), + FileSys::ContentRecordType::Program); + + if (update_nca) { + exefs = update_nca->GetExeFS(); + } + + if (exefs == nullptr) { + return {ResultStatus::ErrorNoExeFS, {}}; + } } directory_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>(exefs, true); @@ -77,14 +93,6 @@ ResultStatus AppLoader_NCA::ReadRomFS(FileSys::VirtualFile& dir) { return ResultStatus::Success; } -u64 AppLoader_NCA::ReadRomFSIVFCOffset() const { - if (nca == nullptr) { - return 0; - } - - return nca->GetBaseIVFCOffset(); -} - ResultStatus AppLoader_NCA::ReadProgramId(u64& out_program_id) { if (nca == nullptr || nca->GetStatus() != ResultStatus::Success) { return ResultStatus::ErrorNotInitialized; diff --git a/src/core/loader/nca.h b/src/core/loader/nca.h index d22d9146e..cf356ce63 100644 --- a/src/core/loader/nca.h +++ b/src/core/loader/nca.h @@ -40,7 +40,6 @@ public: LoadResult Load(Kernel::KProcess& process, Core::System& system) override; ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; - u64 ReadRomFSIVFCOffset() const override; ResultStatus ReadProgramId(u64& out_program_id) override; ResultStatus ReadBanner(std::vector<u8>& buffer) override; diff --git a/src/core/loader/nsp.cpp b/src/core/loader/nsp.cpp index 80663e0e0..f9b2549a3 100644 --- a/src/core/loader/nsp.cpp +++ b/src/core/loader/nsp.cpp @@ -121,10 +121,6 @@ ResultStatus AppLoader_NSP::ReadRomFS(FileSys::VirtualFile& out_file) { return secondary_loader->ReadRomFS(out_file); } -u64 AppLoader_NSP::ReadRomFSIVFCOffset() const { - return secondary_loader->ReadRomFSIVFCOffset(); -} - ResultStatus AppLoader_NSP::ReadUpdateRaw(FileSys::VirtualFile& out_file) { if (nsp->IsExtractedType()) { return ResultStatus::ErrorNoPackedUpdate; diff --git a/src/core/loader/nsp.h b/src/core/loader/nsp.h index 003cc345c..79df4586a 100644 --- a/src/core/loader/nsp.h +++ b/src/core/loader/nsp.h @@ -46,7 +46,6 @@ public: LoadResult Load(Kernel::KProcess& process, Core::System& system) override; ResultStatus ReadRomFS(FileSys::VirtualFile& out_file) override; - u64 ReadRomFSIVFCOffset() const override; ResultStatus ReadUpdateRaw(FileSys::VirtualFile& out_file) override; ResultStatus ReadProgramId(u64& out_program_id) override; ResultStatus ReadProgramIds(std::vector<u64>& out_program_ids) override; diff --git a/src/core/loader/xci.cpp b/src/core/loader/xci.cpp index c7b1b3815..3a76bc788 100644 --- a/src/core/loader/xci.cpp +++ b/src/core/loader/xci.cpp @@ -89,10 +89,6 @@ ResultStatus AppLoader_XCI::ReadRomFS(FileSys::VirtualFile& out_file) { return nca_loader->ReadRomFS(out_file); } -u64 AppLoader_XCI::ReadRomFSIVFCOffset() const { - return nca_loader->ReadRomFSIVFCOffset(); -} - ResultStatus AppLoader_XCI::ReadUpdateRaw(FileSys::VirtualFile& out_file) { u64 program_id{}; nca_loader->ReadProgramId(program_id); diff --git a/src/core/loader/xci.h b/src/core/loader/xci.h index 2affb6c6e..ff05e6f62 100644 --- a/src/core/loader/xci.h +++ b/src/core/loader/xci.h @@ -46,7 +46,6 @@ public: LoadResult Load(Kernel::KProcess& process, Core::System& system) override; ResultStatus ReadRomFS(FileSys::VirtualFile& out_file) override; - u64 ReadRomFSIVFCOffset() const override; ResultStatus ReadUpdateRaw(FileSys::VirtualFile& out_file) override; ResultStatus ReadProgramId(u64& out_program_id) override; ResultStatus ReadProgramIds(std::vector<u64>& out_program_ids) override; diff --git a/src/core/memory.h b/src/core/memory.h index 2eb61ffd3..13047a545 100644 --- a/src/core/memory.h +++ b/src/core/memory.h @@ -509,9 +509,9 @@ class GuestMemory { public: GuestMemory() = delete; - explicit GuestMemory(M& memory_, u64 addr_, std::size_t size_, + explicit GuestMemory(M& memory, u64 addr, std::size_t size, Common::ScratchBuffer<T>* backup = nullptr) - : memory{memory_}, addr{addr_}, size{size_} { + : m_memory{memory}, m_addr{addr}, m_size{size} { static_assert(FLAGS & GuestMemoryFlags::Read || FLAGS & GuestMemoryFlags::Write); if constexpr (FLAGS & GuestMemoryFlags::Read) { Read(addr, size, backup); @@ -521,89 +521,97 @@ public: ~GuestMemory() = default; T* data() noexcept { - return data_span.data(); + return m_data_span.data(); } const T* data() const noexcept { - return data_span.data(); + return m_data_span.data(); + } + + size_t size() const noexcept { + return m_size; + } + + size_t size_bytes() const noexcept { + return this->size() * sizeof(T); } [[nodiscard]] T* begin() noexcept { - return data(); + return this->data(); } [[nodiscard]] const T* begin() const noexcept { - return data(); + return this->data(); } [[nodiscard]] T* end() noexcept { - return data() + size; + return this->data() + this->size(); } [[nodiscard]] const T* end() const noexcept { - return data() + size; + return this->data() + this->size(); } T& operator[](size_t index) noexcept { - return data_span[index]; + return m_data_span[index]; } const T& operator[](size_t index) const noexcept { - return data_span[index]; + return m_data_span[index]; } - void SetAddressAndSize(u64 addr_, std::size_t size_) noexcept { - addr = addr_; - size = size_; - addr_changed = true; + void SetAddressAndSize(u64 addr, std::size_t size) noexcept { + m_addr = addr; + m_size = size; + m_addr_changed = true; } - std::span<T> Read(u64 addr_, std::size_t size_, + std::span<T> Read(u64 addr, std::size_t size, Common::ScratchBuffer<T>* backup = nullptr) noexcept { - addr = addr_; - size = size_; - if (size == 0) { - is_data_copy = true; + m_addr = addr; + m_size = size; + if (m_size == 0) { + m_is_data_copy = true; return {}; } - if (TrySetSpan()) { + if (this->TrySetSpan()) { if constexpr (FLAGS & GuestMemoryFlags::Safe) { - memory.FlushRegion(addr, size * sizeof(T)); + m_memory.FlushRegion(m_addr, this->size_bytes()); } } else { if (backup) { - backup->resize_destructive(size); - data_span = *backup; + backup->resize_destructive(this->size()); + m_data_span = *backup; } else { - data_copy.resize(size); - data_span = std::span(data_copy); + m_data_copy.resize(this->size()); + m_data_span = std::span(m_data_copy); } - is_data_copy = true; - span_valid = true; + m_is_data_copy = true; + m_span_valid = true; if constexpr (FLAGS & GuestMemoryFlags::Safe) { - memory.ReadBlock(addr, data_span.data(), size * sizeof(T)); + m_memory.ReadBlock(m_addr, this->data(), this->size_bytes()); } else { - memory.ReadBlockUnsafe(addr, data_span.data(), size * sizeof(T)); + m_memory.ReadBlockUnsafe(m_addr, this->data(), this->size_bytes()); } } - return data_span; + return m_data_span; } void Write(std::span<T> write_data) noexcept { if constexpr (FLAGS & GuestMemoryFlags::Cached) { - memory.WriteBlockCached(addr, write_data.data(), size * sizeof(T)); + m_memory.WriteBlockCached(m_addr, write_data.data(), this->size_bytes()); } else if constexpr (FLAGS & GuestMemoryFlags::Safe) { - memory.WriteBlock(addr, write_data.data(), size * sizeof(T)); + m_memory.WriteBlock(m_addr, write_data.data(), this->size_bytes()); } else { - memory.WriteBlockUnsafe(addr, write_data.data(), size * sizeof(T)); + m_memory.WriteBlockUnsafe(m_addr, write_data.data(), this->size_bytes()); } } bool TrySetSpan() noexcept { - if (u8* ptr = memory.GetSpan(addr, size * sizeof(T)); ptr) { - data_span = {reinterpret_cast<T*>(ptr), size}; - span_valid = true; + if (u8* ptr = m_memory.GetSpan(m_addr, this->size_bytes()); ptr) { + m_data_span = {reinterpret_cast<T*>(ptr), this->size()}; + m_span_valid = true; return true; } return false; @@ -611,36 +619,36 @@ public: protected: bool IsDataCopy() const noexcept { - return is_data_copy; + return m_is_data_copy; } bool AddressChanged() const noexcept { - return addr_changed; + return m_addr_changed; } - M& memory; - u64 addr; - size_t size; - std::span<T> data_span{}; - std::vector<T> data_copy; - bool span_valid{false}; - bool is_data_copy{false}; - bool addr_changed{false}; + M& m_memory; + u64 m_addr{}; + size_t m_size{}; + std::span<T> m_data_span{}; + std::vector<T> m_data_copy{}; + bool m_span_valid{false}; + bool m_is_data_copy{false}; + bool m_addr_changed{false}; }; template <typename M, typename T, GuestMemoryFlags FLAGS> class GuestMemoryScoped : public GuestMemory<M, T, FLAGS> { public: GuestMemoryScoped() = delete; - explicit GuestMemoryScoped(M& memory_, u64 addr_, std::size_t size_, + explicit GuestMemoryScoped(M& memory, u64 addr, std::size_t size, Common::ScratchBuffer<T>* backup = nullptr) - : GuestMemory<M, T, FLAGS>(memory_, addr_, size_, backup) { + : GuestMemory<M, T, FLAGS>(memory, addr, size, backup) { if constexpr (!(FLAGS & GuestMemoryFlags::Read)) { if (!this->TrySetSpan()) { if (backup) { - this->data_span = *backup; - this->span_valid = true; - this->is_data_copy = true; + this->m_data_span = *backup; + this->m_span_valid = true; + this->m_is_data_copy = true; } } } @@ -648,24 +656,21 @@ public: ~GuestMemoryScoped() { if constexpr (FLAGS & GuestMemoryFlags::Write) { - if (this->size == 0) [[unlikely]] { + if (this->size() == 0) [[unlikely]] { return; } if (this->AddressChanged() || this->IsDataCopy()) { - ASSERT(this->span_valid); + ASSERT(this->m_span_valid); if constexpr (FLAGS & GuestMemoryFlags::Cached) { - this->memory.WriteBlockCached(this->addr, this->data_span.data(), - this->size * sizeof(T)); + this->m_memory.WriteBlockCached(this->m_addr, this->data(), this->size_bytes()); } else if constexpr (FLAGS & GuestMemoryFlags::Safe) { - this->memory.WriteBlock(this->addr, this->data_span.data(), - this->size * sizeof(T)); + this->m_memory.WriteBlock(this->m_addr, this->data(), this->size_bytes()); } else { - this->memory.WriteBlockUnsafe(this->addr, this->data_span.data(), - this->size * sizeof(T)); + this->m_memory.WriteBlockUnsafe(this->m_addr, this->data(), this->size_bytes()); } } else if constexpr (FLAGS & GuestMemoryFlags::Safe) { - this->memory.InvalidateRegion(this->addr, this->size * sizeof(T)); + this->m_memory.InvalidateRegion(this->m_addr, this->size_bytes()); } } } diff --git a/src/video_core/engines/maxwell_dma.cpp b/src/video_core/engines/maxwell_dma.cpp index cd8e24b0b..da8eab7ee 100644 --- a/src/video_core/engines/maxwell_dma.cpp +++ b/src/video_core/engines/maxwell_dma.cpp @@ -5,6 +5,7 @@ #include "common/assert.h" #include "common/logging/log.h" #include "common/microprofile.h" +#include "common/polyfill_ranges.h" #include "common/settings.h" #include "core/core.h" #include "core/memory.h" diff --git a/src/video_core/host_shaders/CMakeLists.txt b/src/video_core/host_shaders/CMakeLists.txt index e61d9af80..c4d459077 100644 --- a/src/video_core/host_shaders/CMakeLists.txt +++ b/src/video_core/host_shaders/CMakeLists.txt @@ -50,6 +50,7 @@ set(SHADER_FILES vulkan_blit_depth_stencil.frag vulkan_color_clear.frag vulkan_color_clear.vert + vulkan_depthstencil_clear.frag vulkan_fidelityfx_fsr_easu_fp16.comp vulkan_fidelityfx_fsr_easu_fp32.comp vulkan_fidelityfx_fsr_rcas_fp16.comp diff --git a/src/video_core/host_shaders/vulkan_depthstencil_clear.frag b/src/video_core/host_shaders/vulkan_depthstencil_clear.frag new file mode 100644 index 000000000..1ac177c7e --- /dev/null +++ b/src/video_core/host_shaders/vulkan_depthstencil_clear.frag @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#version 460 core + +layout (push_constant) uniform PushConstants { + vec4 clear_depth; +}; + +void main() { + gl_FragDepth = clear_depth.x; +} diff --git a/src/video_core/renderer_opengl/gl_device.cpp b/src/video_core/renderer_opengl/gl_device.cpp index ee140c9c2..94258ccd0 100644 --- a/src/video_core/renderer_opengl/gl_device.cpp +++ b/src/video_core/renderer_opengl/gl_device.cpp @@ -106,6 +106,43 @@ bool IsASTCSupported() { return true; } +static bool HasSlowSoftwareAstc(std::string_view vendor_name, std::string_view renderer) { +// ifdef for Unix reduces string comparisons for non-Windows drivers, and Intel +#ifdef YUZU_UNIX + // Sorted vaguely by how likely a vendor is to appear + if (vendor_name == "AMD") { + // RadeonSI + return true; + } + if (vendor_name == "Intel") { + // Must be inside YUZU_UNIX ifdef as the Windows driver uses the same vendor string + // iris, crocus + const bool is_intel_dg = (renderer.find("DG") != std::string_view::npos); + return is_intel_dg; + } + if (vendor_name == "nouveau") { + return true; + } + if (vendor_name == "X.Org") { + // R600 + return true; + } +#endif + if (vendor_name == "Collabora Ltd") { + // Zink + return true; + } + if (vendor_name == "Microsoft Corporation") { + // d3d12 + return true; + } + if (vendor_name == "Mesa/X.org") { + // llvmpipe, softpipe, virgl + return true; + } + return false; +} + [[nodiscard]] bool IsDebugToolAttached(std::span<const std::string_view> extensions) { const bool nsight = std::getenv("NVTX_INJECTION64_PATH") || std::getenv("NSIGHT_LAUNCHED"); return nsight || HasExtension(extensions, "GL_EXT_debug_tool") || @@ -120,12 +157,16 @@ Device::Device(Core::Frontend::EmuWindow& emu_window) { } vendor_name = reinterpret_cast<const char*>(glGetString(GL_VENDOR)); const std::string_view version = reinterpret_cast<const char*>(glGetString(GL_VERSION)); + const std::string_view renderer = reinterpret_cast<const char*>(glGetString(GL_RENDERER)); const std::vector extensions = GetExtensions(); const bool is_nvidia = vendor_name == "NVIDIA Corporation"; const bool is_amd = vendor_name == "ATI Technologies Inc."; const bool is_intel = vendor_name == "Intel"; + const bool has_slow_software_astc = + !is_nvidia && !is_amd && HasSlowSoftwareAstc(vendor_name, renderer); + #ifdef __unix__ constexpr bool is_linux = true; #else @@ -152,7 +193,7 @@ Device::Device(Core::Frontend::EmuWindow& emu_window) { has_vertex_viewport_layer = GLAD_GL_ARB_shader_viewport_layer_array; has_image_load_formatted = HasExtension(extensions, "GL_EXT_shader_image_load_formatted"); has_texture_shadow_lod = HasExtension(extensions, "GL_EXT_texture_shadow_lod"); - has_astc = IsASTCSupported(); + has_astc = !has_slow_software_astc && IsASTCSupported(); has_variable_aoffi = TestVariableAoffi(); has_component_indexing_bug = is_amd; has_precise_bug = TestPreciseBug(); diff --git a/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp b/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp index f822fa856..44a771d65 100644 --- a/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp +++ b/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp @@ -220,7 +220,8 @@ GraphicsPipeline::GraphicsPipeline(const Device& device, TextureCache& texture_c ASSERT(num_textures <= MAX_TEXTURES); ASSERT(num_images <= MAX_IMAGES); - const bool assembly_shaders{assembly_programs[0].handle != 0}; + const auto backend = device.GetShaderBackend(); + const bool assembly_shaders{backend == Settings::ShaderBackend::Glasm}; use_storage_buffers = !assembly_shaders || num_storage_buffers <= device.GetMaxGLASMStorageBufferBlocks(); writes_global_memory &= !use_storage_buffers; @@ -230,7 +231,6 @@ GraphicsPipeline::GraphicsPipeline(const Device& device, TextureCache& texture_c GenerateTransformFeedbackState(); } const bool in_parallel = thread_worker != nullptr; - const auto backend = device.GetShaderBackend(); auto func{[this, sources_ = std::move(sources), sources_spirv_ = std::move(sources_spirv), shader_notify, backend, in_parallel, force_context_flush](ShaderContext::Context*) mutable { @@ -559,15 +559,13 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) { } void GraphicsPipeline::ConfigureTransformFeedbackImpl() const { - glTransformFeedbackStreamAttribsNV(num_xfb_attribs, xfb_attribs.data(), num_xfb_strides, - xfb_streams.data(), GL_INTERLEAVED_ATTRIBS); + glTransformFeedbackAttribsNV(num_xfb_attribs, xfb_attribs.data(), GL_SEPARATE_ATTRIBS); } void GraphicsPipeline::GenerateTransformFeedbackState() { // TODO(Rodrigo): Inject SKIP_COMPONENTS*_NV when required. An unimplemented message will signal // when this is required. GLint* cursor{xfb_attribs.data()}; - GLint* current_stream{xfb_streams.data()}; for (size_t feedback = 0; feedback < Maxwell::NumTransformFeedbackBuffers; ++feedback) { const auto& layout = key.xfb_state.layouts[feedback]; @@ -575,15 +573,6 @@ void GraphicsPipeline::GenerateTransformFeedbackState() { if (layout.varying_count == 0) { continue; } - *current_stream = static_cast<GLint>(feedback); - if (current_stream != xfb_streams.data()) { - // When stepping one stream, push the expected token - cursor[0] = GL_NEXT_BUFFER_NV; - cursor[1] = 0; - cursor[2] = 0; - cursor += XFB_ENTRY_STRIDE; - } - ++current_stream; const auto& locations = key.xfb_state.varyings[feedback]; std::optional<u32> current_index; @@ -619,7 +608,6 @@ void GraphicsPipeline::GenerateTransformFeedbackState() { } } num_xfb_attribs = static_cast<GLsizei>((cursor - xfb_attribs.data()) / XFB_ENTRY_STRIDE); - num_xfb_strides = static_cast<GLsizei>(current_stream - xfb_streams.data()); } void GraphicsPipeline::WaitForBuild() { diff --git a/src/video_core/renderer_opengl/gl_graphics_pipeline.h b/src/video_core/renderer_opengl/gl_graphics_pipeline.h index 7b3d7eae8..74fc9cc3d 100644 --- a/src/video_core/renderer_opengl/gl_graphics_pipeline.h +++ b/src/video_core/renderer_opengl/gl_graphics_pipeline.h @@ -154,9 +154,7 @@ private: static constexpr std::size_t XFB_ENTRY_STRIDE = 3; GLsizei num_xfb_attribs{}; - GLsizei num_xfb_strides{}; std::array<GLint, 128 * XFB_ENTRY_STRIDE * Maxwell::NumTransformFeedbackBuffers> xfb_attribs{}; - std::array<GLint, Maxwell::NumTransformFeedbackBuffers> xfb_streams{}; std::mutex built_mutex; std::condition_variable built_condvar; diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index aadd6967c..1ba31be88 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -1335,7 +1335,8 @@ bool AccelerateDMA::DmaBufferImageCopy(const Tegra::DMA::ImageCopy& copy_info, } const u32 buffer_size = static_cast<u32>(buffer_operand.pitch * buffer_operand.height); static constexpr auto sync_info = VideoCommon::ObtainBufferSynchronize::FullSynchronize; - const auto post_op = VideoCommon::ObtainBufferOperation::DoNothing; + const auto post_op = IS_IMAGE_UPLOAD ? VideoCommon::ObtainBufferOperation::DoNothing + : VideoCommon::ObtainBufferOperation::MarkAsWritten; const auto [buffer, offset] = buffer_cache.ObtainBuffer(buffer_operand.address, buffer_size, sync_info, post_op); @@ -1344,8 +1345,12 @@ bool AccelerateDMA::DmaBufferImageCopy(const Tegra::DMA::ImageCopy& copy_info, const std::span copy_span{©, 1}; if constexpr (IS_IMAGE_UPLOAD) { + texture_cache.PrepareImage(image_id, true, false); image->UploadMemory(buffer->Handle(), offset, copy_span); } else { + if (offset % BytesPerBlock(image->info.format)) { + return false; + } texture_cache.DownloadImageIntoBuffer(image, buffer->Handle(), offset, copy_span, buffer_operand.address, buffer_size); } diff --git a/src/video_core/renderer_vulkan/blit_image.cpp b/src/video_core/renderer_vulkan/blit_image.cpp index f74ae972e..1032c9d12 100644 --- a/src/video_core/renderer_vulkan/blit_image.cpp +++ b/src/video_core/renderer_vulkan/blit_image.cpp @@ -16,6 +16,7 @@ #include "video_core/host_shaders/vulkan_blit_depth_stencil_frag_spv.h" #include "video_core/host_shaders/vulkan_color_clear_frag_spv.h" #include "video_core/host_shaders/vulkan_color_clear_vert_spv.h" +#include "video_core/host_shaders/vulkan_depthstencil_clear_frag_spv.h" #include "video_core/renderer_vulkan/blit_image.h" #include "video_core/renderer_vulkan/maxwell_to_vk.h" #include "video_core/renderer_vulkan/vk_scheduler.h" @@ -428,6 +429,7 @@ BlitImageHelper::BlitImageHelper(const Device& device_, Scheduler& scheduler_, blit_depth_stencil_frag(BuildShader(device, VULKAN_BLIT_DEPTH_STENCIL_FRAG_SPV)), clear_color_vert(BuildShader(device, VULKAN_COLOR_CLEAR_VERT_SPV)), clear_color_frag(BuildShader(device, VULKAN_COLOR_CLEAR_FRAG_SPV)), + clear_stencil_frag(BuildShader(device, VULKAN_DEPTHSTENCIL_CLEAR_FRAG_SPV)), convert_depth_to_float_frag(BuildShader(device, CONVERT_DEPTH_TO_FLOAT_FRAG_SPV)), convert_float_to_depth_frag(BuildShader(device, CONVERT_FLOAT_TO_DEPTH_FRAG_SPV)), convert_abgr8_to_d24s8_frag(BuildShader(device, CONVERT_ABGR8_TO_D24S8_FRAG_SPV)), @@ -593,6 +595,28 @@ void BlitImageHelper::ClearColor(const Framebuffer* dst_framebuffer, u8 color_ma scheduler.InvalidateState(); } +void BlitImageHelper::ClearDepthStencil(const Framebuffer* dst_framebuffer, bool depth_clear, + f32 clear_depth, u8 stencil_mask, u32 stencil_ref, + u32 stencil_compare_mask, const Region2D& dst_region) { + const BlitDepthStencilPipelineKey key{ + .renderpass = dst_framebuffer->RenderPass(), + .depth_clear = depth_clear, + .stencil_mask = stencil_mask, + .stencil_compare_mask = stencil_compare_mask, + .stencil_ref = stencil_ref, + }; + const VkPipeline pipeline = FindOrEmplaceClearStencilPipeline(key); + const VkPipelineLayout layout = *clear_color_pipeline_layout; + scheduler.RequestRenderpass(dst_framebuffer); + scheduler.Record([pipeline, layout, clear_depth, dst_region](vk::CommandBuffer cmdbuf) { + cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); + BindBlitState(cmdbuf, dst_region); + cmdbuf.PushConstants(layout, VK_SHADER_STAGE_FRAGMENT_BIT, clear_depth); + cmdbuf.Draw(3, 1, 0, 0); + }); + scheduler.InvalidateState(); +} + void BlitImageHelper::Convert(VkPipeline pipeline, const Framebuffer* dst_framebuffer, const ImageView& src_image_view) { const VkPipelineLayout layout = *one_texture_pipeline_layout; @@ -820,6 +844,61 @@ VkPipeline BlitImageHelper::FindOrEmplaceClearColorPipeline(const BlitImagePipel return *clear_color_pipelines.back(); } +VkPipeline BlitImageHelper::FindOrEmplaceClearStencilPipeline( + const BlitDepthStencilPipelineKey& key) { + const auto it = std::ranges::find(clear_stencil_keys, key); + if (it != clear_stencil_keys.end()) { + return *clear_stencil_pipelines[std::distance(clear_stencil_keys.begin(), it)]; + } + clear_stencil_keys.push_back(key); + const std::array stages = MakeStages(*clear_color_vert, *clear_stencil_frag); + const auto stencil = VkStencilOpState{ + .failOp = VK_STENCIL_OP_KEEP, + .passOp = VK_STENCIL_OP_REPLACE, + .depthFailOp = VK_STENCIL_OP_KEEP, + .compareOp = VK_COMPARE_OP_ALWAYS, + .compareMask = key.stencil_compare_mask, + .writeMask = key.stencil_mask, + .reference = key.stencil_ref, + }; + const VkPipelineDepthStencilStateCreateInfo depth_stencil_ci{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .depthTestEnable = VK_FALSE, + .depthWriteEnable = key.depth_clear, + .depthCompareOp = VK_COMPARE_OP_ALWAYS, + .depthBoundsTestEnable = VK_FALSE, + .stencilTestEnable = VK_TRUE, + .front = stencil, + .back = stencil, + .minDepthBounds = 0.0f, + .maxDepthBounds = 0.0f, + }; + clear_stencil_pipelines.push_back(device.GetLogical().CreateGraphicsPipeline({ + .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .stageCount = static_cast<u32>(stages.size()), + .pStages = stages.data(), + .pVertexInputState = &PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, + .pInputAssemblyState = &PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, + .pTessellationState = nullptr, + .pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO, + .pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO, + .pMultisampleState = &PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, + .pDepthStencilState = &depth_stencil_ci, + .pColorBlendState = &PIPELINE_COLOR_BLEND_STATE_GENERIC_CREATE_INFO, + .pDynamicState = &PIPELINE_DYNAMIC_STATE_CREATE_INFO, + .layout = *clear_color_pipeline_layout, + .renderPass = key.renderpass, + .subpass = 0, + .basePipelineHandle = VK_NULL_HANDLE, + .basePipelineIndex = 0, + })); + return *clear_stencil_pipelines.back(); +} + void BlitImageHelper::ConvertPipeline(vk::Pipeline& pipeline, VkRenderPass renderpass, bool is_target_depth) { if (pipeline) { diff --git a/src/video_core/renderer_vulkan/blit_image.h b/src/video_core/renderer_vulkan/blit_image.h index 2976a7d91..dcfe217aa 100644 --- a/src/video_core/renderer_vulkan/blit_image.h +++ b/src/video_core/renderer_vulkan/blit_image.h @@ -27,6 +27,16 @@ struct BlitImagePipelineKey { Tegra::Engines::Fermi2D::Operation operation; }; +struct BlitDepthStencilPipelineKey { + constexpr auto operator<=>(const BlitDepthStencilPipelineKey&) const noexcept = default; + + VkRenderPass renderpass; + bool depth_clear; + u8 stencil_mask; + u32 stencil_compare_mask; + u32 stencil_ref; +}; + class BlitImageHelper { public: explicit BlitImageHelper(const Device& device, Scheduler& scheduler, @@ -64,6 +74,10 @@ public: void ClearColor(const Framebuffer* dst_framebuffer, u8 color_mask, const std::array<f32, 4>& clear_color, const Region2D& dst_region); + void ClearDepthStencil(const Framebuffer* dst_framebuffer, bool depth_clear, f32 clear_depth, + u8 stencil_mask, u32 stencil_ref, u32 stencil_compare_mask, + const Region2D& dst_region); + private: void Convert(VkPipeline pipeline, const Framebuffer* dst_framebuffer, const ImageView& src_image_view); @@ -76,6 +90,8 @@ private: [[nodiscard]] VkPipeline FindOrEmplaceDepthStencilPipeline(const BlitImagePipelineKey& key); [[nodiscard]] VkPipeline FindOrEmplaceClearColorPipeline(const BlitImagePipelineKey& key); + [[nodiscard]] VkPipeline FindOrEmplaceClearStencilPipeline( + const BlitDepthStencilPipelineKey& key); void ConvertPipeline(vk::Pipeline& pipeline, VkRenderPass renderpass, bool is_target_depth); @@ -108,6 +124,7 @@ private: vk::ShaderModule blit_depth_stencil_frag; vk::ShaderModule clear_color_vert; vk::ShaderModule clear_color_frag; + vk::ShaderModule clear_stencil_frag; vk::ShaderModule convert_depth_to_float_frag; vk::ShaderModule convert_float_to_depth_frag; vk::ShaderModule convert_abgr8_to_d24s8_frag; @@ -122,6 +139,8 @@ private: std::vector<vk::Pipeline> blit_depth_stencil_pipelines; std::vector<BlitImagePipelineKey> clear_color_keys; std::vector<vk::Pipeline> clear_color_pipelines; + std::vector<BlitDepthStencilPipelineKey> clear_stencil_keys; + std::vector<vk::Pipeline> clear_stencil_pipelines; vk::Pipeline convert_d32_to_r32_pipeline; vk::Pipeline convert_r32_to_d32_pipeline; vk::Pipeline convert_d16_to_r16_pipeline; diff --git a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp index a8540339d..35bf80ea3 100644 --- a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp +++ b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp @@ -126,7 +126,7 @@ struct FormatTuple { {VK_FORMAT_A1R5G5B5_UNORM_PACK16, Attachable}, // A1R5G5B5_UNORM {VK_FORMAT_A2B10G10R10_UNORM_PACK32, Attachable | Storage}, // A2B10G10R10_UNORM {VK_FORMAT_A2B10G10R10_UINT_PACK32, Attachable | Storage}, // A2B10G10R10_UINT - {VK_FORMAT_A2R10G10B10_UNORM_PACK32, Attachable | Storage}, // A2R10G10B10_UNORM + {VK_FORMAT_A2R10G10B10_UNORM_PACK32, Attachable}, // A2R10G10B10_UNORM {VK_FORMAT_A1R5G5B5_UNORM_PACK16, Attachable}, // A1B5G5R5_UNORM (flipped with swizzle) {VK_FORMAT_R5G5B5A1_UNORM_PACK16}, // A5B5G5R1_UNORM (specially swizzled) {VK_FORMAT_R8_UNORM, Attachable | Storage}, // R8_UNORM diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index aa59889bd..032f694bc 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -428,15 +428,27 @@ void RasterizerVulkan::Clear(u32 layer_count) { if (aspect_flags == 0) { return; } - scheduler.Record([clear_depth = regs.clear_depth, clear_stencil = regs.clear_stencil, - clear_rect, aspect_flags](vk::CommandBuffer cmdbuf) { - VkClearAttachment attachment; - attachment.aspectMask = aspect_flags; - attachment.colorAttachment = 0; - attachment.clearValue.depthStencil.depth = clear_depth; - attachment.clearValue.depthStencil.stencil = clear_stencil; - cmdbuf.ClearAttachments(attachment, clear_rect); - }); + + if (use_stencil && regs.stencil_front_mask != 0xFF && regs.stencil_front_mask != 0) { + Region2D dst_region = { + Offset2D{.x = clear_rect.rect.offset.x, .y = clear_rect.rect.offset.y}, + Offset2D{.x = clear_rect.rect.offset.x + static_cast<s32>(clear_rect.rect.extent.width), + .y = clear_rect.rect.offset.y + + static_cast<s32>(clear_rect.rect.extent.height)}}; + blit_image.ClearDepthStencil(framebuffer, use_depth, regs.clear_depth, + static_cast<u8>(regs.stencil_front_mask), regs.clear_stencil, + regs.stencil_front_func_mask, dst_region); + } else { + scheduler.Record([clear_depth = regs.clear_depth, clear_stencil = regs.clear_stencil, + clear_rect, aspect_flags](vk::CommandBuffer cmdbuf) { + VkClearAttachment attachment; + attachment.aspectMask = aspect_flags; + attachment.colorAttachment = 0; + attachment.clearValue.depthStencil.depth = clear_depth; + attachment.clearValue.depthStencil.stencil = clear_stencil; + cmdbuf.ClearAttachments(attachment, clear_rect); + }); + } } void RasterizerVulkan::DispatchCompute() { @@ -830,7 +842,8 @@ bool AccelerateDMA::DmaBufferImageCopy(const Tegra::DMA::ImageCopy& copy_info, } const u32 buffer_size = static_cast<u32>(buffer_operand.pitch * buffer_operand.height); static constexpr auto sync_info = VideoCommon::ObtainBufferSynchronize::FullSynchronize; - const auto post_op = VideoCommon::ObtainBufferOperation::DoNothing; + const auto post_op = IS_IMAGE_UPLOAD ? VideoCommon::ObtainBufferOperation::DoNothing + : VideoCommon::ObtainBufferOperation::MarkAsWritten; const auto [buffer, offset] = buffer_cache.ObtainBuffer(buffer_operand.address, buffer_size, sync_info, post_op); @@ -839,8 +852,12 @@ bool AccelerateDMA::DmaBufferImageCopy(const Tegra::DMA::ImageCopy& copy_info, const std::span copy_span{©, 1}; if constexpr (IS_IMAGE_UPLOAD) { + texture_cache.PrepareImage(image_id, true, false); image->UploadMemory(buffer->Handle(), offset, copy_span); } else { + if (offset % BytesPerBlock(image->info.format)) { + return false; + } texture_cache.DownloadImageIntoBuffer(image, buffer->Handle(), offset, copy_span, buffer_operand.address, buffer_size); } diff --git a/src/video_core/texture_cache/texture_cache_base.h b/src/video_core/texture_cache/texture_cache_base.h index e9ec91265..a40825c9f 100644 --- a/src/video_core/texture_cache/texture_cache_base.h +++ b/src/video_core/texture_cache/texture_cache_base.h @@ -243,6 +243,9 @@ public: /// Create channel state. void CreateChannel(Tegra::Control::ChannelState& channel) final override; + /// Prepare an image to be used + void PrepareImage(ImageId image_id, bool is_modification, bool invalidate); + std::recursive_mutex mutex; private: @@ -387,9 +390,6 @@ private: /// Synchronize image aliases, copying data if needed void SynchronizeAliases(ImageId image_id); - /// Prepare an image to be used - void PrepareImage(ImageId image_id, bool is_modification, bool invalidate); - /// Prepare an image view to be used void PrepareImageView(ImageViewId image_view_id, bool is_modification, bool invalidate); diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index 710929ac5..617417040 100644 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp @@ -71,6 +71,11 @@ constexpr std::array R8G8B8_SSCALED{ VK_FORMAT_UNDEFINED, }; +constexpr std::array VK_FORMAT_R32G32B32_SFLOAT{ + VK_FORMAT_R32G32B32A32_SFLOAT, + VK_FORMAT_UNDEFINED, +}; + } // namespace Alternatives enum class NvidiaArchitecture { @@ -103,6 +108,8 @@ constexpr const VkFormat* GetFormatAlternatives(VkFormat format) { return Alternatives::R16G16B16_SSCALED.data(); case VK_FORMAT_R8G8B8_SSCALED: return Alternatives::R8G8B8_SSCALED.data(); + case VK_FORMAT_R32G32B32_SFLOAT: + return Alternatives::VK_FORMAT_R32G32B32_SFLOAT.data(); default: return nullptr; } @@ -130,6 +137,7 @@ std::unordered_map<VkFormat, VkFormatProperties> GetFormatProperties(vk::Physica VK_FORMAT_A2B10G10R10_UINT_PACK32, VK_FORMAT_A2B10G10R10_UNORM_PACK32, VK_FORMAT_A2B10G10R10_USCALED_PACK32, + VK_FORMAT_A2R10G10B10_UNORM_PACK32, VK_FORMAT_A8B8G8R8_SINT_PACK32, VK_FORMAT_A8B8G8R8_SNORM_PACK32, VK_FORMAT_A8B8G8R8_SRGB_PACK32, @@ -326,6 +334,43 @@ std::vector<const char*> ExtensionListForVulkan( } // Anonymous namespace +void Device::RemoveExtension(bool& extension, const std::string& extension_name) { + extension = false; + loaded_extensions.erase(extension_name); +} + +void Device::RemoveExtensionIfUnsuitable(bool is_suitable, const std::string& extension_name) { + if (loaded_extensions.contains(extension_name) && !is_suitable) { + LOG_WARNING(Render_Vulkan, "Removing unsuitable extension {}", extension_name); + this->RemoveExtension(is_suitable, extension_name); + } +} + +template <typename Feature> +void Device::RemoveExtensionFeature(bool& extension, Feature& feature, + const std::string& extension_name) { + // Unload extension. + this->RemoveExtension(extension, extension_name); + + // Save sType and pNext for chain. + VkStructureType sType = feature.sType; + void* pNext = feature.pNext; + + // Clear feature struct and restore chain. + feature = {}; + feature.sType = sType; + feature.pNext = pNext; +} + +template <typename Feature> +void Device::RemoveExtensionFeatureIfUnsuitable(bool is_suitable, Feature& feature, + const std::string& extension_name) { + if (loaded_extensions.contains(extension_name) && !is_suitable) { + LOG_WARNING(Render_Vulkan, "Removing features for unsuitable extension {}", extension_name); + this->RemoveExtensionFeature(is_suitable, feature, extension_name); + } +} + Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR surface, const vk::InstanceDispatch& dld_) : instance{instance_}, dld{dld_}, physical{physical_}, @@ -397,21 +442,20 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR if (is_qualcomm || is_turnip) { LOG_WARNING(Render_Vulkan, "Qualcomm and Turnip drivers have broken VK_EXT_custom_border_color"); - extensions.custom_border_color = false; - loaded_extensions.erase(VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME); + RemoveExtensionFeature(extensions.custom_border_color, features.custom_border_color, + VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME); } if (is_qualcomm) { must_emulate_scaled_formats = true; LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken VK_EXT_extended_dynamic_state"); - extensions.extended_dynamic_state = false; - loaded_extensions.erase(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME); + RemoveExtensionFeature(extensions.extended_dynamic_state, features.extended_dynamic_state, + VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME); LOG_WARNING(Render_Vulkan, "Qualcomm drivers have a slow VK_KHR_push_descriptor implementation"); - extensions.push_descriptor = false; - loaded_extensions.erase(VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME); + RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME); #if defined(ANDROID) && defined(ARCHITECTURE_arm64) // Patch the driver to enable BCn textures. @@ -440,15 +484,12 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR must_emulate_scaled_formats = true; LOG_WARNING(Render_Vulkan, "ARM drivers have broken VK_EXT_extended_dynamic_state"); - extensions.extended_dynamic_state = false; - loaded_extensions.erase(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME); + RemoveExtensionFeature(extensions.extended_dynamic_state, features.extended_dynamic_state, + VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME); LOG_WARNING(Render_Vulkan, "ARM drivers have broken VK_EXT_extended_dynamic_state2"); - features.extended_dynamic_state2.extendedDynamicState2 = false; - features.extended_dynamic_state2.extendedDynamicState2LogicOp = false; - features.extended_dynamic_state2.extendedDynamicState2PatchControlPoints = false; - extensions.extended_dynamic_state2 = false; - loaded_extensions.erase(VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME); + RemoveExtensionFeature(extensions.extended_dynamic_state2, features.extended_dynamic_state2, + VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME); } if (is_nvidia) { @@ -464,8 +505,7 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR case NvidiaArchitecture::VoltaOrOlder: if (nv_major_version < 527) { LOG_WARNING(Render_Vulkan, "Volta and older have broken VK_KHR_push_descriptor"); - extensions.push_descriptor = false; - loaded_extensions.erase(VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME); + RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME); } break; } @@ -480,8 +520,9 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR if (version < VK_MAKE_API_VERSION(0, 21, 2, 0)) { LOG_WARNING(Render_Vulkan, "RADV versions older than 21.2 have broken VK_EXT_extended_dynamic_state"); - extensions.extended_dynamic_state = false; - loaded_extensions.erase(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME); + RemoveExtensionFeature(extensions.extended_dynamic_state, + features.extended_dynamic_state, + VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME); } } if (extensions.extended_dynamic_state2 && is_radv) { @@ -490,11 +531,9 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR LOG_WARNING( Render_Vulkan, "RADV versions older than 22.3.1 have broken VK_EXT_extended_dynamic_state2"); - features.extended_dynamic_state2.extendedDynamicState2 = false; - features.extended_dynamic_state2.extendedDynamicState2LogicOp = false; - features.extended_dynamic_state2.extendedDynamicState2PatchControlPoints = false; - extensions.extended_dynamic_state2 = false; - loaded_extensions.erase(VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME); + RemoveExtensionFeature(extensions.extended_dynamic_state2, + features.extended_dynamic_state2, + VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME); } } if (extensions.extended_dynamic_state2 && is_qualcomm) { @@ -504,11 +543,9 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR // Qualcomm Adreno 7xx drivers do not properly support extended_dynamic_state2. LOG_WARNING(Render_Vulkan, "Qualcomm Adreno 7xx drivers have broken VK_EXT_extended_dynamic_state2"); - features.extended_dynamic_state2.extendedDynamicState2 = false; - features.extended_dynamic_state2.extendedDynamicState2LogicOp = false; - features.extended_dynamic_state2.extendedDynamicState2PatchControlPoints = false; - extensions.extended_dynamic_state2 = false; - loaded_extensions.erase(VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME); + RemoveExtensionFeature(extensions.extended_dynamic_state2, + features.extended_dynamic_state2, + VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME); } } if (extensions.extended_dynamic_state3 && is_radv) { @@ -540,9 +577,9 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR if (is_rdna2) { LOG_WARNING(Render_Vulkan, "RADV has broken VK_EXT_vertex_input_dynamic_state on RDNA2 hardware"); - features.vertex_input_dynamic_state.vertexInputDynamicState = false; - extensions.vertex_input_dynamic_state = false; - loaded_extensions.erase(VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME); + RemoveExtensionFeature(extensions.vertex_input_dynamic_state, + features.vertex_input_dynamic_state, + VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME); } } if (extensions.vertex_input_dynamic_state && is_qualcomm) { @@ -553,9 +590,9 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR LOG_WARNING( Render_Vulkan, "Qualcomm Adreno 7xx drivers have broken VK_EXT_vertex_input_dynamic_state"); - features.vertex_input_dynamic_state.vertexInputDynamicState = false; - extensions.vertex_input_dynamic_state = false; - loaded_extensions.erase(VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME); + RemoveExtensionFeature(extensions.vertex_input_dynamic_state, + features.vertex_input_dynamic_state, + VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME); } } @@ -575,8 +612,8 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR if (!features.shader_float16_int8.shaderFloat16) { LOG_WARNING(Render_Vulkan, "AMD GCN4 and earlier have broken VK_EXT_sampler_filter_minmax"); - extensions.sampler_filter_minmax = false; - loaded_extensions.erase(VK_EXT_SAMPLER_FILTER_MINMAX_EXTENSION_NAME); + RemoveExtension(extensions.sampler_filter_minmax, + VK_EXT_SAMPLER_FILTER_MINMAX_EXTENSION_NAME); } } @@ -584,8 +621,9 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR const u32 version = (properties.properties.driverVersion << 3) >> 3; if (version < VK_MAKE_API_VERSION(27, 20, 100, 0)) { LOG_WARNING(Render_Vulkan, "Intel has broken VK_EXT_vertex_input_dynamic_state"); - extensions.vertex_input_dynamic_state = false; - loaded_extensions.erase(VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME); + RemoveExtensionFeature(extensions.vertex_input_dynamic_state, + features.vertex_input_dynamic_state, + VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME); } } if (features.shader_float16_int8.shaderFloat16 && is_intel_windows) { @@ -612,8 +650,7 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR // mesa/mesa/-/commit/ff91c5ca42bc80aa411cb3fd8f550aa6fdd16bdc LOG_WARNING(Render_Vulkan, "ANV drivers 22.3.0 to 23.1.0 have broken VK_KHR_push_descriptor"); - extensions.push_descriptor = false; - loaded_extensions.erase(VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME); + RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME); } } if (is_mvk) { @@ -1007,34 +1044,29 @@ bool Device::GetSuitability(bool requires_swapchain) { return suitable; } -void Device::RemoveExtensionIfUnsuitable(bool is_suitable, const std::string& extension_name) { - if (loaded_extensions.contains(extension_name) && !is_suitable) { - LOG_WARNING(Render_Vulkan, "Removing unsuitable extension {}", extension_name); - loaded_extensions.erase(extension_name); - } -} - void Device::RemoveUnsuitableExtensions() { // VK_EXT_custom_border_color extensions.custom_border_color = features.custom_border_color.customBorderColors && features.custom_border_color.customBorderColorWithoutFormat; - RemoveExtensionIfUnsuitable(extensions.custom_border_color, - VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME); + RemoveExtensionFeatureIfUnsuitable(extensions.custom_border_color, features.custom_border_color, + VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME); // VK_EXT_depth_clip_control extensions.depth_clip_control = features.depth_clip_control.depthClipControl; - RemoveExtensionIfUnsuitable(extensions.depth_clip_control, - VK_EXT_DEPTH_CLIP_CONTROL_EXTENSION_NAME); + RemoveExtensionFeatureIfUnsuitable(extensions.depth_clip_control, features.depth_clip_control, + VK_EXT_DEPTH_CLIP_CONTROL_EXTENSION_NAME); // VK_EXT_extended_dynamic_state extensions.extended_dynamic_state = features.extended_dynamic_state.extendedDynamicState; - RemoveExtensionIfUnsuitable(extensions.extended_dynamic_state, - VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME); + RemoveExtensionFeatureIfUnsuitable(extensions.extended_dynamic_state, + features.extended_dynamic_state, + VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME); // VK_EXT_extended_dynamic_state2 extensions.extended_dynamic_state2 = features.extended_dynamic_state2.extendedDynamicState2; - RemoveExtensionIfUnsuitable(extensions.extended_dynamic_state2, - VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME); + RemoveExtensionFeatureIfUnsuitable(extensions.extended_dynamic_state2, + features.extended_dynamic_state2, + VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME); // VK_EXT_extended_dynamic_state3 dynamic_state3_blending = @@ -1048,35 +1080,38 @@ void Device::RemoveUnsuitableExtensions() { extensions.extended_dynamic_state3 = dynamic_state3_blending || dynamic_state3_enables; dynamic_state3_blending = dynamic_state3_blending && extensions.extended_dynamic_state3; dynamic_state3_enables = dynamic_state3_enables && extensions.extended_dynamic_state3; - RemoveExtensionIfUnsuitable(extensions.extended_dynamic_state3, - VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME); + RemoveExtensionFeatureIfUnsuitable(extensions.extended_dynamic_state3, + features.extended_dynamic_state3, + VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME); // VK_EXT_provoking_vertex extensions.provoking_vertex = features.provoking_vertex.provokingVertexLast && features.provoking_vertex.transformFeedbackPreservesProvokingVertex; - RemoveExtensionIfUnsuitable(extensions.provoking_vertex, - VK_EXT_PROVOKING_VERTEX_EXTENSION_NAME); + RemoveExtensionFeatureIfUnsuitable(extensions.provoking_vertex, features.provoking_vertex, + VK_EXT_PROVOKING_VERTEX_EXTENSION_NAME); // VK_KHR_shader_atomic_int64 extensions.shader_atomic_int64 = features.shader_atomic_int64.shaderBufferInt64Atomics && features.shader_atomic_int64.shaderSharedInt64Atomics; - RemoveExtensionIfUnsuitable(extensions.shader_atomic_int64, - VK_KHR_SHADER_ATOMIC_INT64_EXTENSION_NAME); + RemoveExtensionFeatureIfUnsuitable(extensions.shader_atomic_int64, features.shader_atomic_int64, + VK_KHR_SHADER_ATOMIC_INT64_EXTENSION_NAME); // VK_EXT_shader_demote_to_helper_invocation extensions.shader_demote_to_helper_invocation = features.shader_demote_to_helper_invocation.shaderDemoteToHelperInvocation; - RemoveExtensionIfUnsuitable(extensions.shader_demote_to_helper_invocation, - VK_EXT_SHADER_DEMOTE_TO_HELPER_INVOCATION_EXTENSION_NAME); + RemoveExtensionFeatureIfUnsuitable(extensions.shader_demote_to_helper_invocation, + features.shader_demote_to_helper_invocation, + VK_EXT_SHADER_DEMOTE_TO_HELPER_INVOCATION_EXTENSION_NAME); // VK_EXT_subgroup_size_control extensions.subgroup_size_control = features.subgroup_size_control.subgroupSizeControl && properties.subgroup_size_control.minSubgroupSize <= GuestWarpSize && properties.subgroup_size_control.maxSubgroupSize >= GuestWarpSize; - RemoveExtensionIfUnsuitable(extensions.subgroup_size_control, - VK_EXT_SUBGROUP_SIZE_CONTROL_EXTENSION_NAME); + RemoveExtensionFeatureIfUnsuitable(extensions.subgroup_size_control, + features.subgroup_size_control, + VK_EXT_SUBGROUP_SIZE_CONTROL_EXTENSION_NAME); // VK_EXT_transform_feedback extensions.transform_feedback = @@ -1086,24 +1121,27 @@ void Device::RemoveUnsuitableExtensions() { properties.transform_feedback.maxTransformFeedbackBuffers > 0 && properties.transform_feedback.transformFeedbackQueries && properties.transform_feedback.transformFeedbackDraw; - RemoveExtensionIfUnsuitable(extensions.transform_feedback, - VK_EXT_TRANSFORM_FEEDBACK_EXTENSION_NAME); + RemoveExtensionFeatureIfUnsuitable(extensions.transform_feedback, features.transform_feedback, + VK_EXT_TRANSFORM_FEEDBACK_EXTENSION_NAME); // VK_EXT_vertex_input_dynamic_state extensions.vertex_input_dynamic_state = features.vertex_input_dynamic_state.vertexInputDynamicState; - RemoveExtensionIfUnsuitable(extensions.vertex_input_dynamic_state, - VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME); + RemoveExtensionFeatureIfUnsuitable(extensions.vertex_input_dynamic_state, + features.vertex_input_dynamic_state, + VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME); // VK_KHR_pipeline_executable_properties if (Settings::values.renderer_shader_feedback.GetValue()) { extensions.pipeline_executable_properties = features.pipeline_executable_properties.pipelineExecutableInfo; - RemoveExtensionIfUnsuitable(extensions.pipeline_executable_properties, - VK_KHR_PIPELINE_EXECUTABLE_PROPERTIES_EXTENSION_NAME); + RemoveExtensionFeatureIfUnsuitable(extensions.pipeline_executable_properties, + features.pipeline_executable_properties, + VK_KHR_PIPELINE_EXECUTABLE_PROPERTIES_EXTENSION_NAME); } else { - extensions.pipeline_executable_properties = false; - loaded_extensions.erase(VK_KHR_PIPELINE_EXECUTABLE_PROPERTIES_EXTENSION_NAME); + RemoveExtensionFeature(extensions.pipeline_executable_properties, + features.pipeline_executable_properties, + VK_KHR_PIPELINE_EXECUTABLE_PROPERTIES_EXTENSION_NAME); } // VK_KHR_workgroup_memory_explicit_layout @@ -1113,8 +1151,9 @@ void Device::RemoveUnsuitableExtensions() { features.workgroup_memory_explicit_layout.workgroupMemoryExplicitLayout8BitAccess && features.workgroup_memory_explicit_layout.workgroupMemoryExplicitLayout16BitAccess && features.workgroup_memory_explicit_layout.workgroupMemoryExplicitLayoutScalarBlockLayout; - RemoveExtensionIfUnsuitable(extensions.workgroup_memory_explicit_layout, - VK_KHR_WORKGROUP_MEMORY_EXPLICIT_LAYOUT_EXTENSION_NAME); + RemoveExtensionFeatureIfUnsuitable(extensions.workgroup_memory_explicit_layout, + features.workgroup_memory_explicit_layout, + VK_KHR_WORKGROUP_MEMORY_EXPLICIT_LAYOUT_EXTENSION_NAME); } void Device::SetupFamilies(VkSurfaceKHR surface) { diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h index d8dd41e51..488fdd313 100644 --- a/src/video_core/vulkan_common/vulkan_device.h +++ b/src/video_core/vulkan_common/vulkan_device.h @@ -639,8 +639,17 @@ private: // Remove extensions which have incomplete feature support. void RemoveUnsuitableExtensions(); + + void RemoveExtension(bool& extension, const std::string& extension_name); void RemoveExtensionIfUnsuitable(bool is_suitable, const std::string& extension_name); + template <typename Feature> + void RemoveExtensionFeature(bool& extension, Feature& feature, + const std::string& extension_name); + template <typename Feature> + void RemoveExtensionFeatureIfUnsuitable(bool is_suitable, Feature& feature, + const std::string& extension_name); + /// Sets up queue families. void SetupFamilies(VkSurfaceKHR surface); diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index 465084fea..b5a02700d 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp @@ -214,13 +214,17 @@ void GameList::OnTextChanged(const QString& new_text) { const int children_count = folder->rowCount(); for (int j = 0; j < children_count; ++j) { ++children_total; + const QStandardItem* child = folder->child(j, 0); + + const auto program_id = child->data(GameListItemPath::ProgramIdRole).toULongLong(); + const QString file_path = child->data(GameListItemPath::FullPathRole).toString().toLower(); const QString file_title = child->data(GameListItemPath::TitleRole).toString().toLower(); const QString file_program_id = - child->data(GameListItemPath::ProgramIdRole).toString().toLower(); + QStringLiteral("%1").arg(program_id, 16, 16, QLatin1Char{'0'}); // Only items which filename in combination with its title contains all words // that are in the searchfield will be visible in the gamelist @@ -231,7 +235,7 @@ void GameList::OnTextChanged(const QString& new_text) { file_path.mid(file_path.lastIndexOf(QLatin1Char{'/'}) + 1) + QLatin1Char{' '} + file_title; if (ContainsAllWords(file_name, edit_filter_text) || - (file_program_id.count() == 16 && edit_filter_text.contains(file_program_id))) { + (file_program_id.count() == 16 && file_program_id.contains(edit_filter_text))) { tree_view->setRowHidden(j, folder_index, false); ++result_count; } else { diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 97ae9e49a..a9d035f3d 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -2535,8 +2535,8 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa return; } - FileSys::VirtualFile file; - if (loader->ReadRomFS(file) != Loader::ResultStatus::Success) { + FileSys::VirtualFile base_romfs; + if (loader->ReadRomFS(base_romfs) != Loader::ResultStatus::Success) { failed(); return; } @@ -2549,6 +2549,14 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa return; } + const auto type = *romfs_title_id == program_id ? FileSys::ContentRecordType::Program + : FileSys::ContentRecordType::Data; + const auto base_nca = installed.GetEntry(*romfs_title_id, type); + if (!base_nca) { + failed(); + return; + } + const auto dump_dir = target == DumpRomFSTarget::Normal ? Common::FS::GetYuzuPath(Common::FS::YuzuPath::DumpDir) @@ -2560,12 +2568,10 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa FileSys::VirtualFile romfs; if (*romfs_title_id == program_id) { - const u64 ivfc_offset = loader->ReadRomFSIVFCOffset(); const FileSys::PatchManager pm{program_id, system->GetFileSystemController(), installed}; - romfs = - pm.PatchRomFS(file, ivfc_offset, FileSys::ContentRecordType::Program, nullptr, false); + romfs = pm.PatchRomFS(base_nca.get(), base_romfs, type, nullptr, false); } else { - romfs = installed.GetEntry(*romfs_title_id, FileSys::ContentRecordType::Data)->GetRomFS(); + romfs = installed.GetEntry(*romfs_title_id, type)->GetRomFS(); } const auto extracted = FileSys::ExtractRomFS(romfs, FileSys::RomFSExtractionType::Full); |