diff options
Diffstat (limited to 'src')
130 files changed, 9526 insertions, 1828 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() | |||
| 134 | endif() | 134 | endif() |
| 135 | 135 | ||
| 136 | # GCC bugs | 136 | # GCC bugs |
| 137 | if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "12" AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") | 137 | if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "11" AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") |
| 138 | # These diagnostics would be great if they worked, but are just completely broken | 138 | # These diagnostics would be great if they worked, but are just completely broken |
| 139 | # and produce bogus errors on external libraries like fmt. | 139 | # and produce bogus errors on external libraries like fmt. |
| 140 | add_compile_options( | 140 | add_compile_options( |
diff --git a/src/android/app/build.gradle.kts b/src/android/app/build.gradle.kts index 9a47e2bd8..a8db70511 100644 --- a/src/android/app/build.gradle.kts +++ b/src/android/app/build.gradle.kts | |||
| @@ -160,6 +160,11 @@ android { | |||
| 160 | } | 160 | } |
| 161 | } | 161 | } |
| 162 | 162 | ||
| 163 | tasks.create<Delete>("ktlintReset") { | ||
| 164 | delete(File(buildDir.path + File.separator + "intermediates/ktLint")) | ||
| 165 | } | ||
| 166 | |||
| 167 | tasks.getByPath("loadKtlintReporters").dependsOn("ktlintReset") | ||
| 163 | tasks.getByPath("preBuild").dependsOn("ktlintCheck") | 168 | tasks.getByPath("preBuild").dependsOn("ktlintCheck") |
| 164 | 169 | ||
| 165 | ktlint { | 170 | ktlint { |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt index aadc445f9..9f859b442 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt | |||
| @@ -3,19 +3,25 @@ | |||
| 3 | 3 | ||
| 4 | package org.yuzu.yuzu_emu.adapters | 4 | package org.yuzu.yuzu_emu.adapters |
| 5 | 5 | ||
| 6 | import android.text.TextUtils | ||
| 6 | import android.view.LayoutInflater | 7 | import android.view.LayoutInflater |
| 7 | import android.view.View | 8 | import android.view.View |
| 8 | import android.view.ViewGroup | 9 | import android.view.ViewGroup |
| 9 | import androidx.appcompat.app.AppCompatActivity | 10 | import androidx.appcompat.app.AppCompatActivity |
| 10 | import androidx.core.content.ContextCompat | 11 | import androidx.core.content.ContextCompat |
| 11 | import androidx.core.content.res.ResourcesCompat | 12 | import androidx.core.content.res.ResourcesCompat |
| 13 | import androidx.lifecycle.LifecycleOwner | ||
| 12 | import androidx.recyclerview.widget.RecyclerView | 14 | import androidx.recyclerview.widget.RecyclerView |
| 13 | import org.yuzu.yuzu_emu.R | 15 | import org.yuzu.yuzu_emu.R |
| 14 | import org.yuzu.yuzu_emu.databinding.CardHomeOptionBinding | 16 | import org.yuzu.yuzu_emu.databinding.CardHomeOptionBinding |
| 15 | import org.yuzu.yuzu_emu.fragments.MessageDialogFragment | 17 | import org.yuzu.yuzu_emu.fragments.MessageDialogFragment |
| 16 | import org.yuzu.yuzu_emu.model.HomeSetting | 18 | import org.yuzu.yuzu_emu.model.HomeSetting |
| 17 | 19 | ||
| 18 | class HomeSettingAdapter(private val activity: AppCompatActivity, var options: List<HomeSetting>) : | 20 | class HomeSettingAdapter( |
| 21 | private val activity: AppCompatActivity, | ||
| 22 | private val viewLifecycle: LifecycleOwner, | ||
| 23 | var options: List<HomeSetting> | ||
| 24 | ) : | ||
| 19 | RecyclerView.Adapter<HomeSettingAdapter.HomeOptionViewHolder>(), | 25 | RecyclerView.Adapter<HomeSettingAdapter.HomeOptionViewHolder>(), |
| 20 | View.OnClickListener { | 26 | View.OnClickListener { |
| 21 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HomeOptionViewHolder { | 27 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HomeOptionViewHolder { |
| @@ -79,6 +85,22 @@ class HomeSettingAdapter(private val activity: AppCompatActivity, var options: L | |||
| 79 | binding.optionDescription.alpha = 0.5f | 85 | binding.optionDescription.alpha = 0.5f |
| 80 | binding.optionIcon.alpha = 0.5f | 86 | binding.optionIcon.alpha = 0.5f |
| 81 | } | 87 | } |
| 88 | |||
| 89 | option.details.observe(viewLifecycle) { updateOptionDetails(it) } | ||
| 90 | binding.optionDetail.postDelayed( | ||
| 91 | { | ||
| 92 | binding.optionDetail.ellipsize = TextUtils.TruncateAt.MARQUEE | ||
| 93 | binding.optionDetail.isSelected = true | ||
| 94 | }, | ||
| 95 | 3000 | ||
| 96 | ) | ||
| 97 | } | ||
| 98 | |||
| 99 | private fun updateOptionDetails(detailString: String) { | ||
| 100 | if (detailString.isNotEmpty()) { | ||
| 101 | binding.optionDetail.text = detailString | ||
| 102 | binding.optionDetail.visibility = View.VISIBLE | ||
| 103 | } | ||
| 82 | } | 104 | } |
| 83 | } | 105 | } |
| 84 | } | 106 | } |
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 | |||
| 5 | 5 | ||
| 6 | import android.text.Html | 6 | import android.text.Html |
| 7 | import android.view.LayoutInflater | 7 | import android.view.LayoutInflater |
| 8 | import android.view.View | ||
| 8 | import android.view.ViewGroup | 9 | import android.view.ViewGroup |
| 9 | import androidx.appcompat.app.AppCompatActivity | 10 | import androidx.appcompat.app.AppCompatActivity |
| 10 | import androidx.core.content.res.ResourcesCompat | 11 | import androidx.core.content.res.ResourcesCompat |
| 12 | import androidx.lifecycle.ViewModelProvider | ||
| 11 | import androidx.recyclerview.widget.RecyclerView | 13 | import androidx.recyclerview.widget.RecyclerView |
| 12 | import com.google.android.material.button.MaterialButton | 14 | import com.google.android.material.button.MaterialButton |
| 13 | import org.yuzu.yuzu_emu.databinding.PageSetupBinding | 15 | import org.yuzu.yuzu_emu.databinding.PageSetupBinding |
| 16 | import org.yuzu.yuzu_emu.model.HomeViewModel | ||
| 17 | import org.yuzu.yuzu_emu.model.SetupCallback | ||
| 14 | import org.yuzu.yuzu_emu.model.SetupPage | 18 | import org.yuzu.yuzu_emu.model.SetupPage |
| 19 | import org.yuzu.yuzu_emu.model.StepState | ||
| 20 | import org.yuzu.yuzu_emu.utils.ViewUtils | ||
| 15 | 21 | ||
| 16 | class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>) : | 22 | class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>) : |
| 17 | RecyclerView.Adapter<SetupAdapter.SetupPageViewHolder>() { | 23 | RecyclerView.Adapter<SetupAdapter.SetupPageViewHolder>() { |
| @@ -26,7 +32,7 @@ class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>) | |||
| 26 | holder.bind(pages[position]) | 32 | holder.bind(pages[position]) |
| 27 | 33 | ||
| 28 | inner class SetupPageViewHolder(val binding: PageSetupBinding) : | 34 | inner class SetupPageViewHolder(val binding: PageSetupBinding) : |
| 29 | RecyclerView.ViewHolder(binding.root) { | 35 | RecyclerView.ViewHolder(binding.root), SetupCallback { |
| 30 | lateinit var page: SetupPage | 36 | lateinit var page: SetupPage |
| 31 | 37 | ||
| 32 | init { | 38 | init { |
| @@ -35,6 +41,12 @@ class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>) | |||
| 35 | 41 | ||
| 36 | fun bind(page: SetupPage) { | 42 | fun bind(page: SetupPage) { |
| 37 | this.page = page | 43 | this.page = page |
| 44 | |||
| 45 | if (page.stepCompleted.invoke() == StepState.COMPLETE) { | ||
| 46 | binding.buttonAction.visibility = View.INVISIBLE | ||
| 47 | binding.textConfirmation.visibility = View.VISIBLE | ||
| 48 | } | ||
| 49 | |||
| 38 | binding.icon.setImageDrawable( | 50 | binding.icon.setImageDrawable( |
| 39 | ResourcesCompat.getDrawable( | 51 | ResourcesCompat.getDrawable( |
| 40 | activity.resources, | 52 | activity.resources, |
| @@ -62,9 +74,15 @@ class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>) | |||
| 62 | MaterialButton.ICON_GRAVITY_END | 74 | MaterialButton.ICON_GRAVITY_END |
| 63 | } | 75 | } |
| 64 | setOnClickListener { | 76 | setOnClickListener { |
| 65 | page.buttonAction.invoke() | 77 | page.buttonAction.invoke(this@SetupPageViewHolder) |
| 66 | } | 78 | } |
| 67 | } | 79 | } |
| 68 | } | 80 | } |
| 81 | |||
| 82 | override fun onStepCompleted() { | ||
| 83 | ViewUtils.hideView(binding.buttonAction, 200) | ||
| 84 | ViewUtils.showView(binding.textConfirmation, 200) | ||
| 85 | ViewModelProvider(activity)[HomeViewModel::class.java].setShouldPageForward(true) | ||
| 86 | } | ||
| 69 | } | 87 | } |
| 70 | } | 88 | } |
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 { | |||
| 297 | emulationActivity?.let { | 297 | emulationActivity?.let { |
| 298 | it.requestedOrientation = when (IntSetting.RENDERER_SCREEN_LAYOUT.int) { | 298 | it.requestedOrientation = when (IntSetting.RENDERER_SCREEN_LAYOUT.int) { |
| 299 | Settings.LayoutOption_MobileLandscape -> | 299 | Settings.LayoutOption_MobileLandscape -> |
| 300 | ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE | 300 | ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE |
| 301 | Settings.LayoutOption_MobilePortrait -> | 301 | Settings.LayoutOption_MobilePortrait -> |
| 302 | ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT | 302 | ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT |
| 303 | Settings.LayoutOption_Unspecified -> ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED | 303 | Settings.LayoutOption_Unspecified -> ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED |
| 304 | else -> ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE | 304 | else -> ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE |
| 305 | } | 305 | } |
| 306 | } | 306 | } |
| 307 | } | 307 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt index c001af892..d5e793491 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt | |||
| @@ -129,7 +129,11 @@ class HomeSettingsFragment : Fragment() { | |||
| 129 | mainActivity.getGamesDirectory.launch( | 129 | mainActivity.getGamesDirectory.launch( |
| 130 | Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data | 130 | Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data |
| 131 | ) | 131 | ) |
| 132 | } | 132 | }, |
| 133 | { true }, | ||
| 134 | 0, | ||
| 135 | 0, | ||
| 136 | homeViewModel.gamesDir | ||
| 133 | ) | 137 | ) |
| 134 | ) | 138 | ) |
| 135 | add( | 139 | add( |
| @@ -201,7 +205,11 @@ class HomeSettingsFragment : Fragment() { | |||
| 201 | 205 | ||
| 202 | binding.homeSettingsList.apply { | 206 | binding.homeSettingsList.apply { |
| 203 | layoutManager = LinearLayoutManager(requireContext()) | 207 | layoutManager = LinearLayoutManager(requireContext()) |
| 204 | adapter = HomeSettingAdapter(requireActivity() as AppCompatActivity, optionsList) | 208 | adapter = HomeSettingAdapter( |
| 209 | requireActivity() as AppCompatActivity, | ||
| 210 | viewLifecycleOwner, | ||
| 211 | optionsList | ||
| 212 | ) | ||
| 205 | } | 213 | } |
| 206 | 214 | ||
| 207 | setInsets() | 215 | setInsets() |
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 | |||
| 19 | import androidx.core.view.ViewCompat | 19 | import androidx.core.view.ViewCompat |
| 20 | import androidx.core.view.WindowInsetsCompat | 20 | import androidx.core.view.WindowInsetsCompat |
| 21 | import androidx.core.view.isVisible | 21 | import androidx.core.view.isVisible |
| 22 | import androidx.core.view.updatePadding | ||
| 22 | import androidx.fragment.app.Fragment | 23 | import androidx.fragment.app.Fragment |
| 23 | import androidx.fragment.app.activityViewModels | 24 | import androidx.fragment.app.activityViewModels |
| 24 | import androidx.navigation.findNavController | 25 | import androidx.navigation.findNavController |
| @@ -32,10 +33,13 @@ import org.yuzu.yuzu_emu.adapters.SetupAdapter | |||
| 32 | import org.yuzu.yuzu_emu.databinding.FragmentSetupBinding | 33 | import org.yuzu.yuzu_emu.databinding.FragmentSetupBinding |
| 33 | import org.yuzu.yuzu_emu.features.settings.model.Settings | 34 | import org.yuzu.yuzu_emu.features.settings.model.Settings |
| 34 | import org.yuzu.yuzu_emu.model.HomeViewModel | 35 | import org.yuzu.yuzu_emu.model.HomeViewModel |
| 36 | import org.yuzu.yuzu_emu.model.SetupCallback | ||
| 35 | import org.yuzu.yuzu_emu.model.SetupPage | 37 | import org.yuzu.yuzu_emu.model.SetupPage |
| 38 | import org.yuzu.yuzu_emu.model.StepState | ||
| 36 | import org.yuzu.yuzu_emu.ui.main.MainActivity | 39 | import org.yuzu.yuzu_emu.ui.main.MainActivity |
| 37 | import org.yuzu.yuzu_emu.utils.DirectoryInitialization | 40 | import org.yuzu.yuzu_emu.utils.DirectoryInitialization |
| 38 | import org.yuzu.yuzu_emu.utils.GameHelper | 41 | import org.yuzu.yuzu_emu.utils.GameHelper |
| 42 | import org.yuzu.yuzu_emu.utils.ViewUtils | ||
| 39 | 43 | ||
| 40 | class SetupFragment : Fragment() { | 44 | class SetupFragment : Fragment() { |
| 41 | private var _binding: FragmentSetupBinding? = null | 45 | private var _binding: FragmentSetupBinding? = null |
| @@ -112,14 +116,22 @@ class SetupFragment : Fragment() { | |||
| 112 | 0, | 116 | 0, |
| 113 | false, | 117 | false, |
| 114 | R.string.give_permission, | 118 | R.string.give_permission, |
| 115 | { permissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) }, | 119 | { |
| 120 | notificationCallback = it | ||
| 121 | permissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) | ||
| 122 | }, | ||
| 116 | true, | 123 | true, |
| 117 | R.string.notification_warning, | 124 | R.string.notification_warning, |
| 118 | R.string.notification_warning_description, | 125 | R.string.notification_warning_description, |
| 119 | 0, | 126 | 0, |
| 120 | { | 127 | { |
| 121 | NotificationManagerCompat.from(requireContext()) | 128 | if (NotificationManagerCompat.from(requireContext()) |
| 122 | .areNotificationsEnabled() | 129 | .areNotificationsEnabled() |
| 130 | ) { | ||
| 131 | StepState.COMPLETE | ||
| 132 | } else { | ||
| 133 | StepState.INCOMPLETE | ||
| 134 | } | ||
| 123 | } | 135 | } |
| 124 | ) | 136 | ) |
| 125 | ) | 137 | ) |
| @@ -133,12 +145,22 @@ class SetupFragment : Fragment() { | |||
| 133 | R.drawable.ic_add, | 145 | R.drawable.ic_add, |
| 134 | true, | 146 | true, |
| 135 | R.string.select_keys, | 147 | R.string.select_keys, |
| 136 | { mainActivity.getProdKey.launch(arrayOf("*/*")) }, | 148 | { |
| 149 | keyCallback = it | ||
| 150 | getProdKey.launch(arrayOf("*/*")) | ||
| 151 | }, | ||
| 137 | true, | 152 | true, |
| 138 | R.string.install_prod_keys_warning, | 153 | R.string.install_prod_keys_warning, |
| 139 | R.string.install_prod_keys_warning_description, | 154 | R.string.install_prod_keys_warning_description, |
| 140 | R.string.install_prod_keys_warning_help, | 155 | R.string.install_prod_keys_warning_help, |
| 141 | { File(DirectoryInitialization.userDirectory + "/keys/prod.keys").exists() } | 156 | { |
| 157 | val file = File(DirectoryInitialization.userDirectory + "/keys/prod.keys") | ||
| 158 | if (file.exists()) { | ||
| 159 | StepState.COMPLETE | ||
| 160 | } else { | ||
| 161 | StepState.INCOMPLETE | ||
| 162 | } | ||
| 163 | } | ||
| 142 | ) | 164 | ) |
| 143 | ) | 165 | ) |
| 144 | add( | 166 | add( |
| @@ -150,9 +172,8 @@ class SetupFragment : Fragment() { | |||
| 150 | true, | 172 | true, |
| 151 | R.string.add_games, | 173 | R.string.add_games, |
| 152 | { | 174 | { |
| 153 | mainActivity.getGamesDirectory.launch( | 175 | gamesDirCallback = it |
| 154 | Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data | 176 | getGamesDirectory.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data) |
| 155 | ) | ||
| 156 | }, | 177 | }, |
| 157 | true, | 178 | true, |
| 158 | R.string.add_games_warning, | 179 | R.string.add_games_warning, |
| @@ -163,7 +184,11 @@ class SetupFragment : Fragment() { | |||
| 163 | PreferenceManager.getDefaultSharedPreferences( | 184 | PreferenceManager.getDefaultSharedPreferences( |
| 164 | YuzuApplication.appContext | 185 | YuzuApplication.appContext |
| 165 | ) | 186 | ) |
| 166 | preferences.getString(GameHelper.KEY_GAME_PATH, "")!!.isNotEmpty() | 187 | if (preferences.getString(GameHelper.KEY_GAME_PATH, "")!!.isNotEmpty()) { |
| 188 | StepState.COMPLETE | ||
| 189 | } else { | ||
| 190 | StepState.INCOMPLETE | ||
| 191 | } | ||
| 167 | } | 192 | } |
| 168 | ) | 193 | ) |
| 169 | ) | 194 | ) |
| @@ -181,6 +206,13 @@ class SetupFragment : Fragment() { | |||
| 181 | ) | 206 | ) |
| 182 | } | 207 | } |
| 183 | 208 | ||
| 209 | homeViewModel.shouldPageForward.observe(viewLifecycleOwner) { | ||
| 210 | if (it) { | ||
| 211 | pageForward() | ||
| 212 | homeViewModel.setShouldPageForward(false) | ||
| 213 | } | ||
| 214 | } | ||
| 215 | |||
| 184 | binding.viewPager2.apply { | 216 | binding.viewPager2.apply { |
| 185 | adapter = SetupAdapter(requireActivity() as AppCompatActivity, pages) | 217 | adapter = SetupAdapter(requireActivity() as AppCompatActivity, pages) |
| 186 | offscreenPageLimit = 2 | 218 | offscreenPageLimit = 2 |
| @@ -194,15 +226,15 @@ class SetupFragment : Fragment() { | |||
| 194 | super.onPageSelected(position) | 226 | super.onPageSelected(position) |
| 195 | 227 | ||
| 196 | if (position == 1 && previousPosition == 0) { | 228 | if (position == 1 && previousPosition == 0) { |
| 197 | showView(binding.buttonNext) | 229 | ViewUtils.showView(binding.buttonNext) |
| 198 | showView(binding.buttonBack) | 230 | ViewUtils.showView(binding.buttonBack) |
| 199 | } else if (position == 0 && previousPosition == 1) { | 231 | } else if (position == 0 && previousPosition == 1) { |
| 200 | hideView(binding.buttonBack) | 232 | ViewUtils.hideView(binding.buttonBack) |
| 201 | hideView(binding.buttonNext) | 233 | ViewUtils.hideView(binding.buttonNext) |
| 202 | } else if (position == pages.size - 1 && previousPosition == pages.size - 2) { | 234 | } else if (position == pages.size - 1 && previousPosition == pages.size - 2) { |
| 203 | hideView(binding.buttonNext) | 235 | ViewUtils.hideView(binding.buttonNext) |
| 204 | } else if (position == pages.size - 2 && previousPosition == pages.size - 1) { | 236 | } else if (position == pages.size - 2 && previousPosition == pages.size - 1) { |
| 205 | showView(binding.buttonNext) | 237 | ViewUtils.showView(binding.buttonNext) |
| 206 | } | 238 | } |
| 207 | 239 | ||
| 208 | previousPosition = position | 240 | previousPosition = position |
| @@ -215,7 +247,8 @@ class SetupFragment : Fragment() { | |||
| 215 | 247 | ||
| 216 | // Checks if the user has completed the task on the current page | 248 | // Checks if the user has completed the task on the current page |
| 217 | if (currentPage.hasWarning) { | 249 | if (currentPage.hasWarning) { |
| 218 | if (currentPage.taskCompleted.invoke()) { | 250 | val stepState = currentPage.stepCompleted.invoke() |
| 251 | if (stepState != StepState.INCOMPLETE) { | ||
| 219 | pageForward() | 252 | pageForward() |
| 220 | return@setOnClickListener | 253 | return@setOnClickListener |
| 221 | } | 254 | } |
| @@ -264,9 +297,15 @@ class SetupFragment : Fragment() { | |||
| 264 | _binding = null | 297 | _binding = null |
| 265 | } | 298 | } |
| 266 | 299 | ||
| 300 | private lateinit var notificationCallback: SetupCallback | ||
| 301 | |||
| 267 | @RequiresApi(Build.VERSION_CODES.TIRAMISU) | 302 | @RequiresApi(Build.VERSION_CODES.TIRAMISU) |
| 268 | private val permissionLauncher = | 303 | private val permissionLauncher = |
| 269 | registerForActivityResult(ActivityResultContracts.RequestPermission()) { | 304 | registerForActivityResult(ActivityResultContracts.RequestPermission()) { |
| 305 | if (it) { | ||
| 306 | notificationCallback.onStepCompleted() | ||
| 307 | } | ||
| 308 | |||
| 270 | if (!it && | 309 | if (!it && |
| 271 | !shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS) | 310 | !shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS) |
| 272 | ) { | 311 | ) { |
| @@ -277,6 +316,27 @@ class SetupFragment : Fragment() { | |||
| 277 | } | 316 | } |
| 278 | } | 317 | } |
| 279 | 318 | ||
| 319 | private lateinit var keyCallback: SetupCallback | ||
| 320 | |||
| 321 | val getProdKey = | ||
| 322 | registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> | ||
| 323 | if (result != null) { | ||
| 324 | if (mainActivity.processKey(result)) { | ||
| 325 | keyCallback.onStepCompleted() | ||
| 326 | } | ||
| 327 | } | ||
| 328 | } | ||
| 329 | |||
| 330 | private lateinit var gamesDirCallback: SetupCallback | ||
| 331 | |||
| 332 | val getGamesDirectory = | ||
| 333 | registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result -> | ||
| 334 | if (result != null) { | ||
| 335 | mainActivity.processGamesDir(result) | ||
| 336 | gamesDirCallback.onStepCompleted() | ||
| 337 | } | ||
| 338 | } | ||
| 339 | |||
| 280 | private fun finishSetup() { | 340 | private fun finishSetup() { |
| 281 | PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext).edit() | 341 | PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext).edit() |
| 282 | .putBoolean(Settings.PREF_FIRST_APP_LAUNCH, false) | 342 | .putBoolean(Settings.PREF_FIRST_APP_LAUNCH, false) |
| @@ -284,33 +344,6 @@ class SetupFragment : Fragment() { | |||
| 284 | mainActivity.finishSetup(binding.root.findNavController()) | 344 | mainActivity.finishSetup(binding.root.findNavController()) |
| 285 | } | 345 | } |
| 286 | 346 | ||
| 287 | private fun showView(view: View) { | ||
| 288 | view.apply { | ||
| 289 | alpha = 0f | ||
| 290 | visibility = View.VISIBLE | ||
| 291 | isClickable = true | ||
| 292 | }.animate().apply { | ||
| 293 | duration = 300 | ||
| 294 | alpha(1f) | ||
| 295 | }.start() | ||
| 296 | } | ||
| 297 | |||
| 298 | private fun hideView(view: View) { | ||
| 299 | if (view.visibility == View.INVISIBLE) { | ||
| 300 | return | ||
| 301 | } | ||
| 302 | |||
| 303 | view.apply { | ||
| 304 | alpha = 1f | ||
| 305 | isClickable = false | ||
| 306 | }.animate().apply { | ||
| 307 | duration = 300 | ||
| 308 | alpha(0f) | ||
| 309 | }.withEndAction { | ||
| 310 | view.visibility = View.INVISIBLE | ||
| 311 | } | ||
| 312 | } | ||
| 313 | |||
| 314 | fun pageForward() { | 347 | fun pageForward() { |
| 315 | binding.viewPager2.currentItem = binding.viewPager2.currentItem + 1 | 348 | binding.viewPager2.currentItem = binding.viewPager2.currentItem + 1 |
| 316 | } | 349 | } |
| @@ -326,15 +359,29 @@ class SetupFragment : Fragment() { | |||
| 326 | private fun setInsets() = | 359 | private fun setInsets() = |
| 327 | ViewCompat.setOnApplyWindowInsetsListener( | 360 | ViewCompat.setOnApplyWindowInsetsListener( |
| 328 | binding.root | 361 | binding.root |
| 329 | ) { view: View, windowInsets: WindowInsetsCompat -> | 362 | ) { _: View, windowInsets: WindowInsetsCompat -> |
| 330 | val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) | 363 | val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) |
| 331 | val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) | 364 | val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) |
| 332 | view.setPadding( | 365 | |
| 333 | barInsets.left + cutoutInsets.left, | 366 | val leftPadding = barInsets.left + cutoutInsets.left |
| 334 | barInsets.top + cutoutInsets.top, | 367 | val topPadding = barInsets.top + cutoutInsets.top |
| 335 | barInsets.right + cutoutInsets.right, | 368 | val rightPadding = barInsets.right + cutoutInsets.right |
| 336 | barInsets.bottom + cutoutInsets.bottom | 369 | val bottomPadding = barInsets.bottom + cutoutInsets.bottom |
| 337 | ) | 370 | |
| 371 | if (resources.getBoolean(R.bool.small_layout)) { | ||
| 372 | binding.viewPager2 | ||
| 373 | .updatePadding(left = leftPadding, top = topPadding, right = rightPadding) | ||
| 374 | binding.constraintButtons | ||
| 375 | .updatePadding(left = leftPadding, right = rightPadding, bottom = bottomPadding) | ||
| 376 | } else { | ||
| 377 | binding.viewPager2.updatePadding(top = topPadding, bottom = bottomPadding) | ||
| 378 | binding.constraintButtons | ||
| 379 | .updatePadding( | ||
| 380 | left = leftPadding, | ||
| 381 | right = rightPadding, | ||
| 382 | bottom = bottomPadding | ||
| 383 | ) | ||
| 384 | } | ||
| 338 | windowInsets | 385 | windowInsets |
| 339 | } | 386 | } |
| 340 | } | 387 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeSetting.kt index 522d07c37..498c222fa 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeSetting.kt | |||
| @@ -3,6 +3,9 @@ | |||
| 3 | 3 | ||
| 4 | package org.yuzu.yuzu_emu.model | 4 | package org.yuzu.yuzu_emu.model |
| 5 | 5 | ||
| 6 | import androidx.lifecycle.LiveData | ||
| 7 | import androidx.lifecycle.MutableLiveData | ||
| 8 | |||
| 6 | data class HomeSetting( | 9 | data class HomeSetting( |
| 7 | val titleId: Int, | 10 | val titleId: Int, |
| 8 | val descriptionId: Int, | 11 | val descriptionId: Int, |
| @@ -10,5 +13,6 @@ data class HomeSetting( | |||
| 10 | val onClick: () -> Unit, | 13 | val onClick: () -> Unit, |
| 11 | val isEnabled: () -> Boolean = { true }, | 14 | val isEnabled: () -> Boolean = { true }, |
| 12 | val disabledTitleId: Int = 0, | 15 | val disabledTitleId: Int = 0, |
| 13 | val disabledMessageId: Int = 0 | 16 | val disabledMessageId: Int = 0, |
| 17 | val details: LiveData<String> = MutableLiveData("") | ||
| 14 | ) | 18 | ) |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt index 263ee7144..a48ef7a88 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 | |||
| @@ -3,9 +3,15 @@ | |||
| 3 | 3 | ||
| 4 | package org.yuzu.yuzu_emu.model | 4 | package org.yuzu.yuzu_emu.model |
| 5 | 5 | ||
| 6 | import android.net.Uri | ||
| 7 | import androidx.fragment.app.FragmentActivity | ||
| 6 | import androidx.lifecycle.LiveData | 8 | import androidx.lifecycle.LiveData |
| 7 | import androidx.lifecycle.MutableLiveData | 9 | import androidx.lifecycle.MutableLiveData |
| 8 | import androidx.lifecycle.ViewModel | 10 | import androidx.lifecycle.ViewModel |
| 11 | import androidx.lifecycle.ViewModelProvider | ||
| 12 | import androidx.preference.PreferenceManager | ||
| 13 | import org.yuzu.yuzu_emu.YuzuApplication | ||
| 14 | import org.yuzu.yuzu_emu.utils.GameHelper | ||
| 9 | 15 | ||
| 10 | class HomeViewModel : ViewModel() { | 16 | class HomeViewModel : ViewModel() { |
| 11 | private val _navigationVisible = MutableLiveData<Pair<Boolean, Boolean>>() | 17 | private val _navigationVisible = MutableLiveData<Pair<Boolean, Boolean>>() |
| @@ -14,6 +20,17 @@ class HomeViewModel : ViewModel() { | |||
| 14 | private val _statusBarShadeVisible = MutableLiveData(true) | 20 | private val _statusBarShadeVisible = MutableLiveData(true) |
| 15 | val statusBarShadeVisible: LiveData<Boolean> get() = _statusBarShadeVisible | 21 | val statusBarShadeVisible: LiveData<Boolean> get() = _statusBarShadeVisible |
| 16 | 22 | ||
| 23 | private val _shouldPageForward = MutableLiveData(false) | ||
| 24 | val shouldPageForward: LiveData<Boolean> get() = _shouldPageForward | ||
| 25 | |||
| 26 | private val _gamesDir = MutableLiveData( | ||
| 27 | Uri.parse( | ||
| 28 | PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) | ||
| 29 | .getString(GameHelper.KEY_GAME_PATH, "") | ||
| 30 | ).path ?: "" | ||
| 31 | ) | ||
| 32 | val gamesDir: LiveData<String> get() = _gamesDir | ||
| 33 | |||
| 17 | var navigatedToSetup = false | 34 | var navigatedToSetup = false |
| 18 | 35 | ||
| 19 | init { | 36 | init { |
| @@ -33,4 +50,13 @@ class HomeViewModel : ViewModel() { | |||
| 33 | } | 50 | } |
| 34 | _statusBarShadeVisible.value = visible | 51 | _statusBarShadeVisible.value = visible |
| 35 | } | 52 | } |
| 53 | |||
| 54 | fun setShouldPageForward(pageForward: Boolean) { | ||
| 55 | _shouldPageForward.value = pageForward | ||
| 56 | } | ||
| 57 | |||
| 58 | fun setGamesDir(activity: FragmentActivity, dir: String) { | ||
| 59 | ViewModelProvider(activity)[GamesViewModel::class.java].reloadGames(true) | ||
| 60 | _gamesDir.value = dir | ||
| 61 | } | ||
| 36 | } | 62 | } |
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( | |||
| 10 | val buttonIconId: Int, | 10 | val buttonIconId: Int, |
| 11 | val leftAlignedIcon: Boolean, | 11 | val leftAlignedIcon: Boolean, |
| 12 | val buttonTextId: Int, | 12 | val buttonTextId: Int, |
| 13 | val buttonAction: () -> Unit, | 13 | val buttonAction: (callback: SetupCallback) -> Unit, |
| 14 | val hasWarning: Boolean, | 14 | val hasWarning: Boolean, |
| 15 | val warningTitleId: Int = 0, | 15 | val warningTitleId: Int = 0, |
| 16 | val warningDescriptionId: Int = 0, | 16 | val warningDescriptionId: Int = 0, |
| 17 | val warningHelpLinkId: Int = 0, | 17 | val warningHelpLinkId: Int = 0, |
| 18 | val taskCompleted: () -> Boolean = { true } | 18 | val stepCompleted: () -> StepState = { StepState.UNDEFINED } |
| 19 | ) | 19 | ) |
| 20 | |||
| 21 | interface SetupCallback { | ||
| 22 | fun onStepCompleted() | ||
| 23 | } | ||
| 24 | |||
| 25 | enum class StepState { | ||
| 26 | COMPLETE, | ||
| 27 | INCOMPLETE, | ||
| 28 | UNDEFINED | ||
| 29 | } | ||
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..aaf3a0ec1 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,81 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 266 | 266 | ||
| 267 | val getGamesDirectory = | 267 | val getGamesDirectory = |
| 268 | registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result -> | 268 | registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result -> |
| 269 | if (result == null) { | 269 | if (result != null) { |
| 270 | return@registerForActivityResult | 270 | processGamesDir(result) |
| 271 | } | 271 | } |
| 272 | } | ||
| 272 | 273 | ||
| 273 | contentResolver.takePersistableUriPermission( | 274 | fun processGamesDir(result: Uri) { |
| 274 | result, | 275 | contentResolver.takePersistableUriPermission( |
| 275 | Intent.FLAG_GRANT_READ_URI_PERMISSION | 276 | result, |
| 276 | ) | 277 | Intent.FLAG_GRANT_READ_URI_PERMISSION |
| 278 | ) | ||
| 277 | 279 | ||
| 278 | // When a new directory is picked, we currently will reset the existing games | 280 | // When a new directory is picked, we currently will reset the existing games |
| 279 | // database. This effectively means that only one game directory is supported. | 281 | // database. This effectively means that only one game directory is supported. |
| 280 | PreferenceManager.getDefaultSharedPreferences(applicationContext).edit() | 282 | PreferenceManager.getDefaultSharedPreferences(applicationContext).edit() |
| 281 | .putString(GameHelper.KEY_GAME_PATH, result.toString()) | 283 | .putString(GameHelper.KEY_GAME_PATH, result.toString()) |
| 282 | .apply() | 284 | .apply() |
| 283 | 285 | ||
| 284 | Toast.makeText( | 286 | Toast.makeText( |
| 285 | applicationContext, | 287 | applicationContext, |
| 286 | R.string.games_dir_selected, | 288 | R.string.games_dir_selected, |
| 287 | Toast.LENGTH_LONG | 289 | Toast.LENGTH_LONG |
| 288 | ).show() | 290 | ).show() |
| 289 | 291 | ||
| 290 | gamesViewModel.reloadGames(true) | 292 | gamesViewModel.reloadGames(true) |
| 291 | } | 293 | homeViewModel.setGamesDir(this, result.path!!) |
| 294 | } | ||
| 292 | 295 | ||
| 293 | val getProdKey = | 296 | val getProdKey = |
| 294 | registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> | 297 | registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> |
| 295 | if (result == null) { | 298 | if (result != null) { |
| 296 | return@registerForActivityResult | 299 | processKey(result) |
| 297 | } | 300 | } |
| 301 | } | ||
| 298 | 302 | ||
| 299 | if (FileUtil.getExtension(result) != "keys") { | 303 | fun processKey(result: Uri): Boolean { |
| 300 | MessageDialogFragment.newInstance( | 304 | if (FileUtil.getExtension(result) != "keys") { |
| 301 | R.string.reading_keys_failure, | 305 | MessageDialogFragment.newInstance( |
| 302 | R.string.install_prod_keys_failure_extension_description | 306 | R.string.reading_keys_failure, |
| 303 | ).show(supportFragmentManager, MessageDialogFragment.TAG) | 307 | R.string.install_prod_keys_failure_extension_description |
| 304 | return@registerForActivityResult | 308 | ).show(supportFragmentManager, MessageDialogFragment.TAG) |
| 305 | } | 309 | return false |
| 310 | } | ||
| 306 | 311 | ||
| 307 | contentResolver.takePersistableUriPermission( | 312 | contentResolver.takePersistableUriPermission( |
| 313 | result, | ||
| 314 | Intent.FLAG_GRANT_READ_URI_PERMISSION | ||
| 315 | ) | ||
| 316 | |||
| 317 | val dstPath = DirectoryInitialization.userDirectory + "/keys/" | ||
| 318 | if (FileUtil.copyUriToInternalStorage( | ||
| 319 | applicationContext, | ||
| 308 | result, | 320 | result, |
| 309 | Intent.FLAG_GRANT_READ_URI_PERMISSION | 321 | dstPath, |
| 322 | "prod.keys" | ||
| 310 | ) | 323 | ) |
| 311 | 324 | ) { | |
| 312 | val dstPath = DirectoryInitialization.userDirectory + "/keys/" | 325 | if (NativeLibrary.reloadKeys()) { |
| 313 | if (FileUtil.copyUriToInternalStorage( | 326 | Toast.makeText( |
| 314 | applicationContext, | 327 | applicationContext, |
| 315 | result, | 328 | R.string.install_keys_success, |
| 316 | dstPath, | 329 | Toast.LENGTH_SHORT |
| 317 | "prod.keys" | 330 | ).show() |
| 318 | ) | 331 | gamesViewModel.reloadGames(true) |
| 319 | ) { | 332 | return true |
| 320 | if (NativeLibrary.reloadKeys()) { | 333 | } else { |
| 321 | Toast.makeText( | 334 | MessageDialogFragment.newInstance( |
| 322 | applicationContext, | 335 | R.string.invalid_keys_error, |
| 323 | R.string.install_keys_success, | 336 | R.string.install_keys_failure_description, |
| 324 | Toast.LENGTH_SHORT | 337 | R.string.dumping_keys_quickstart_link |
| 325 | ).show() | 338 | ).show(supportFragmentManager, MessageDialogFragment.TAG) |
| 326 | gamesViewModel.reloadGames(true) | 339 | return false |
| 327 | } else { | ||
| 328 | MessageDialogFragment.newInstance( | ||
| 329 | R.string.invalid_keys_error, | ||
| 330 | R.string.install_keys_failure_description, | ||
| 331 | R.string.dumping_keys_quickstart_link | ||
| 332 | ).show(supportFragmentManager, MessageDialogFragment.TAG) | ||
| 333 | } | ||
| 334 | } | 340 | } |
| 335 | } | 341 | } |
| 342 | return false | ||
| 343 | } | ||
| 336 | 344 | ||
| 337 | val getFirmware = | 345 | val getFirmware = |
| 338 | registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> | 346 | 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | package org.yuzu.yuzu_emu.utils | ||
| 5 | |||
| 6 | import android.view.View | ||
| 7 | |||
| 8 | object ViewUtils { | ||
| 9 | fun showView(view: View, length: Long = 300) { | ||
| 10 | view.apply { | ||
| 11 | alpha = 0f | ||
| 12 | visibility = View.VISIBLE | ||
| 13 | isClickable = true | ||
| 14 | }.animate().apply { | ||
| 15 | duration = length | ||
| 16 | alpha(1f) | ||
| 17 | }.start() | ||
| 18 | } | ||
| 19 | |||
| 20 | fun hideView(view: View, length: Long = 300) { | ||
| 21 | if (view.visibility == View.INVISIBLE) { | ||
| 22 | return | ||
| 23 | } | ||
| 24 | |||
| 25 | view.apply { | ||
| 26 | alpha = 1f | ||
| 27 | isClickable = false | ||
| 28 | }.animate().apply { | ||
| 29 | duration = length | ||
| 30 | alpha(0f) | ||
| 31 | }.withEndAction { | ||
| 32 | view.visibility = View.INVISIBLE | ||
| 33 | }.start() | ||
| 34 | } | ||
| 35 | } | ||
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 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | 1 | <?xml version="1.0" encoding="utf-8"?> |
| 2 | <androidx.constraintlayout.widget.ConstraintLayout | 2 | <RelativeLayout |
| 3 | xmlns:android="http://schemas.android.com/apk/res/android" | 3 | xmlns:android="http://schemas.android.com/apk/res/android" |
| 4 | xmlns:app="http://schemas.android.com/apk/res-auto" | 4 | xmlns:app="http://schemas.android.com/apk/res-auto" |
| 5 | android:id="@+id/setup_root" | 5 | android:id="@+id/setup_root" |
| @@ -8,33 +8,39 @@ | |||
| 8 | 8 | ||
| 9 | <androidx.viewpager2.widget.ViewPager2 | 9 | <androidx.viewpager2.widget.ViewPager2 |
| 10 | android:id="@+id/viewPager2" | 10 | android:id="@+id/viewPager2" |
| 11 | android:layout_width="0dp" | 11 | android:layout_width="match_parent" |
| 12 | android:layout_height="0dp" | 12 | android:layout_height="match_parent" |
| 13 | app:layout_constraintBottom_toBottomOf="parent" | 13 | android:layout_alignParentTop="true" |
| 14 | app:layout_constraintEnd_toEndOf="parent" | 14 | android:layout_alignParentBottom="true" |
| 15 | app:layout_constraintStart_toStartOf="parent" | 15 | android:clipToPadding="false" /> |
| 16 | app:layout_constraintTop_toTopOf="parent" /> | ||
| 17 | 16 | ||
| 18 | <com.google.android.material.button.MaterialButton | 17 | <androidx.constraintlayout.widget.ConstraintLayout |
| 19 | style="@style/Widget.Material3.Button.TextButton" | 18 | android:id="@+id/constraint_buttons" |
| 20 | android:id="@+id/button_next" | 19 | android:layout_width="match_parent" |
| 21 | android:layout_width="wrap_content" | ||
| 22 | android:layout_height="wrap_content" | 20 | android:layout_height="wrap_content" |
| 23 | android:layout_margin="16dp" | 21 | android:layout_alignParentBottom="true" |
| 24 | android:text="@string/next" | 22 | android:layout_margin="8dp"> |
| 25 | android:visibility="invisible" | ||
| 26 | app:layout_constraintBottom_toBottomOf="parent" | ||
| 27 | app:layout_constraintEnd_toEndOf="parent" /> | ||
| 28 | 23 | ||
| 29 | <com.google.android.material.button.MaterialButton | 24 | <com.google.android.material.button.MaterialButton |
| 30 | android:id="@+id/button_back" | 25 | android:id="@+id/button_next" |
| 31 | style="@style/Widget.Material3.Button.TextButton" | 26 | style="@style/Widget.Material3.Button.TextButton" |
| 32 | android:layout_width="wrap_content" | 27 | android:layout_width="wrap_content" |
| 33 | android:layout_height="wrap_content" | 28 | android:layout_height="wrap_content" |
| 34 | android:layout_margin="16dp" | 29 | android:text="@string/next" |
| 35 | android:text="@string/back" | 30 | android:visibility="invisible" |
| 36 | android:visibility="invisible" | 31 | app:layout_constraintBottom_toBottomOf="parent" |
| 37 | app:layout_constraintBottom_toBottomOf="parent" | 32 | app:layout_constraintEnd_toEndOf="parent" /> |
| 38 | app:layout_constraintStart_toStartOf="parent" /> | 33 | |
| 34 | <com.google.android.material.button.MaterialButton | ||
| 35 | android:id="@+id/button_back" | ||
| 36 | style="@style/Widget.Material3.Button.TextButton" | ||
| 37 | android:layout_width="wrap_content" | ||
| 38 | android:layout_height="wrap_content" | ||
| 39 | android:text="@string/back" | ||
| 40 | android:visibility="invisible" | ||
| 41 | app:layout_constraintBottom_toBottomOf="parent" | ||
| 42 | app:layout_constraintStart_toStartOf="parent" /> | ||
| 43 | |||
| 44 | </androidx.constraintlayout.widget.ConstraintLayout> | ||
| 39 | 45 | ||
| 40 | </androidx.constraintlayout.widget.ConstraintLayout> | 46 | </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 @@ | |||
| 21 | 21 | ||
| 22 | </LinearLayout> | 22 | </LinearLayout> |
| 23 | 23 | ||
| 24 | <LinearLayout | 24 | <androidx.constraintlayout.widget.ConstraintLayout |
| 25 | android:layout_width="match_parent" | 25 | android:layout_width="match_parent" |
| 26 | android:layout_height="match_parent" | 26 | android:layout_height="match_parent" |
| 27 | android:layout_weight="1" | 27 | android:layout_weight="1"> |
| 28 | android:orientation="vertical" | ||
| 29 | android:gravity="center"> | ||
| 30 | 28 | ||
| 31 | <com.google.android.material.textview.MaterialTextView | 29 | <com.google.android.material.textview.MaterialTextView |
| 32 | style="@style/TextAppearance.Material3.DisplaySmall" | ||
| 33 | android:id="@+id/text_title" | 30 | android:id="@+id/text_title" |
| 34 | android:layout_width="match_parent" | 31 | style="@style/TextAppearance.Material3.DisplaySmall" |
| 35 | android:layout_height="wrap_content" | 32 | android:layout_width="0dp" |
| 36 | android:textAlignment="center" | 33 | android:layout_height="0dp" |
| 34 | android:gravity="center" | ||
| 37 | android:textColor="?attr/colorOnSurface" | 35 | android:textColor="?attr/colorOnSurface" |
| 38 | android:textStyle="bold" | 36 | android:textStyle="bold" |
| 37 | app:layout_constraintBottom_toTopOf="@+id/text_description" | ||
| 38 | app:layout_constraintEnd_toEndOf="parent" | ||
| 39 | app:layout_constraintStart_toStartOf="parent" | ||
| 40 | app:layout_constraintTop_toTopOf="parent" | ||
| 41 | app:layout_constraintVertical_weight="2" | ||
| 39 | tools:text="@string/welcome" /> | 42 | tools:text="@string/welcome" /> |
| 40 | 43 | ||
| 41 | <com.google.android.material.textview.MaterialTextView | 44 | <com.google.android.material.textview.MaterialTextView |
| 42 | style="@style/TextAppearance.Material3.TitleLarge" | ||
| 43 | android:id="@+id/text_description" | 45 | android:id="@+id/text_description" |
| 44 | android:layout_width="match_parent" | 46 | style="@style/TextAppearance.Material3.TitleLarge" |
| 45 | android:layout_height="wrap_content" | 47 | android:layout_width="0dp" |
| 46 | android:layout_marginTop="16dp" | 48 | android:layout_height="0dp" |
| 47 | android:paddingHorizontal="32dp" | 49 | android:gravity="center" |
| 48 | android:textAlignment="center" | 50 | android:textSize="20sp" |
| 49 | android:textSize="26sp" | 51 | android:paddingHorizontal="16dp" |
| 50 | app:lineHeight="40sp" | 52 | app:layout_constraintBottom_toTopOf="@+id/button_action" |
| 53 | app:layout_constraintEnd_toEndOf="parent" | ||
| 54 | app:layout_constraintStart_toStartOf="parent" | ||
| 55 | app:layout_constraintTop_toBottomOf="@+id/text_title" | ||
| 56 | app:layout_constraintVertical_weight="2" | ||
| 57 | app:lineHeight="30sp" | ||
| 51 | tools:text="@string/welcome_description" /> | 58 | tools:text="@string/welcome_description" /> |
| 52 | 59 | ||
| 60 | <com.google.android.material.textview.MaterialTextView | ||
| 61 | android:id="@+id/text_confirmation" | ||
| 62 | style="@style/TextAppearance.Material3.TitleLarge" | ||
| 63 | android:layout_width="0dp" | ||
| 64 | android:layout_height="0dp" | ||
| 65 | android:paddingHorizontal="16dp" | ||
| 66 | android:paddingBottom="20dp" | ||
| 67 | android:gravity="center" | ||
| 68 | android:textSize="30sp" | ||
| 69 | android:visibility="invisible" | ||
| 70 | android:text="@string/step_complete" | ||
| 71 | android:textStyle="bold" | ||
| 72 | app:layout_constraintBottom_toBottomOf="parent" | ||
| 73 | app:layout_constraintEnd_toEndOf="parent" | ||
| 74 | app:layout_constraintStart_toStartOf="parent" | ||
| 75 | app:layout_constraintTop_toBottomOf="@+id/text_description" | ||
| 76 | app:layout_constraintVertical_weight="1" | ||
| 77 | app:lineHeight="30sp" /> | ||
| 78 | |||
| 53 | <com.google.android.material.button.MaterialButton | 79 | <com.google.android.material.button.MaterialButton |
| 54 | android:id="@+id/button_action" | 80 | android:id="@+id/button_action" |
| 55 | android:layout_width="wrap_content" | 81 | android:layout_width="wrap_content" |
| 56 | android:layout_height="56dp" | 82 | android:layout_height="56dp" |
| 57 | android:layout_marginTop="32dp" | 83 | android:layout_marginTop="16dp" |
| 84 | android:layout_marginBottom="48dp" | ||
| 58 | android:textSize="20sp" | 85 | android:textSize="20sp" |
| 59 | app:iconSize="24sp" | ||
| 60 | app:iconGravity="end" | 86 | app:iconGravity="end" |
| 87 | app:iconSize="24sp" | ||
| 88 | app:layout_constraintBottom_toBottomOf="parent" | ||
| 89 | app:layout_constraintEnd_toEndOf="parent" | ||
| 90 | app:layout_constraintStart_toStartOf="parent" | ||
| 91 | app:layout_constraintTop_toBottomOf="@+id/text_description" | ||
| 61 | tools:text="Get started" /> | 92 | tools:text="Get started" /> |
| 62 | 93 | ||
| 63 | </LinearLayout> | 94 | </androidx.constraintlayout.widget.ConstraintLayout> |
| 64 | 95 | ||
| 65 | </LinearLayout> | 96 | </LinearLayout> |
diff --git a/src/android/app/src/main/res/layout/card_home_option.xml b/src/android/app/src/main/res/layout/card_home_option.xml index dc289db17..f9f1d89fb 100644 --- a/src/android/app/src/main/res/layout/card_home_option.xml +++ b/src/android/app/src/main/res/layout/card_home_option.xml | |||
| @@ -53,6 +53,23 @@ | |||
| 53 | android:layout_marginTop="5dp" | 53 | android:layout_marginTop="5dp" |
| 54 | tools:text="@string/install_prod_keys_description" /> | 54 | tools:text="@string/install_prod_keys_description" /> |
| 55 | 55 | ||
| 56 | <com.google.android.material.textview.MaterialTextView | ||
| 57 | style="@style/TextAppearance.Material3.LabelMedium" | ||
| 58 | android:id="@+id/option_detail" | ||
| 59 | android:layout_width="match_parent" | ||
| 60 | android:layout_height="wrap_content" | ||
| 61 | android:textAlignment="viewStart" | ||
| 62 | android:textSize="14sp" | ||
| 63 | android:textStyle="bold" | ||
| 64 | android:singleLine="true" | ||
| 65 | android:marqueeRepeatLimit="marquee_forever" | ||
| 66 | android:ellipsize="none" | ||
| 67 | android:requiresFadingEdge="horizontal" | ||
| 68 | android:layout_marginTop="5dp" | ||
| 69 | android:visibility="gone" | ||
| 70 | tools:visibility="visible" | ||
| 71 | tools:text="/tree/primary:Games" /> | ||
| 72 | |||
| 56 | </LinearLayout> | 73 | </LinearLayout> |
| 57 | 74 | ||
| 58 | </LinearLayout> | 75 | </LinearLayout> |
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 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | 1 | <?xml version="1.0" encoding="utf-8"?> |
| 2 | <androidx.constraintlayout.widget.ConstraintLayout | 2 | <RelativeLayout |
| 3 | xmlns:android="http://schemas.android.com/apk/res/android" | 3 | xmlns:android="http://schemas.android.com/apk/res/android" |
| 4 | xmlns:app="http://schemas.android.com/apk/res-auto" | 4 | xmlns:app="http://schemas.android.com/apk/res-auto" |
| 5 | android:id="@+id/setup_root" | 5 | android:id="@+id/setup_root" |
| @@ -8,35 +8,39 @@ | |||
| 8 | 8 | ||
| 9 | <androidx.viewpager2.widget.ViewPager2 | 9 | <androidx.viewpager2.widget.ViewPager2 |
| 10 | android:id="@+id/viewPager2" | 10 | android:id="@+id/viewPager2" |
| 11 | android:layout_width="0dp" | 11 | android:layout_width="match_parent" |
| 12 | android:layout_height="0dp" | ||
| 13 | android:clipToPadding="false" | ||
| 14 | android:layout_marginBottom="16dp" | ||
| 15 | app:layout_constraintBottom_toTopOf="@+id/button_next" | ||
| 16 | app:layout_constraintEnd_toEndOf="parent" | ||
| 17 | app:layout_constraintStart_toStartOf="parent" | ||
| 18 | app:layout_constraintTop_toTopOf="parent" /> | ||
| 19 | |||
| 20 | <com.google.android.material.button.MaterialButton | ||
| 21 | style="@style/Widget.Material3.Button.TextButton" | ||
| 22 | android:id="@+id/button_next" | ||
| 23 | android:layout_width="wrap_content" | ||
| 24 | android:layout_height="wrap_content" | 12 | android:layout_height="wrap_content" |
| 25 | android:layout_margin="12dp" | 13 | android:layout_above="@+id/constraint_buttons" |
| 26 | android:text="@string/next" | 14 | android:layout_alignParentTop="true" |
| 27 | android:visibility="invisible" | 15 | android:clipToPadding="false" /> |
| 28 | app:layout_constraintBottom_toBottomOf="parent" | ||
| 29 | app:layout_constraintEnd_toEndOf="parent" /> | ||
| 30 | 16 | ||
| 31 | <com.google.android.material.button.MaterialButton | 17 | <androidx.constraintlayout.widget.ConstraintLayout |
| 32 | style="@style/Widget.Material3.Button.TextButton" | 18 | android:id="@+id/constraint_buttons" |
| 33 | android:id="@+id/button_back" | 19 | android:layout_width="match_parent" |
| 34 | android:layout_width="wrap_content" | ||
| 35 | android:layout_height="wrap_content" | 20 | android:layout_height="wrap_content" |
| 36 | android:layout_margin="12dp" | 21 | android:layout_margin="8dp" |
| 37 | android:text="@string/back" | 22 | android:layout_alignParentBottom="true"> |
| 38 | android:visibility="invisible" | 23 | |
| 39 | app:layout_constraintBottom_toBottomOf="parent" | 24 | <com.google.android.material.button.MaterialButton |
| 40 | app:layout_constraintStart_toStartOf="parent" /> | 25 | android:id="@+id/button_next" |
| 26 | style="@style/Widget.Material3.Button.TextButton" | ||
| 27 | android:layout_width="wrap_content" | ||
| 28 | android:layout_height="wrap_content" | ||
| 29 | android:text="@string/next" | ||
| 30 | android:visibility="invisible" | ||
| 31 | app:layout_constraintBottom_toBottomOf="parent" | ||
| 32 | app:layout_constraintEnd_toEndOf="parent" /> | ||
| 33 | |||
| 34 | <com.google.android.material.button.MaterialButton | ||
| 35 | android:id="@+id/button_back" | ||
| 36 | style="@style/Widget.Material3.Button.TextButton" | ||
| 37 | android:layout_width="wrap_content" | ||
| 38 | android:layout_height="wrap_content" | ||
| 39 | android:text="@string/back" | ||
| 40 | android:visibility="invisible" | ||
| 41 | app:layout_constraintBottom_toBottomOf="parent" | ||
| 42 | app:layout_constraintStart_toStartOf="parent" /> | ||
| 43 | |||
| 44 | </androidx.constraintlayout.widget.ConstraintLayout> | ||
| 41 | 45 | ||
| 42 | </androidx.constraintlayout.widget.ConstraintLayout> | 46 | </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 @@ | |||
| 21 | app:layout_constraintVertical_chainStyle="spread" | 21 | app:layout_constraintVertical_chainStyle="spread" |
| 22 | app:layout_constraintWidth_max="220dp" | 22 | app:layout_constraintWidth_max="220dp" |
| 23 | app:layout_constraintWidth_min="110dp" | 23 | app:layout_constraintWidth_min="110dp" |
| 24 | app:layout_constraintVertical_weight="3" /> | 24 | app:layout_constraintVertical_weight="3" |
| 25 | tools:src="@drawable/ic_notification" /> | ||
| 25 | 26 | ||
| 26 | <com.google.android.material.textview.MaterialTextView | 27 | <com.google.android.material.textview.MaterialTextView |
| 27 | android:id="@+id/text_title" | 28 | android:id="@+id/text_title" |
| 28 | style="@style/TextAppearance.Material3.DisplayMedium" | 29 | style="@style/TextAppearance.Material3.DisplaySmall" |
| 29 | android:layout_width="0dp" | 30 | android:layout_width="0dp" |
| 30 | android:layout_height="0dp" | 31 | android:layout_height="0dp" |
| 31 | android:textAlignment="center" | 32 | android:textAlignment="center" |
| @@ -44,23 +45,42 @@ | |||
| 44 | android:layout_width="0dp" | 45 | android:layout_width="0dp" |
| 45 | android:layout_height="0dp" | 46 | android:layout_height="0dp" |
| 46 | android:textAlignment="center" | 47 | android:textAlignment="center" |
| 47 | android:textSize="26sp" | 48 | android:textSize="20sp" |
| 48 | android:paddingHorizontal="16dp" | 49 | android:paddingHorizontal="16dp" |
| 49 | app:layout_constraintBottom_toTopOf="@+id/button_action" | 50 | app:layout_constraintBottom_toTopOf="@+id/button_action" |
| 50 | app:layout_constraintEnd_toEndOf="parent" | 51 | app:layout_constraintEnd_toEndOf="parent" |
| 51 | app:layout_constraintStart_toStartOf="parent" | 52 | app:layout_constraintStart_toStartOf="parent" |
| 52 | app:layout_constraintTop_toBottomOf="@+id/text_title" | 53 | app:layout_constraintTop_toBottomOf="@+id/text_title" |
| 53 | app:layout_constraintVertical_weight="2" | 54 | app:layout_constraintVertical_weight="2" |
| 54 | app:lineHeight="40sp" | 55 | app:lineHeight="30sp" |
| 55 | tools:text="@string/welcome_description" /> | 56 | tools:text="@string/welcome_description" /> |
| 56 | 57 | ||
| 58 | <com.google.android.material.textview.MaterialTextView | ||
| 59 | android:id="@+id/text_confirmation" | ||
| 60 | style="@style/TextAppearance.Material3.TitleLarge" | ||
| 61 | android:layout_width="wrap_content" | ||
| 62 | android:layout_height="0dp" | ||
| 63 | android:paddingHorizontal="16dp" | ||
| 64 | android:paddingTop="24dp" | ||
| 65 | android:textAlignment="center" | ||
| 66 | android:textSize="30sp" | ||
| 67 | android:visibility="invisible" | ||
| 68 | android:text="@string/step_complete" | ||
| 69 | android:textStyle="bold" | ||
| 70 | app:layout_constraintBottom_toBottomOf="parent" | ||
| 71 | app:layout_constraintEnd_toEndOf="parent" | ||
| 72 | app:layout_constraintStart_toStartOf="parent" | ||
| 73 | app:layout_constraintTop_toBottomOf="@+id/text_description" | ||
| 74 | app:layout_constraintVertical_weight="1" | ||
| 75 | app:lineHeight="30sp" /> | ||
| 76 | |||
| 57 | <com.google.android.material.button.MaterialButton | 77 | <com.google.android.material.button.MaterialButton |
| 58 | android:id="@+id/button_action" | 78 | android:id="@+id/button_action" |
| 59 | android:layout_width="wrap_content" | 79 | android:layout_width="wrap_content" |
| 60 | android:layout_height="56dp" | 80 | android:layout_height="56dp" |
| 61 | android:textSize="20sp" | ||
| 62 | android:layout_marginTop="16dp" | 81 | android:layout_marginTop="16dp" |
| 63 | android:layout_marginBottom="48dp" | 82 | android:layout_marginBottom="48dp" |
| 83 | android:textSize="20sp" | ||
| 64 | app:iconGravity="end" | 84 | app:iconGravity="end" |
| 65 | app:iconSize="24sp" | 85 | app:iconSize="24sp" |
| 66 | app:layout_constraintBottom_toBottomOf="parent" | 86 | 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 82359f358..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 @@ | |||
| 29 | <string name="back">Back</string> | 29 | <string name="back">Back</string> |
| 30 | <string name="add_games">Add Games</string> | 30 | <string name="add_games">Add Games</string> |
| 31 | <string name="add_games_description">Select your games folder</string> | 31 | <string name="add_games_description">Select your games folder</string> |
| 32 | <string name="step_complete">Complete!</string> | ||
| 32 | 33 | ||
| 33 | <!-- Home strings --> | 34 | <!-- Home strings --> |
| 34 | <string name="home_games">Games</string> | 35 | <string name="home_games">Games</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 @@ | |||
| 3 | 3 | ||
| 4 | #pragma once | 4 | #pragma once |
| 5 | 5 | ||
| 6 | #include <bit> | ||
| 6 | #include <cstddef> | 7 | #include <cstddef> |
| 7 | #include <new> | 8 | #include <new> |
| 8 | #include <type_traits> | 9 | #include <type_traits> |
| @@ -10,8 +11,10 @@ | |||
| 10 | namespace Common { | 11 | namespace Common { |
| 11 | 12 | ||
| 12 | template <typename T> | 13 | template <typename T> |
| 13 | requires std::is_unsigned_v<T> | 14 | requires std::is_integral_v<T> |
| 14 | [[nodiscard]] constexpr T AlignUp(T value, size_t size) { | 15 | [[nodiscard]] constexpr T AlignUp(T value_, size_t size) { |
| 16 | using U = typename std::make_unsigned_t<T>; | ||
| 17 | auto value{static_cast<U>(value_)}; | ||
| 15 | auto mod{static_cast<T>(value % size)}; | 18 | auto mod{static_cast<T>(value % size)}; |
| 16 | value -= mod; | 19 | value -= mod; |
| 17 | return static_cast<T>(mod == T{0} ? value : value + size); | 20 | return static_cast<T>(mod == T{0} ? value : value + size); |
| @@ -24,8 +27,10 @@ template <typename T> | |||
| 24 | } | 27 | } |
| 25 | 28 | ||
| 26 | template <typename T> | 29 | template <typename T> |
| 27 | requires std::is_unsigned_v<T> | 30 | requires std::is_integral_v<T> |
| 28 | [[nodiscard]] constexpr T AlignDown(T value, size_t size) { | 31 | [[nodiscard]] constexpr T AlignDown(T value_, size_t size) { |
| 32 | using U = typename std::make_unsigned_t<T>; | ||
| 33 | const auto value{static_cast<U>(value_)}; | ||
| 29 | return static_cast<T>(value - value % size); | 34 | return static_cast<T>(value - value % size); |
| 30 | } | 35 | } |
| 31 | 36 | ||
| @@ -55,6 +60,30 @@ template <typename T, typename U> | |||
| 55 | return (x + (y - 1)) / y; | 60 | return (x + (y - 1)) / y; |
| 56 | } | 61 | } |
| 57 | 62 | ||
| 63 | template <typename T> | ||
| 64 | requires std::is_integral_v<T> | ||
| 65 | [[nodiscard]] constexpr T LeastSignificantOneBit(T x) { | ||
| 66 | return x & ~(x - 1); | ||
| 67 | } | ||
| 68 | |||
| 69 | template <typename T> | ||
| 70 | requires std::is_integral_v<T> | ||
| 71 | [[nodiscard]] constexpr T ResetLeastSignificantOneBit(T x) { | ||
| 72 | return x & (x - 1); | ||
| 73 | } | ||
| 74 | |||
| 75 | template <typename T> | ||
| 76 | requires std::is_integral_v<T> | ||
| 77 | [[nodiscard]] constexpr bool IsPowerOfTwo(T x) { | ||
| 78 | return x > 0 && ResetLeastSignificantOneBit(x) == 0; | ||
| 79 | } | ||
| 80 | |||
| 81 | template <typename T> | ||
| 82 | requires std::is_integral_v<T> | ||
| 83 | [[nodiscard]] constexpr T FloorPowerOfTwo(T x) { | ||
| 84 | return T{1} << (sizeof(T) * 8 - std::countl_zero(x) - 1); | ||
| 85 | } | ||
| 86 | |||
| 58 | template <typename T, size_t Align = 16> | 87 | template <typename T, size_t Align = 16> |
| 59 | class AlignmentAllocator { | 88 | class AlignmentAllocator { |
| 60 | public: | 89 | 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 | |||
| 71 | return uncompressed; | 71 | return uncompressed; |
| 72 | } | 72 | } |
| 73 | 73 | ||
| 74 | int DecompressDataLZ4(void* dst, size_t dst_size, const void* src, size_t src_size) { | ||
| 75 | // This is just a thin wrapper around LZ4. | ||
| 76 | return LZ4_decompress_safe(reinterpret_cast<const char*>(src), reinterpret_cast<char*>(dst), | ||
| 77 | static_cast<int>(src_size), static_cast<int>(dst_size)); | ||
| 78 | } | ||
| 79 | |||
| 74 | } // namespace Common::Compression | 80 | } // 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 { | |||
| 56 | [[nodiscard]] std::vector<u8> DecompressDataLZ4(std::span<const u8> compressed, | 56 | [[nodiscard]] std::vector<u8> DecompressDataLZ4(std::span<const u8> compressed, |
| 57 | std::size_t uncompressed_size); | 57 | std::size_t uncompressed_size); |
| 58 | 58 | ||
| 59 | [[nodiscard]] int DecompressDataLZ4(void* dst, size_t dst_size, const void* src, size_t src_size); | ||
| 60 | |||
| 59 | } // namespace Common::Compression | 61 | } // namespace Common::Compression |
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 | |||
| 37 | debugger/gdbstub.h | 37 | debugger/gdbstub.h |
| 38 | device_memory.cpp | 38 | device_memory.cpp |
| 39 | device_memory.h | 39 | device_memory.h |
| 40 | file_sys/fssystem/fs_i_storage.h | ||
| 41 | file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.cpp | ||
| 42 | file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.h | ||
| 43 | file_sys/fssystem/fssystem_aes_ctr_storage.cpp | ||
| 44 | file_sys/fssystem/fssystem_aes_ctr_storage.h | ||
| 45 | file_sys/fssystem/fssystem_aes_xts_storage.cpp | ||
| 46 | file_sys/fssystem/fssystem_aes_xts_storage.h | ||
| 47 | file_sys/fssystem/fssystem_alignment_matching_storage.h | ||
| 48 | file_sys/fssystem/fssystem_alignment_matching_storage_impl.cpp | ||
| 49 | file_sys/fssystem/fssystem_alignment_matching_storage_impl.h | ||
| 50 | file_sys/fssystem/fssystem_bucket_tree.cpp | ||
| 51 | file_sys/fssystem/fssystem_bucket_tree.h | ||
| 52 | file_sys/fssystem/fssystem_bucket_tree_utils.h | ||
| 53 | file_sys/fssystem/fssystem_compressed_storage.h | ||
| 54 | file_sys/fssystem/fssystem_compression_common.h | ||
| 55 | file_sys/fssystem/fssystem_compression_configuration.cpp | ||
| 56 | file_sys/fssystem/fssystem_compression_configuration.h | ||
| 57 | file_sys/fssystem/fssystem_crypto_configuration.cpp | ||
| 58 | file_sys/fssystem/fssystem_crypto_configuration.h | ||
| 59 | file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.cpp | ||
| 60 | file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h | ||
| 61 | file_sys/fssystem/fssystem_hierarchical_sha256_storage.cpp | ||
| 62 | file_sys/fssystem/fssystem_hierarchical_sha256_storage.h | ||
| 63 | file_sys/fssystem/fssystem_indirect_storage.cpp | ||
| 64 | file_sys/fssystem/fssystem_indirect_storage.h | ||
| 65 | file_sys/fssystem/fssystem_integrity_romfs_storage.cpp | ||
| 66 | file_sys/fssystem/fssystem_integrity_romfs_storage.h | ||
| 67 | file_sys/fssystem/fssystem_integrity_verification_storage.cpp | ||
| 68 | file_sys/fssystem/fssystem_integrity_verification_storage.h | ||
| 69 | file_sys/fssystem/fssystem_memory_resource_buffer_hold_storage.h | ||
| 70 | file_sys/fssystem/fssystem_nca_file_system_driver.cpp | ||
| 71 | file_sys/fssystem/fssystem_nca_file_system_driver.h | ||
| 72 | file_sys/fssystem/fssystem_nca_header.cpp | ||
| 73 | file_sys/fssystem/fssystem_nca_header.h | ||
| 74 | file_sys/fssystem/fssystem_nca_reader.cpp | ||
| 75 | file_sys/fssystem/fssystem_pooled_buffer.cpp | ||
| 76 | file_sys/fssystem/fssystem_pooled_buffer.h | ||
| 77 | file_sys/fssystem/fssystem_sparse_storage.cpp | ||
| 78 | file_sys/fssystem/fssystem_sparse_storage.h | ||
| 79 | file_sys/fssystem/fssystem_switch_storage.h | ||
| 80 | file_sys/fssystem/fssystem_utility.cpp | ||
| 81 | file_sys/fssystem/fssystem_utility.h | ||
| 82 | file_sys/fssystem/fs_types.h | ||
| 40 | file_sys/bis_factory.cpp | 83 | file_sys/bis_factory.cpp |
| 41 | file_sys/bis_factory.h | 84 | file_sys/bis_factory.h |
| 42 | file_sys/card_image.cpp | 85 | file_sys/card_image.cpp |
| @@ -57,8 +100,6 @@ add_library(core STATIC | |||
| 57 | file_sys/mode.h | 100 | file_sys/mode.h |
| 58 | file_sys/nca_metadata.cpp | 101 | file_sys/nca_metadata.cpp |
| 59 | file_sys/nca_metadata.h | 102 | file_sys/nca_metadata.h |
| 60 | file_sys/nca_patch.cpp | ||
| 61 | file_sys/nca_patch.h | ||
| 62 | file_sys/partition_filesystem.cpp | 103 | file_sys/partition_filesystem.cpp |
| 63 | file_sys/partition_filesystem.h | 104 | file_sys/partition_filesystem.h |
| 64 | file_sys/patch_manager.cpp | 105 | 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 | |||
| 263 | 263 | ||
| 264 | std::vector<u8> mem(size); | 264 | std::vector<u8> mem(size); |
| 265 | if (system.ApplicationMemory().ReadBlock(addr, mem.data(), size)) { | 265 | if (system.ApplicationMemory().ReadBlock(addr, mem.data(), size)) { |
| 266 | // Restore any bytes belonging to replaced instructions. | ||
| 267 | auto it = replaced_instructions.lower_bound(addr); | ||
| 268 | for (; it != replaced_instructions.end() && it->first < addr + size; it++) { | ||
| 269 | // Get the bytes of the instruction we previously replaced. | ||
| 270 | const u32 original_bytes = it->second; | ||
| 271 | |||
| 272 | // Calculate where to start writing to the output buffer. | ||
| 273 | const size_t output_offset = it->first - addr; | ||
| 274 | |||
| 275 | // Calculate how many bytes to write. | ||
| 276 | // The loop condition ensures output_offset < size. | ||
| 277 | const size_t n = std::min<size_t>(size - output_offset, sizeof(u32)); | ||
| 278 | |||
| 279 | // Write the bytes to the output buffer. | ||
| 280 | std::memcpy(mem.data() + output_offset, &original_bytes, n); | ||
| 281 | } | ||
| 282 | |||
| 266 | SendReply(Common::HexToString(mem)); | 283 | SendReply(Common::HexToString(mem)); |
| 267 | } else { | 284 | } else { |
| 268 | SendReply(GDB_STUB_REPLY_ERR); | 285 | 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..8b9a4fc5a 100644 --- a/src/core/file_sys/card_image.cpp +++ b/src/core/file_sys/card_image.cpp | |||
| @@ -31,13 +31,9 @@ XCI::XCI(VirtualFile file_, u64 program_id, size_t program_index) | |||
| 31 | : file(std::move(file_)), program_nca_status{Loader::ResultStatus::ErrorXCIMissingProgramNCA}, | 31 | : file(std::move(file_)), program_nca_status{Loader::ResultStatus::ErrorXCIMissingProgramNCA}, |
| 32 | partitions(partition_names.size()), | 32 | partitions(partition_names.size()), |
| 33 | partitions_raw(partition_names.size()), keys{Core::Crypto::KeyManager::Instance()} { | 33 | partitions_raw(partition_names.size()), keys{Core::Crypto::KeyManager::Instance()} { |
| 34 | if (file->ReadObject(&header) != sizeof(GamecardHeader)) { | 34 | const auto header_status = TryReadHeader(); |
| 35 | status = Loader::ResultStatus::ErrorBadXCIHeader; | 35 | if (header_status != Loader::ResultStatus::Success) { |
| 36 | return; | 36 | status = header_status; |
| 37 | } | ||
| 38 | |||
| 39 | if (header.magic != Common::MakeMagic('H', 'E', 'A', 'D')) { | ||
| 40 | status = Loader::ResultStatus::ErrorBadXCIHeader; | ||
| 41 | return; | 37 | return; |
| 42 | } | 38 | } |
| 43 | 39 | ||
| @@ -183,9 +179,9 @@ u32 XCI::GetSystemUpdateVersion() { | |||
| 183 | } | 179 | } |
| 184 | 180 | ||
| 185 | for (const auto& update_file : update->GetFiles()) { | 181 | for (const auto& update_file : update->GetFiles()) { |
| 186 | NCA nca{update_file, nullptr, 0}; | 182 | NCA nca{update_file}; |
| 187 | 183 | ||
| 188 | if (nca.GetStatus() != Loader::ResultStatus::Success) { | 184 | if (nca.GetStatus() != Loader::ResultStatus::Success || nca.GetSubdirectories().empty()) { |
| 189 | continue; | 185 | continue; |
| 190 | } | 186 | } |
| 191 | 187 | ||
| @@ -296,7 +292,7 @@ Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) { | |||
| 296 | continue; | 292 | continue; |
| 297 | } | 293 | } |
| 298 | 294 | ||
| 299 | auto nca = std::make_shared<NCA>(partition_file, nullptr, 0); | 295 | auto nca = std::make_shared<NCA>(partition_file); |
| 300 | if (nca->IsUpdate()) { | 296 | if (nca->IsUpdate()) { |
| 301 | continue; | 297 | continue; |
| 302 | } | 298 | } |
| @@ -316,6 +312,44 @@ Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) { | |||
| 316 | return Loader::ResultStatus::Success; | 312 | return Loader::ResultStatus::Success; |
| 317 | } | 313 | } |
| 318 | 314 | ||
| 315 | Loader::ResultStatus XCI::TryReadHeader() { | ||
| 316 | constexpr size_t CardInitialDataRegionSize = 0x1000; | ||
| 317 | |||
| 318 | // Define the function we'll use to determine if we read a valid header. | ||
| 319 | const auto ReadCardHeader = [&]() { | ||
| 320 | // Ensure we can read the entire header. If we can't, we can't read the card image. | ||
| 321 | if (file->ReadObject(&header) != sizeof(GamecardHeader)) { | ||
| 322 | return Loader::ResultStatus::ErrorBadXCIHeader; | ||
| 323 | } | ||
| 324 | |||
| 325 | // Ensure the header magic matches. If it doesn't, this isn't a card image header. | ||
| 326 | if (header.magic != Common::MakeMagic('H', 'E', 'A', 'D')) { | ||
| 327 | return Loader::ResultStatus::ErrorBadXCIHeader; | ||
| 328 | } | ||
| 329 | |||
| 330 | // We read a card image header. | ||
| 331 | return Loader::ResultStatus::Success; | ||
| 332 | }; | ||
| 333 | |||
| 334 | // Try to read the header directly. | ||
| 335 | if (ReadCardHeader() == Loader::ResultStatus::Success) { | ||
| 336 | return Loader::ResultStatus::Success; | ||
| 337 | } | ||
| 338 | |||
| 339 | // Get the size of the file. | ||
| 340 | const size_t card_image_size = file->GetSize(); | ||
| 341 | |||
| 342 | // If we are large enough to have a key area, offset past the key area and retry. | ||
| 343 | if (card_image_size >= CardInitialDataRegionSize) { | ||
| 344 | file = std::make_shared<OffsetVfsFile>(file, card_image_size - CardInitialDataRegionSize, | ||
| 345 | CardInitialDataRegionSize); | ||
| 346 | return ReadCardHeader(); | ||
| 347 | } | ||
| 348 | |||
| 349 | // We had no header and aren't large enough to have a key area, so this can't be parsed. | ||
| 350 | return Loader::ResultStatus::ErrorBadXCIHeader; | ||
| 351 | } | ||
| 352 | |||
| 319 | u8 XCI::GetFormatVersion() { | 353 | u8 XCI::GetFormatVersion() { |
| 320 | return GetLogoPartition() == nullptr ? 0x1 : 0x2; | 354 | return GetLogoPartition() == nullptr ? 0x1 : 0x2; |
| 321 | } | 355 | } |
diff --git a/src/core/file_sys/card_image.h b/src/core/file_sys/card_image.h index 1283f8216..9886123e7 100644 --- a/src/core/file_sys/card_image.h +++ b/src/core/file_sys/card_image.h | |||
| @@ -128,6 +128,7 @@ public: | |||
| 128 | 128 | ||
| 129 | private: | 129 | private: |
| 130 | Loader::ResultStatus AddNCAFromPartition(XCIPartition part); | 130 | Loader::ResultStatus AddNCAFromPartition(XCIPartition part); |
| 131 | Loader::ResultStatus TryReadHeader(); | ||
| 131 | 132 | ||
| 132 | VirtualFile file; | 133 | VirtualFile file; |
| 133 | GamecardHeader header{}; | 134 | GamecardHeader header{}; |
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 @@ | |||
| 12 | #include "core/crypto/ctr_encryption_layer.h" | 12 | #include "core/crypto/ctr_encryption_layer.h" |
| 13 | #include "core/crypto/key_manager.h" | 13 | #include "core/crypto/key_manager.h" |
| 14 | #include "core/file_sys/content_archive.h" | 14 | #include "core/file_sys/content_archive.h" |
| 15 | #include "core/file_sys/nca_patch.h" | ||
| 16 | #include "core/file_sys/partition_filesystem.h" | 15 | #include "core/file_sys/partition_filesystem.h" |
| 17 | #include "core/file_sys/vfs_offset.h" | 16 | #include "core/file_sys/vfs_offset.h" |
| 18 | #include "core/loader/loader.h" | 17 | #include "core/loader/loader.h" |
| 19 | 18 | ||
| 20 | namespace FileSys { | 19 | #include "core/file_sys/fssystem/fssystem_compression_configuration.h" |
| 20 | #include "core/file_sys/fssystem/fssystem_crypto_configuration.h" | ||
| 21 | #include "core/file_sys/fssystem/fssystem_nca_file_system_driver.h" | ||
| 21 | 22 | ||
| 22 | // Media offsets in headers are stored divided by 512. Mult. by this to get real offset. | 23 | namespace FileSys { |
| 23 | constexpr u64 MEDIA_OFFSET_MULTIPLIER = 0x200; | ||
| 24 | |||
| 25 | constexpr u64 SECTION_HEADER_SIZE = 0x200; | ||
| 26 | constexpr u64 SECTION_HEADER_OFFSET = 0x400; | ||
| 27 | |||
| 28 | constexpr u32 IVFC_MAX_LEVEL = 6; | ||
| 29 | |||
| 30 | enum class NCASectionFilesystemType : u8 { | ||
| 31 | PFS0 = 0x2, | ||
| 32 | ROMFS = 0x3, | ||
| 33 | }; | ||
| 34 | |||
| 35 | struct IVFCLevel { | ||
| 36 | u64_le offset; | ||
| 37 | u64_le size; | ||
| 38 | u32_le block_size; | ||
| 39 | u32_le reserved; | ||
| 40 | }; | ||
| 41 | static_assert(sizeof(IVFCLevel) == 0x18, "IVFCLevel has incorrect size."); | ||
| 42 | |||
| 43 | struct IVFCHeader { | ||
| 44 | u32_le magic; | ||
| 45 | u32_le magic_number; | ||
| 46 | INSERT_PADDING_BYTES_NOINIT(8); | ||
| 47 | std::array<IVFCLevel, 6> levels; | ||
| 48 | INSERT_PADDING_BYTES_NOINIT(64); | ||
| 49 | }; | ||
| 50 | static_assert(sizeof(IVFCHeader) == 0xE0, "IVFCHeader has incorrect size."); | ||
| 51 | |||
| 52 | struct NCASectionHeaderBlock { | ||
| 53 | INSERT_PADDING_BYTES_NOINIT(3); | ||
| 54 | NCASectionFilesystemType filesystem_type; | ||
| 55 | NCASectionCryptoType crypto_type; | ||
| 56 | INSERT_PADDING_BYTES_NOINIT(3); | ||
| 57 | }; | ||
| 58 | static_assert(sizeof(NCASectionHeaderBlock) == 0x8, "NCASectionHeaderBlock has incorrect size."); | ||
| 59 | |||
| 60 | struct NCABucketInfo { | ||
| 61 | u64 table_offset; | ||
| 62 | u64 table_size; | ||
| 63 | std::array<u8, 0x10> table_header; | ||
| 64 | }; | ||
| 65 | static_assert(sizeof(NCABucketInfo) == 0x20, "NCABucketInfo has incorrect size."); | ||
| 66 | |||
| 67 | struct NCASparseInfo { | ||
| 68 | NCABucketInfo bucket; | ||
| 69 | u64 physical_offset; | ||
| 70 | u16 generation; | ||
| 71 | INSERT_PADDING_BYTES_NOINIT(0x6); | ||
| 72 | }; | ||
| 73 | static_assert(sizeof(NCASparseInfo) == 0x30, "NCASparseInfo has incorrect size."); | ||
| 74 | |||
| 75 | struct NCACompressionInfo { | ||
| 76 | NCABucketInfo bucket; | ||
| 77 | INSERT_PADDING_BYTES_NOINIT(0x8); | ||
| 78 | }; | ||
| 79 | static_assert(sizeof(NCACompressionInfo) == 0x28, "NCACompressionInfo has incorrect size."); | ||
| 80 | |||
| 81 | struct NCASectionRaw { | ||
| 82 | NCASectionHeaderBlock header; | ||
| 83 | std::array<u8, 0x138> block_data; | ||
| 84 | std::array<u8, 0x8> section_ctr; | ||
| 85 | NCASparseInfo sparse_info; | ||
| 86 | NCACompressionInfo compression_info; | ||
| 87 | INSERT_PADDING_BYTES_NOINIT(0x60); | ||
| 88 | }; | ||
| 89 | static_assert(sizeof(NCASectionRaw) == 0x200, "NCASectionRaw has incorrect size."); | ||
| 90 | |||
| 91 | struct PFS0Superblock { | ||
| 92 | NCASectionHeaderBlock header_block; | ||
| 93 | std::array<u8, 0x20> hash; | ||
| 94 | u32_le size; | ||
| 95 | INSERT_PADDING_BYTES_NOINIT(4); | ||
| 96 | u64_le hash_table_offset; | ||
| 97 | u64_le hash_table_size; | ||
| 98 | u64_le pfs0_header_offset; | ||
| 99 | u64_le pfs0_size; | ||
| 100 | INSERT_PADDING_BYTES_NOINIT(0x1B0); | ||
| 101 | }; | ||
| 102 | static_assert(sizeof(PFS0Superblock) == 0x200, "PFS0Superblock has incorrect size."); | ||
| 103 | |||
| 104 | struct RomFSSuperblock { | ||
| 105 | NCASectionHeaderBlock header_block; | ||
| 106 | IVFCHeader ivfc; | ||
| 107 | INSERT_PADDING_BYTES_NOINIT(0x118); | ||
| 108 | }; | ||
| 109 | static_assert(sizeof(RomFSSuperblock) == 0x200, "RomFSSuperblock has incorrect size."); | ||
| 110 | |||
| 111 | struct BKTRHeader { | ||
| 112 | u64_le offset; | ||
| 113 | u64_le size; | ||
| 114 | u32_le magic; | ||
| 115 | INSERT_PADDING_BYTES_NOINIT(0x4); | ||
| 116 | u32_le number_entries; | ||
| 117 | INSERT_PADDING_BYTES_NOINIT(0x4); | ||
| 118 | }; | ||
| 119 | static_assert(sizeof(BKTRHeader) == 0x20, "BKTRHeader has incorrect size."); | ||
| 120 | |||
| 121 | struct BKTRSuperblock { | ||
| 122 | NCASectionHeaderBlock header_block; | ||
| 123 | IVFCHeader ivfc; | ||
| 124 | INSERT_PADDING_BYTES_NOINIT(0x18); | ||
| 125 | BKTRHeader relocation; | ||
| 126 | BKTRHeader subsection; | ||
| 127 | INSERT_PADDING_BYTES_NOINIT(0xC0); | ||
| 128 | }; | ||
| 129 | static_assert(sizeof(BKTRSuperblock) == 0x200, "BKTRSuperblock has incorrect size."); | ||
| 130 | |||
| 131 | union NCASectionHeader { | ||
| 132 | NCASectionRaw raw{}; | ||
| 133 | PFS0Superblock pfs0; | ||
| 134 | RomFSSuperblock romfs; | ||
| 135 | BKTRSuperblock bktr; | ||
| 136 | }; | ||
| 137 | static_assert(sizeof(NCASectionHeader) == 0x200, "NCASectionHeader has incorrect size."); | ||
| 138 | |||
| 139 | static bool IsValidNCA(const NCAHeader& header) { | ||
| 140 | // TODO(DarkLordZach): Add NCA2/NCA0 support. | ||
| 141 | return header.magic == Common::MakeMagic('N', 'C', 'A', '3'); | ||
| 142 | } | ||
| 143 | 24 | ||
| 144 | NCA::NCA(VirtualFile file_, VirtualFile bktr_base_romfs_, u64 bktr_base_ivfc_offset) | 25 | NCA::NCA(VirtualFile file_, const NCA* base_nca) |
| 145 | : file(std::move(file_)), | 26 | : file(std::move(file_)), keys{Core::Crypto::KeyManager::Instance()} { |
| 146 | bktr_base_romfs(std::move(bktr_base_romfs_)), keys{Core::Crypto::KeyManager::Instance()} { | ||
| 147 | if (file == nullptr) { | 27 | if (file == nullptr) { |
| 148 | status = Loader::ResultStatus::ErrorNullFile; | 28 | status = Loader::ResultStatus::ErrorNullFile; |
| 149 | return; | 29 | return; |
| 150 | } | 30 | } |
| 151 | 31 | ||
| 152 | if (sizeof(NCAHeader) != file->ReadObject(&header)) { | 32 | reader = std::make_shared<NcaReader>(); |
| 153 | LOG_ERROR(Loader, "File reader errored out during header read."); | 33 | if (Result rc = |
| 34 | reader->Initialize(file, GetCryptoConfiguration(), GetNcaCompressionConfiguration()); | ||
| 35 | R_FAILED(rc)) { | ||
| 36 | if (rc != ResultInvalidNcaSignature) { | ||
| 37 | LOG_ERROR(Loader, "File reader errored out during header read: {:#x}", | ||
| 38 | rc.GetInnerValue()); | ||
| 39 | } | ||
| 154 | status = Loader::ResultStatus::ErrorBadNCAHeader; | 40 | status = Loader::ResultStatus::ErrorBadNCAHeader; |
| 155 | return; | 41 | return; |
| 156 | } | 42 | } |
| 157 | 43 | ||
| 158 | if (!HandlePotentialHeaderDecryption()) { | 44 | RightsId rights_id{}; |
| 159 | return; | 45 | reader->GetRightsId(rights_id.data(), rights_id.size()); |
| 160 | } | 46 | if (rights_id != RightsId{}) { |
| 161 | 47 | // External decryption key required; provide it here. | |
| 162 | has_rights_id = std::ranges::any_of(header.rights_id, [](char c) { return c != '\0'; }); | 48 | const auto key_generation = std::max<s32>(reader->GetKeyGeneration(), 1) - 1; |
| 163 | |||
| 164 | const std::vector<NCASectionHeader> sections = ReadSectionHeaders(); | ||
| 165 | is_update = std::ranges::any_of(sections, [](const NCASectionHeader& nca_header) { | ||
| 166 | return nca_header.raw.header.crypto_type == NCASectionCryptoType::BKTR; | ||
| 167 | }); | ||
| 168 | |||
| 169 | if (!ReadSections(sections, bktr_base_ivfc_offset)) { | ||
| 170 | return; | ||
| 171 | } | ||
| 172 | |||
| 173 | status = Loader::ResultStatus::Success; | ||
| 174 | } | ||
| 175 | |||
| 176 | NCA::~NCA() = default; | ||
| 177 | |||
| 178 | bool NCA::CheckSupportedNCA(const NCAHeader& nca_header) { | ||
| 179 | if (nca_header.magic == Common::MakeMagic('N', 'C', 'A', '2')) { | ||
| 180 | status = Loader::ResultStatus::ErrorNCA2; | ||
| 181 | return false; | ||
| 182 | } | ||
| 183 | |||
| 184 | if (nca_header.magic == Common::MakeMagic('N', 'C', 'A', '0')) { | ||
| 185 | status = Loader::ResultStatus::ErrorNCA0; | ||
| 186 | return false; | ||
| 187 | } | ||
| 188 | |||
| 189 | return true; | ||
| 190 | } | ||
| 191 | 49 | ||
| 192 | bool NCA::HandlePotentialHeaderDecryption() { | 50 | u128 rights_id_u128; |
| 193 | if (IsValidNCA(header)) { | 51 | std::memcpy(rights_id_u128.data(), rights_id.data(), sizeof(rights_id)); |
| 194 | return true; | ||
| 195 | } | ||
| 196 | |||
| 197 | if (!CheckSupportedNCA(header)) { | ||
| 198 | return false; | ||
| 199 | } | ||
| 200 | 52 | ||
| 201 | NCAHeader dec_header{}; | 53 | auto titlekey = |
| 202 | Core::Crypto::AESCipher<Core::Crypto::Key256> cipher( | 54 | keys.GetKey(Core::Crypto::S128KeyType::Titlekey, rights_id_u128[1], rights_id_u128[0]); |
| 203 | keys.GetKey(Core::Crypto::S256KeyType::Header), Core::Crypto::Mode::XTS); | 55 | if (titlekey == Core::Crypto::Key128{}) { |
| 204 | cipher.XTSTranscode(&header, sizeof(NCAHeader), &dec_header, 0, 0x200, | 56 | status = Loader::ResultStatus::ErrorMissingTitlekey; |
| 205 | Core::Crypto::Op::Decrypt); | 57 | return; |
| 206 | if (IsValidNCA(dec_header)) { | ||
| 207 | header = dec_header; | ||
| 208 | encrypted = true; | ||
| 209 | } else { | ||
| 210 | if (!CheckSupportedNCA(dec_header)) { | ||
| 211 | return false; | ||
| 212 | } | 58 | } |
| 213 | 59 | ||
| 214 | if (keys.HasKey(Core::Crypto::S256KeyType::Header)) { | 60 | if (!keys.HasKey(Core::Crypto::S128KeyType::Titlekek, key_generation)) { |
| 215 | status = Loader::ResultStatus::ErrorIncorrectHeaderKey; | 61 | status = Loader::ResultStatus::ErrorMissingTitlekek; |
| 216 | } else { | 62 | return; |
| 217 | status = Loader::ResultStatus::ErrorMissingHeaderKey; | ||
| 218 | } | 63 | } |
| 219 | return false; | ||
| 220 | } | ||
| 221 | 64 | ||
| 222 | return true; | 65 | auto titlekek = keys.GetKey(Core::Crypto::S128KeyType::Titlekek, key_generation); |
| 223 | } | 66 | Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(titlekek, Core::Crypto::Mode::ECB); |
| 67 | cipher.Transcode(titlekey.data(), titlekey.size(), titlekey.data(), | ||
| 68 | Core::Crypto::Op::Decrypt); | ||
| 224 | 69 | ||
| 225 | std::vector<NCASectionHeader> NCA::ReadSectionHeaders() const { | 70 | reader->SetExternalDecryptionKey(titlekey.data(), titlekey.size()); |
| 226 | const std::ptrdiff_t number_sections = | ||
| 227 | std::ranges::count_if(header.section_tables, [](const NCASectionTableEntry& entry) { | ||
| 228 | return entry.media_offset > 0; | ||
| 229 | }); | ||
| 230 | |||
| 231 | std::vector<NCASectionHeader> sections(number_sections); | ||
| 232 | const auto length_sections = SECTION_HEADER_SIZE * number_sections; | ||
| 233 | |||
| 234 | if (encrypted) { | ||
| 235 | auto raw = file->ReadBytes(length_sections, SECTION_HEADER_OFFSET); | ||
| 236 | Core::Crypto::AESCipher<Core::Crypto::Key256> cipher( | ||
| 237 | keys.GetKey(Core::Crypto::S256KeyType::Header), Core::Crypto::Mode::XTS); | ||
| 238 | cipher.XTSTranscode(raw.data(), length_sections, sections.data(), 2, SECTION_HEADER_SIZE, | ||
| 239 | Core::Crypto::Op::Decrypt); | ||
| 240 | } else { | ||
| 241 | file->ReadBytes(sections.data(), length_sections, SECTION_HEADER_OFFSET); | ||
| 242 | } | 71 | } |
| 243 | 72 | ||
| 244 | return sections; | 73 | const s32 fs_count = reader->GetFsCount(); |
| 245 | } | 74 | NcaFileSystemDriver fs(base_nca ? base_nca->reader : nullptr, reader); |
| 246 | 75 | std::vector<VirtualFile> filesystems(fs_count); | |
| 247 | bool NCA::ReadSections(const std::vector<NCASectionHeader>& sections, u64 bktr_base_ivfc_offset) { | 76 | for (s32 i = 0; i < fs_count; i++) { |
| 248 | for (std::size_t i = 0; i < sections.size(); ++i) { | 77 | NcaFsHeaderReader header_reader; |
| 249 | const auto& section = sections[i]; | 78 | const Result rc = fs.OpenStorage(&filesystems[i], &header_reader, i); |
| 250 | 79 | if (R_FAILED(rc)) { | |
| 251 | if (section.raw.sparse_info.bucket.table_offset != 0 && | 80 | LOG_ERROR(Loader, "File reader errored out during read of section {}: {:#x}", i, |
| 252 | section.raw.sparse_info.bucket.table_size != 0) { | 81 | rc.GetInnerValue()); |
| 253 | LOG_ERROR(Loader, "Sparse NCAs are not supported."); | 82 | status = Loader::ResultStatus::ErrorBadNCAHeader; |
| 254 | status = Loader::ResultStatus::ErrorSparseNCA; | 83 | return; |
| 255 | return false; | ||
| 256 | } | ||
| 257 | |||
| 258 | if (section.raw.compression_info.bucket.table_offset != 0 && | ||
| 259 | section.raw.compression_info.bucket.table_size != 0) { | ||
| 260 | LOG_ERROR(Loader, "Compressed NCAs are not supported."); | ||
| 261 | status = Loader::ResultStatus::ErrorCompressedNCA; | ||
| 262 | return false; | ||
| 263 | } | ||
| 264 | |||
| 265 | if (section.raw.header.filesystem_type == NCASectionFilesystemType::ROMFS) { | ||
| 266 | if (!ReadRomFSSection(section, header.section_tables[i], bktr_base_ivfc_offset)) { | ||
| 267 | return false; | ||
| 268 | } | ||
| 269 | } else if (section.raw.header.filesystem_type == NCASectionFilesystemType::PFS0) { | ||
| 270 | if (!ReadPFS0Section(section, header.section_tables[i])) { | ||
| 271 | return false; | ||
| 272 | } | ||
| 273 | } | ||
| 274 | } | ||
| 275 | |||
| 276 | return true; | ||
| 277 | } | ||
| 278 | |||
| 279 | bool NCA::ReadRomFSSection(const NCASectionHeader& section, const NCASectionTableEntry& entry, | ||
| 280 | u64 bktr_base_ivfc_offset) { | ||
| 281 | const std::size_t base_offset = entry.media_offset * MEDIA_OFFSET_MULTIPLIER; | ||
| 282 | ivfc_offset = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset; | ||
| 283 | const std::size_t romfs_offset = base_offset + ivfc_offset; | ||
| 284 | const std::size_t romfs_size = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].size; | ||
| 285 | auto raw = std::make_shared<OffsetVfsFile>(file, romfs_size, romfs_offset); | ||
| 286 | auto dec = Decrypt(section, raw, romfs_offset); | ||
| 287 | |||
| 288 | if (dec == nullptr) { | ||
| 289 | if (status != Loader::ResultStatus::Success) | ||
| 290 | return false; | ||
| 291 | if (has_rights_id) | ||
| 292 | status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek; | ||
| 293 | else | ||
| 294 | status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey; | ||
| 295 | return false; | ||
| 296 | } | ||
| 297 | |||
| 298 | if (section.raw.header.crypto_type == NCASectionCryptoType::BKTR) { | ||
| 299 | if (section.bktr.relocation.magic != Common::MakeMagic('B', 'K', 'T', 'R') || | ||
| 300 | section.bktr.subsection.magic != Common::MakeMagic('B', 'K', 'T', 'R')) { | ||
| 301 | status = Loader::ResultStatus::ErrorBadBKTRHeader; | ||
| 302 | return false; | ||
| 303 | } | ||
| 304 | |||
| 305 | if (section.bktr.relocation.offset + section.bktr.relocation.size != | ||
| 306 | section.bktr.subsection.offset) { | ||
| 307 | status = Loader::ResultStatus::ErrorBKTRSubsectionNotAfterRelocation; | ||
| 308 | return false; | ||
| 309 | } | ||
| 310 | |||
| 311 | const u64 size = MEDIA_OFFSET_MULTIPLIER * (entry.media_end_offset - entry.media_offset); | ||
| 312 | if (section.bktr.subsection.offset + section.bktr.subsection.size != size) { | ||
| 313 | status = Loader::ResultStatus::ErrorBKTRSubsectionNotAtEnd; | ||
| 314 | return false; | ||
| 315 | } | ||
| 316 | |||
| 317 | const u64 offset = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset; | ||
| 318 | RelocationBlock relocation_block{}; | ||
| 319 | if (dec->ReadObject(&relocation_block, section.bktr.relocation.offset - offset) != | ||
| 320 | sizeof(RelocationBlock)) { | ||
| 321 | status = Loader::ResultStatus::ErrorBadRelocationBlock; | ||
| 322 | return false; | ||
| 323 | } | ||
| 324 | SubsectionBlock subsection_block{}; | ||
| 325 | if (dec->ReadObject(&subsection_block, section.bktr.subsection.offset - offset) != | ||
| 326 | sizeof(RelocationBlock)) { | ||
| 327 | status = Loader::ResultStatus::ErrorBadSubsectionBlock; | ||
| 328 | return false; | ||
| 329 | } | ||
| 330 | |||
| 331 | std::vector<RelocationBucketRaw> relocation_buckets_raw( | ||
| 332 | (section.bktr.relocation.size - sizeof(RelocationBlock)) / sizeof(RelocationBucketRaw)); | ||
| 333 | if (dec->ReadBytes(relocation_buckets_raw.data(), | ||
| 334 | section.bktr.relocation.size - sizeof(RelocationBlock), | ||
| 335 | section.bktr.relocation.offset + sizeof(RelocationBlock) - offset) != | ||
| 336 | section.bktr.relocation.size - sizeof(RelocationBlock)) { | ||
| 337 | status = Loader::ResultStatus::ErrorBadRelocationBuckets; | ||
| 338 | return false; | ||
| 339 | } | 84 | } |
| 340 | 85 | ||
| 341 | std::vector<SubsectionBucketRaw> subsection_buckets_raw( | 86 | if (header_reader.GetFsType() == NcaFsHeader::FsType::RomFs) { |
| 342 | (section.bktr.subsection.size - sizeof(SubsectionBlock)) / sizeof(SubsectionBucketRaw)); | 87 | files.push_back(filesystems[i]); |
| 343 | if (dec->ReadBytes(subsection_buckets_raw.data(), | 88 | romfs = files.back(); |
| 344 | section.bktr.subsection.size - sizeof(SubsectionBlock), | ||
| 345 | section.bktr.subsection.offset + sizeof(SubsectionBlock) - offset) != | ||
| 346 | section.bktr.subsection.size - sizeof(SubsectionBlock)) { | ||
| 347 | status = Loader::ResultStatus::ErrorBadSubsectionBuckets; | ||
| 348 | return false; | ||
| 349 | } | 89 | } |
| 350 | 90 | ||
| 351 | std::vector<RelocationBucket> relocation_buckets(relocation_buckets_raw.size()); | 91 | if (header_reader.GetFsType() == NcaFsHeader::FsType::PartitionFs) { |
| 352 | std::ranges::transform(relocation_buckets_raw, relocation_buckets.begin(), | 92 | auto npfs = std::make_shared<PartitionFilesystem>(filesystems[i]); |
| 353 | &ConvertRelocationBucketRaw); | 93 | if (npfs->GetStatus() == Loader::ResultStatus::Success) { |
| 354 | std::vector<SubsectionBucket> subsection_buckets(subsection_buckets_raw.size()); | 94 | dirs.push_back(npfs); |
| 355 | std::ranges::transform(subsection_buckets_raw, subsection_buckets.begin(), | 95 | if (IsDirectoryExeFS(npfs)) { |
| 356 | &ConvertSubsectionBucketRaw); | 96 | exefs = dirs.back(); |
| 357 | 97 | } else if (IsDirectoryLogoPartition(npfs)) { | |
| 358 | u32 ctr_low; | 98 | logo = dirs.back(); |
| 359 | std::memcpy(&ctr_low, section.raw.section_ctr.data(), sizeof(ctr_low)); | 99 | } else { |
| 360 | subsection_buckets.back().entries.push_back({section.bktr.relocation.offset, {0}, ctr_low}); | 100 | continue; |
| 361 | subsection_buckets.back().entries.push_back({size, {0}, 0}); | ||
| 362 | |||
| 363 | std::optional<Core::Crypto::Key128> key; | ||
| 364 | if (encrypted) { | ||
| 365 | if (has_rights_id) { | ||
| 366 | status = Loader::ResultStatus::Success; | ||
| 367 | key = GetTitlekey(); | ||
| 368 | if (!key) { | ||
| 369 | status = Loader::ResultStatus::ErrorMissingTitlekey; | ||
| 370 | return false; | ||
| 371 | } | ||
| 372 | } else { | ||
| 373 | key = GetKeyAreaKey(NCASectionCryptoType::BKTR); | ||
| 374 | if (!key) { | ||
| 375 | status = Loader::ResultStatus::ErrorMissingKeyAreaKey; | ||
| 376 | return false; | ||
| 377 | } | 101 | } |
| 378 | } | 102 | } |
| 379 | } | 103 | } |
| 380 | 104 | ||
| 381 | if (bktr_base_romfs == nullptr) { | 105 | if (header_reader.GetEncryptionType() == NcaFsHeader::EncryptionType::AesCtrEx) { |
| 382 | status = Loader::ResultStatus::ErrorMissingBKTRBaseRomFS; | 106 | is_update = true; |
| 383 | return false; | ||
| 384 | } | 107 | } |
| 385 | |||
| 386 | auto bktr = std::make_shared<BKTR>( | ||
| 387 | bktr_base_romfs, std::make_shared<OffsetVfsFile>(file, romfs_size, base_offset), | ||
| 388 | relocation_block, relocation_buckets, subsection_block, subsection_buckets, encrypted, | ||
| 389 | encrypted ? *key : Core::Crypto::Key128{}, base_offset, bktr_base_ivfc_offset, | ||
| 390 | section.raw.section_ctr); | ||
| 391 | |||
| 392 | // BKTR applies to entire IVFC, so make an offset version to level 6 | ||
| 393 | files.push_back(std::make_shared<OffsetVfsFile>( | ||
| 394 | bktr, romfs_size, section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset)); | ||
| 395 | } else { | ||
| 396 | files.push_back(std::move(dec)); | ||
| 397 | } | 108 | } |
| 398 | 109 | ||
| 399 | romfs = files.back(); | 110 | if (is_update && base_nca == nullptr) { |
| 400 | return true; | 111 | status = Loader::ResultStatus::ErrorMissingBKTRBaseRomFS; |
| 401 | } | ||
| 402 | |||
| 403 | bool NCA::ReadPFS0Section(const NCASectionHeader& section, const NCASectionTableEntry& entry) { | ||
| 404 | const u64 offset = (static_cast<u64>(entry.media_offset) * MEDIA_OFFSET_MULTIPLIER) + | ||
| 405 | section.pfs0.pfs0_header_offset; | ||
| 406 | const u64 size = MEDIA_OFFSET_MULTIPLIER * (entry.media_end_offset - entry.media_offset); | ||
| 407 | |||
| 408 | auto dec = Decrypt(section, std::make_shared<OffsetVfsFile>(file, size, offset), offset); | ||
| 409 | if (dec != nullptr) { | ||
| 410 | auto npfs = std::make_shared<PartitionFilesystem>(std::move(dec)); | ||
| 411 | |||
| 412 | if (npfs->GetStatus() == Loader::ResultStatus::Success) { | ||
| 413 | dirs.push_back(std::move(npfs)); | ||
| 414 | if (IsDirectoryExeFS(dirs.back())) | ||
| 415 | exefs = dirs.back(); | ||
| 416 | else if (IsDirectoryLogoPartition(dirs.back())) | ||
| 417 | logo = dirs.back(); | ||
| 418 | } else { | ||
| 419 | if (has_rights_id) | ||
| 420 | status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek; | ||
| 421 | else | ||
| 422 | status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey; | ||
| 423 | return false; | ||
| 424 | } | ||
| 425 | } else { | 112 | } else { |
| 426 | if (status != Loader::ResultStatus::Success) | 113 | status = Loader::ResultStatus::Success; |
| 427 | return false; | ||
| 428 | if (has_rights_id) | ||
| 429 | status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek; | ||
| 430 | else | ||
| 431 | status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey; | ||
| 432 | return false; | ||
| 433 | } | 114 | } |
| 434 | |||
| 435 | return true; | ||
| 436 | } | ||
| 437 | |||
| 438 | u8 NCA::GetCryptoRevision() const { | ||
| 439 | u8 master_key_id = header.crypto_type; | ||
| 440 | if (header.crypto_type_2 > master_key_id) | ||
| 441 | master_key_id = header.crypto_type_2; | ||
| 442 | if (master_key_id > 0) | ||
| 443 | --master_key_id; | ||
| 444 | return master_key_id; | ||
| 445 | } | 115 | } |
| 446 | 116 | ||
| 447 | std::optional<Core::Crypto::Key128> NCA::GetKeyAreaKey(NCASectionCryptoType type) const { | 117 | NCA::~NCA() = default; |
| 448 | const auto master_key_id = GetCryptoRevision(); | ||
| 449 | |||
| 450 | if (!keys.HasKey(Core::Crypto::S128KeyType::KeyArea, master_key_id, header.key_index)) { | ||
| 451 | return std::nullopt; | ||
| 452 | } | ||
| 453 | |||
| 454 | std::vector<u8> key_area(header.key_area.begin(), header.key_area.end()); | ||
| 455 | Core::Crypto::AESCipher<Core::Crypto::Key128> cipher( | ||
| 456 | keys.GetKey(Core::Crypto::S128KeyType::KeyArea, master_key_id, header.key_index), | ||
| 457 | Core::Crypto::Mode::ECB); | ||
| 458 | cipher.Transcode(key_area.data(), key_area.size(), key_area.data(), Core::Crypto::Op::Decrypt); | ||
| 459 | |||
| 460 | Core::Crypto::Key128 out{}; | ||
| 461 | if (type == NCASectionCryptoType::XTS) { | ||
| 462 | std::copy(key_area.begin(), key_area.begin() + 0x10, out.begin()); | ||
| 463 | } else if (type == NCASectionCryptoType::CTR || type == NCASectionCryptoType::BKTR) { | ||
| 464 | std::copy(key_area.begin() + 0x20, key_area.begin() + 0x30, out.begin()); | ||
| 465 | } else { | ||
| 466 | LOG_CRITICAL(Crypto, "Called GetKeyAreaKey on invalid NCASectionCryptoType type={:02X}", | ||
| 467 | type); | ||
| 468 | } | ||
| 469 | |||
| 470 | u128 out_128{}; | ||
| 471 | std::memcpy(out_128.data(), out.data(), sizeof(u128)); | ||
| 472 | LOG_TRACE(Crypto, "called with crypto_rev={:02X}, kak_index={:02X}, key={:016X}{:016X}", | ||
| 473 | master_key_id, header.key_index, out_128[1], out_128[0]); | ||
| 474 | |||
| 475 | return out; | ||
| 476 | } | ||
| 477 | |||
| 478 | std::optional<Core::Crypto::Key128> NCA::GetTitlekey() { | ||
| 479 | const auto master_key_id = GetCryptoRevision(); | ||
| 480 | |||
| 481 | u128 rights_id{}; | ||
| 482 | memcpy(rights_id.data(), header.rights_id.data(), 16); | ||
| 483 | if (rights_id == u128{}) { | ||
| 484 | status = Loader::ResultStatus::ErrorInvalidRightsID; | ||
| 485 | return std::nullopt; | ||
| 486 | } | ||
| 487 | |||
| 488 | auto titlekey = keys.GetKey(Core::Crypto::S128KeyType::Titlekey, rights_id[1], rights_id[0]); | ||
| 489 | if (titlekey == Core::Crypto::Key128{}) { | ||
| 490 | status = Loader::ResultStatus::ErrorMissingTitlekey; | ||
| 491 | return std::nullopt; | ||
| 492 | } | ||
| 493 | |||
| 494 | if (!keys.HasKey(Core::Crypto::S128KeyType::Titlekek, master_key_id)) { | ||
| 495 | status = Loader::ResultStatus::ErrorMissingTitlekek; | ||
| 496 | return std::nullopt; | ||
| 497 | } | ||
| 498 | |||
| 499 | Core::Crypto::AESCipher<Core::Crypto::Key128> cipher( | ||
| 500 | keys.GetKey(Core::Crypto::S128KeyType::Titlekek, master_key_id), Core::Crypto::Mode::ECB); | ||
| 501 | cipher.Transcode(titlekey.data(), titlekey.size(), titlekey.data(), Core::Crypto::Op::Decrypt); | ||
| 502 | |||
| 503 | return titlekey; | ||
| 504 | } | ||
| 505 | |||
| 506 | VirtualFile NCA::Decrypt(const NCASectionHeader& s_header, VirtualFile in, u64 starting_offset) { | ||
| 507 | if (!encrypted) | ||
| 508 | return in; | ||
| 509 | |||
| 510 | switch (s_header.raw.header.crypto_type) { | ||
| 511 | case NCASectionCryptoType::NONE: | ||
| 512 | LOG_TRACE(Crypto, "called with mode=NONE"); | ||
| 513 | return in; | ||
| 514 | case NCASectionCryptoType::CTR: | ||
| 515 | // During normal BKTR decryption, this entire function is skipped. This is for the metadata, | ||
| 516 | // which uses the same CTR as usual. | ||
| 517 | case NCASectionCryptoType::BKTR: | ||
| 518 | LOG_TRACE(Crypto, "called with mode=CTR, starting_offset={:016X}", starting_offset); | ||
| 519 | { | ||
| 520 | std::optional<Core::Crypto::Key128> key; | ||
| 521 | if (has_rights_id) { | ||
| 522 | status = Loader::ResultStatus::Success; | ||
| 523 | key = GetTitlekey(); | ||
| 524 | if (!key) { | ||
| 525 | if (status == Loader::ResultStatus::Success) | ||
| 526 | status = Loader::ResultStatus::ErrorMissingTitlekey; | ||
| 527 | return nullptr; | ||
| 528 | } | ||
| 529 | } else { | ||
| 530 | key = GetKeyAreaKey(NCASectionCryptoType::CTR); | ||
| 531 | if (!key) { | ||
| 532 | status = Loader::ResultStatus::ErrorMissingKeyAreaKey; | ||
| 533 | return nullptr; | ||
| 534 | } | ||
| 535 | } | ||
| 536 | |||
| 537 | auto out = std::make_shared<Core::Crypto::CTREncryptionLayer>(std::move(in), *key, | ||
| 538 | starting_offset); | ||
| 539 | Core::Crypto::CTREncryptionLayer::IVData iv{}; | ||
| 540 | for (std::size_t i = 0; i < 8; ++i) { | ||
| 541 | iv[i] = s_header.raw.section_ctr[8 - i - 1]; | ||
| 542 | } | ||
| 543 | out->SetIV(iv); | ||
| 544 | return std::static_pointer_cast<VfsFile>(out); | ||
| 545 | } | ||
| 546 | case NCASectionCryptoType::XTS: | ||
| 547 | // TODO(DarkLordZach): Find a test case for XTS-encrypted NCAs | ||
| 548 | default: | ||
| 549 | LOG_ERROR(Crypto, "called with unhandled crypto type={:02X}", | ||
| 550 | s_header.raw.header.crypto_type); | ||
| 551 | return nullptr; | ||
| 552 | } | ||
| 553 | } | ||
| 554 | 118 | ||
| 555 | Loader::ResultStatus NCA::GetStatus() const { | 119 | Loader::ResultStatus NCA::GetStatus() const { |
| 556 | return status; | 120 | return status; |
| @@ -579,21 +143,24 @@ VirtualDir NCA::GetParentDirectory() const { | |||
| 579 | } | 143 | } |
| 580 | 144 | ||
| 581 | NCAContentType NCA::GetType() const { | 145 | NCAContentType NCA::GetType() const { |
| 582 | return header.content_type; | 146 | return static_cast<NCAContentType>(reader->GetContentType()); |
| 583 | } | 147 | } |
| 584 | 148 | ||
| 585 | u64 NCA::GetTitleId() const { | 149 | u64 NCA::GetTitleId() const { |
| 586 | if (is_update || status == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) | 150 | if (is_update) { |
| 587 | return header.title_id | 0x800; | 151 | return reader->GetProgramId() | 0x800; |
| 588 | return header.title_id; | 152 | } |
| 153 | return reader->GetProgramId(); | ||
| 589 | } | 154 | } |
| 590 | 155 | ||
| 591 | std::array<u8, 16> NCA::GetRightsId() const { | 156 | RightsId NCA::GetRightsId() const { |
| 592 | return header.rights_id; | 157 | RightsId result; |
| 158 | reader->GetRightsId(result.data(), result.size()); | ||
| 159 | return result; | ||
| 593 | } | 160 | } |
| 594 | 161 | ||
| 595 | u32 NCA::GetSDKVersion() const { | 162 | u32 NCA::GetSDKVersion() const { |
| 596 | return header.sdk_version; | 163 | return reader->GetSdkAddonVersion(); |
| 597 | } | 164 | } |
| 598 | 165 | ||
| 599 | bool NCA::IsUpdate() const { | 166 | bool NCA::IsUpdate() const { |
| @@ -612,10 +179,6 @@ VirtualFile NCA::GetBaseFile() const { | |||
| 612 | return file; | 179 | return file; |
| 613 | } | 180 | } |
| 614 | 181 | ||
| 615 | u64 NCA::GetBaseIVFCOffset() const { | ||
| 616 | return ivfc_offset; | ||
| 617 | } | ||
| 618 | |||
| 619 | VirtualDir NCA::GetLogoPartition() const { | 182 | VirtualDir NCA::GetLogoPartition() const { |
| 620 | return logo; | 183 | return logo; |
| 621 | } | 184 | } |
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; | |||
| 21 | 21 | ||
| 22 | namespace FileSys { | 22 | namespace FileSys { |
| 23 | 23 | ||
| 24 | union NCASectionHeader; | 24 | class NcaReader; |
| 25 | 25 | ||
| 26 | /// Describes the type of content within an NCA archive. | 26 | /// Describes the type of content within an NCA archive. |
| 27 | enum class NCAContentType : u8 { | 27 | enum class NCAContentType : u8 { |
| @@ -45,41 +45,7 @@ enum class NCAContentType : u8 { | |||
| 45 | PublicData = 5, | 45 | PublicData = 5, |
| 46 | }; | 46 | }; |
| 47 | 47 | ||
| 48 | enum class NCASectionCryptoType : u8 { | 48 | using RightsId = std::array<u8, 0x10>; |
| 49 | NONE = 1, | ||
| 50 | XTS = 2, | ||
| 51 | CTR = 3, | ||
| 52 | BKTR = 4, | ||
| 53 | }; | ||
| 54 | |||
| 55 | struct NCASectionTableEntry { | ||
| 56 | u32_le media_offset; | ||
| 57 | u32_le media_end_offset; | ||
| 58 | INSERT_PADDING_BYTES(0x8); | ||
| 59 | }; | ||
| 60 | static_assert(sizeof(NCASectionTableEntry) == 0x10, "NCASectionTableEntry has incorrect size."); | ||
| 61 | |||
| 62 | struct NCAHeader { | ||
| 63 | std::array<u8, 0x100> rsa_signature_1; | ||
| 64 | std::array<u8, 0x100> rsa_signature_2; | ||
| 65 | u32_le magic; | ||
| 66 | u8 is_system; | ||
| 67 | NCAContentType content_type; | ||
| 68 | u8 crypto_type; | ||
| 69 | u8 key_index; | ||
| 70 | u64_le size; | ||
| 71 | u64_le title_id; | ||
| 72 | INSERT_PADDING_BYTES(0x4); | ||
| 73 | u32_le sdk_version; | ||
| 74 | u8 crypto_type_2; | ||
| 75 | INSERT_PADDING_BYTES(15); | ||
| 76 | std::array<u8, 0x10> rights_id; | ||
| 77 | std::array<NCASectionTableEntry, 0x4> section_tables; | ||
| 78 | std::array<std::array<u8, 0x20>, 0x4> hash_tables; | ||
| 79 | std::array<u8, 0x40> key_area; | ||
| 80 | INSERT_PADDING_BYTES(0xC0); | ||
| 81 | }; | ||
| 82 | static_assert(sizeof(NCAHeader) == 0x400, "NCAHeader has incorrect size."); | ||
| 83 | 49 | ||
| 84 | inline bool IsDirectoryExeFS(const VirtualDir& pfs) { | 50 | inline bool IsDirectoryExeFS(const VirtualDir& pfs) { |
| 85 | // According to switchbrew, an exefs must only contain these two files: | 51 | // According to switchbrew, an exefs must only contain these two files: |
| @@ -97,8 +63,7 @@ inline bool IsDirectoryLogoPartition(const VirtualDir& pfs) { | |||
| 97 | // After construction, use GetStatus to determine if the file is valid and ready to be used. | 63 | // After construction, use GetStatus to determine if the file is valid and ready to be used. |
| 98 | class NCA : public ReadOnlyVfsDirectory { | 64 | class NCA : public ReadOnlyVfsDirectory { |
| 99 | public: | 65 | public: |
| 100 | explicit NCA(VirtualFile file, VirtualFile bktr_base_romfs = nullptr, | 66 | explicit NCA(VirtualFile file, const NCA* base_nca = nullptr); |
| 101 | u64 bktr_base_ivfc_offset = 0); | ||
| 102 | ~NCA() override; | 67 | ~NCA() override; |
| 103 | 68 | ||
| 104 | Loader::ResultStatus GetStatus() const; | 69 | Loader::ResultStatus GetStatus() const; |
| @@ -110,7 +75,7 @@ public: | |||
| 110 | 75 | ||
| 111 | NCAContentType GetType() const; | 76 | NCAContentType GetType() const; |
| 112 | u64 GetTitleId() const; | 77 | u64 GetTitleId() const; |
| 113 | std::array<u8, 0x10> GetRightsId() const; | 78 | RightsId GetRightsId() const; |
| 114 | u32 GetSDKVersion() const; | 79 | u32 GetSDKVersion() const; |
| 115 | bool IsUpdate() const; | 80 | bool IsUpdate() const; |
| 116 | 81 | ||
| @@ -119,26 +84,9 @@ public: | |||
| 119 | 84 | ||
| 120 | VirtualFile GetBaseFile() const; | 85 | VirtualFile GetBaseFile() const; |
| 121 | 86 | ||
| 122 | // Returns the base ivfc offset used in BKTR patching. | ||
| 123 | u64 GetBaseIVFCOffset() const; | ||
| 124 | |||
| 125 | VirtualDir GetLogoPartition() const; | 87 | VirtualDir GetLogoPartition() const; |
| 126 | 88 | ||
| 127 | private: | 89 | private: |
| 128 | bool CheckSupportedNCA(const NCAHeader& header); | ||
| 129 | bool HandlePotentialHeaderDecryption(); | ||
| 130 | |||
| 131 | std::vector<NCASectionHeader> ReadSectionHeaders() const; | ||
| 132 | bool ReadSections(const std::vector<NCASectionHeader>& sections, u64 bktr_base_ivfc_offset); | ||
| 133 | bool ReadRomFSSection(const NCASectionHeader& section, const NCASectionTableEntry& entry, | ||
| 134 | u64 bktr_base_ivfc_offset); | ||
| 135 | bool ReadPFS0Section(const NCASectionHeader& section, const NCASectionTableEntry& entry); | ||
| 136 | |||
| 137 | u8 GetCryptoRevision() const; | ||
| 138 | std::optional<Core::Crypto::Key128> GetKeyAreaKey(NCASectionCryptoType type) const; | ||
| 139 | std::optional<Core::Crypto::Key128> GetTitlekey(); | ||
| 140 | VirtualFile Decrypt(const NCASectionHeader& header, VirtualFile in, u64 starting_offset); | ||
| 141 | |||
| 142 | std::vector<VirtualDir> dirs; | 90 | std::vector<VirtualDir> dirs; |
| 143 | std::vector<VirtualFile> files; | 91 | std::vector<VirtualFile> files; |
| 144 | 92 | ||
| @@ -146,11 +94,6 @@ private: | |||
| 146 | VirtualDir exefs = nullptr; | 94 | VirtualDir exefs = nullptr; |
| 147 | VirtualDir logo = nullptr; | 95 | VirtualDir logo = nullptr; |
| 148 | VirtualFile file; | 96 | VirtualFile file; |
| 149 | VirtualFile bktr_base_romfs; | ||
| 150 | u64 ivfc_offset = 0; | ||
| 151 | |||
| 152 | NCAHeader header{}; | ||
| 153 | bool has_rights_id{}; | ||
| 154 | 97 | ||
| 155 | Loader::ResultStatus status{}; | 98 | Loader::ResultStatus status{}; |
| 156 | 99 | ||
| @@ -158,6 +101,7 @@ private: | |||
| 158 | bool is_update = false; | 101 | bool is_update = false; |
| 159 | 102 | ||
| 160 | Core::Crypto::KeyManager& keys; | 103 | Core::Crypto::KeyManager& keys; |
| 104 | std::shared_ptr<NcaReader> reader; | ||
| 161 | }; | 105 | }; |
| 162 | 106 | ||
| 163 | } // namespace FileSys | 107 | } // 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}; | |||
| 17 | constexpr Result ERROR_INVALID_OFFSET{ErrorModule::FS, 6061}; | 17 | constexpr Result ERROR_INVALID_OFFSET{ErrorModule::FS, 6061}; |
| 18 | constexpr Result ERROR_INVALID_SIZE{ErrorModule::FS, 6062}; | 18 | constexpr Result ERROR_INVALID_SIZE{ErrorModule::FS, 6062}; |
| 19 | 19 | ||
| 20 | constexpr Result ResultUnsupportedSdkVersion{ErrorModule::FS, 50}; | ||
| 21 | constexpr Result ResultPartitionNotFound{ErrorModule::FS, 1001}; | ||
| 22 | constexpr Result ResultUnsupportedVersion{ErrorModule::FS, 3002}; | ||
| 23 | constexpr Result ResultOutOfRange{ErrorModule::FS, 3005}; | ||
| 24 | constexpr Result ResultAllocationMemoryFailedInFileSystemBuddyHeapA{ErrorModule::FS, 3294}; | ||
| 25 | constexpr Result ResultAllocationMemoryFailedInNcaFileSystemDriverI{ErrorModule::FS, 3341}; | ||
| 26 | constexpr Result ResultAllocationMemoryFailedInNcaReaderA{ErrorModule::FS, 3363}; | ||
| 27 | constexpr Result ResultAllocationMemoryFailedInAesCtrCounterExtendedStorageA{ErrorModule::FS, 3399}; | ||
| 28 | constexpr Result ResultAllocationMemoryFailedInIntegrityRomFsStorageA{ErrorModule::FS, 3412}; | ||
| 29 | constexpr Result ResultAllocationMemoryFailedMakeUnique{ErrorModule::FS, 3422}; | ||
| 30 | constexpr Result ResultAllocationMemoryFailedAllocateShared{ErrorModule::FS, 3423}; | ||
| 31 | constexpr Result ResultInvalidAesCtrCounterExtendedEntryOffset{ErrorModule::FS, 4012}; | ||
| 32 | constexpr Result ResultIndirectStorageCorrupted{ErrorModule::FS, 4021}; | ||
| 33 | constexpr Result ResultInvalidIndirectEntryOffset{ErrorModule::FS, 4022}; | ||
| 34 | constexpr Result ResultInvalidIndirectEntryStorageIndex{ErrorModule::FS, 4023}; | ||
| 35 | constexpr Result ResultInvalidIndirectStorageSize{ErrorModule::FS, 4024}; | ||
| 36 | constexpr Result ResultInvalidBucketTreeSignature{ErrorModule::FS, 4032}; | ||
| 37 | constexpr Result ResultInvalidBucketTreeEntryCount{ErrorModule::FS, 4033}; | ||
| 38 | constexpr Result ResultInvalidBucketTreeNodeEntryCount{ErrorModule::FS, 4034}; | ||
| 39 | constexpr Result ResultInvalidBucketTreeNodeOffset{ErrorModule::FS, 4035}; | ||
| 40 | constexpr Result ResultInvalidBucketTreeEntryOffset{ErrorModule::FS, 4036}; | ||
| 41 | constexpr Result ResultInvalidBucketTreeEntrySetOffset{ErrorModule::FS, 4037}; | ||
| 42 | constexpr Result ResultInvalidBucketTreeNodeIndex{ErrorModule::FS, 4038}; | ||
| 43 | constexpr Result ResultInvalidBucketTreeVirtualOffset{ErrorModule::FS, 4039}; | ||
| 44 | constexpr Result ResultRomNcaInvalidPatchMetaDataHashType{ErrorModule::FS, 4084}; | ||
| 45 | constexpr Result ResultRomNcaInvalidIntegrityLayerInfoOffset{ErrorModule::FS, 4085}; | ||
| 46 | constexpr Result ResultRomNcaInvalidPatchMetaDataHashDataSize{ErrorModule::FS, 4086}; | ||
| 47 | constexpr Result ResultRomNcaInvalidPatchMetaDataHashDataOffset{ErrorModule::FS, 4087}; | ||
| 48 | constexpr Result ResultRomNcaInvalidPatchMetaDataHashDataHash{ErrorModule::FS, 4088}; | ||
| 49 | constexpr Result ResultRomNcaInvalidSparseMetaDataHashType{ErrorModule::FS, 4089}; | ||
| 50 | constexpr Result ResultRomNcaInvalidSparseMetaDataHashDataSize{ErrorModule::FS, 4090}; | ||
| 51 | constexpr Result ResultRomNcaInvalidSparseMetaDataHashDataOffset{ErrorModule::FS, 4091}; | ||
| 52 | constexpr Result ResultRomNcaInvalidSparseMetaDataHashDataHash{ErrorModule::FS, 4091}; | ||
| 53 | constexpr Result ResultNcaBaseStorageOutOfRangeB{ErrorModule::FS, 4509}; | ||
| 54 | constexpr Result ResultNcaBaseStorageOutOfRangeC{ErrorModule::FS, 4510}; | ||
| 55 | constexpr Result ResultNcaBaseStorageOutOfRangeD{ErrorModule::FS, 4511}; | ||
| 56 | constexpr Result ResultInvalidNcaSignature{ErrorModule::FS, 4517}; | ||
| 57 | constexpr Result ResultNcaFsHeaderHashVerificationFailed{ErrorModule::FS, 4520}; | ||
| 58 | constexpr Result ResultInvalidNcaKeyIndex{ErrorModule::FS, 4521}; | ||
| 59 | constexpr Result ResultInvalidNcaFsHeaderHashType{ErrorModule::FS, 4522}; | ||
| 60 | constexpr Result ResultInvalidNcaFsHeaderEncryptionType{ErrorModule::FS, 4523}; | ||
| 61 | constexpr Result ResultInvalidNcaPatchInfoIndirectSize{ErrorModule::FS, 4524}; | ||
| 62 | constexpr Result ResultInvalidNcaPatchInfoAesCtrExSize{ErrorModule::FS, 4525}; | ||
| 63 | constexpr Result ResultInvalidNcaPatchInfoAesCtrExOffset{ErrorModule::FS, 4526}; | ||
| 64 | constexpr Result ResultInvalidNcaHeader{ErrorModule::FS, 4528}; | ||
| 65 | constexpr Result ResultInvalidNcaFsHeader{ErrorModule::FS, 4529}; | ||
| 66 | constexpr Result ResultNcaBaseStorageOutOfRangeE{ErrorModule::FS, 4530}; | ||
| 67 | constexpr Result ResultInvalidHierarchicalSha256BlockSize{ErrorModule::FS, 4532}; | ||
| 68 | constexpr Result ResultInvalidHierarchicalSha256LayerCount{ErrorModule::FS, 4533}; | ||
| 69 | constexpr Result ResultHierarchicalSha256BaseStorageTooLarge{ErrorModule::FS, 4534}; | ||
| 70 | constexpr Result ResultHierarchicalSha256HashVerificationFailed{ErrorModule::FS, 4535}; | ||
| 71 | constexpr Result ResultInvalidNcaHierarchicalIntegrityVerificationLayerCount{ErrorModule::FS, 4541}; | ||
| 72 | constexpr Result ResultInvalidNcaIndirectStorageOutOfRange{ErrorModule::FS, 4542}; | ||
| 73 | constexpr Result ResultInvalidNcaHeader1SignatureKeyGeneration{ErrorModule::FS, 4543}; | ||
| 74 | constexpr Result ResultInvalidCompressedStorageSize{ErrorModule::FS, 4547}; | ||
| 75 | constexpr Result ResultInvalidNcaMetaDataHashDataSize{ErrorModule::FS, 4548}; | ||
| 76 | constexpr Result ResultInvalidNcaMetaDataHashDataHash{ErrorModule::FS, 4549}; | ||
| 77 | constexpr Result ResultUnexpectedInCompressedStorageA{ErrorModule::FS, 5324}; | ||
| 78 | constexpr Result ResultUnexpectedInCompressedStorageB{ErrorModule::FS, 5325}; | ||
| 79 | constexpr Result ResultUnexpectedInCompressedStorageC{ErrorModule::FS, 5326}; | ||
| 80 | constexpr Result ResultUnexpectedInCompressedStorageD{ErrorModule::FS, 5327}; | ||
| 81 | constexpr Result ResultInvalidArgument{ErrorModule::FS, 6001}; | ||
| 82 | constexpr Result ResultInvalidOffset{ErrorModule::FS, 6061}; | ||
| 83 | constexpr Result ResultInvalidSize{ErrorModule::FS, 6062}; | ||
| 84 | constexpr Result ResultNullptrArgument{ErrorModule::FS, 6063}; | ||
| 85 | constexpr Result ResultUnsupportedSetSizeForIndirectStorage{ErrorModule::FS, 6325}; | ||
| 86 | constexpr Result ResultUnsupportedWriteForCompressedStorage{ErrorModule::FS, 6387}; | ||
| 87 | constexpr Result ResultUnsupportedOperateRangeForCompressedStorage{ErrorModule::FS, 6388}; | ||
| 88 | constexpr Result ResultBufferAllocationFailed{ErrorModule::FS, 6705}; | ||
| 89 | |||
| 20 | } // namespace FileSys | 90 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "common/overflow.h" | ||
| 7 | #include "core/file_sys/errors.h" | ||
| 8 | #include "core/file_sys/vfs.h" | ||
| 9 | |||
| 10 | namespace FileSys { | ||
| 11 | |||
| 12 | class IStorage : public VfsFile { | ||
| 13 | public: | ||
| 14 | virtual std::string GetName() const override { | ||
| 15 | return {}; | ||
| 16 | } | ||
| 17 | |||
| 18 | virtual VirtualDir GetContainingDirectory() const override { | ||
| 19 | return {}; | ||
| 20 | } | ||
| 21 | |||
| 22 | virtual bool IsWritable() const override { | ||
| 23 | return true; | ||
| 24 | } | ||
| 25 | |||
| 26 | virtual bool IsReadable() const override { | ||
| 27 | return true; | ||
| 28 | } | ||
| 29 | |||
| 30 | virtual bool Resize(size_t size) override { | ||
| 31 | return false; | ||
| 32 | } | ||
| 33 | |||
| 34 | virtual bool Rename(std::string_view name) override { | ||
| 35 | return false; | ||
| 36 | } | ||
| 37 | |||
| 38 | static inline Result CheckAccessRange(s64 offset, s64 size, s64 total_size) { | ||
| 39 | R_UNLESS(offset >= 0, ResultInvalidOffset); | ||
| 40 | R_UNLESS(size >= 0, ResultInvalidSize); | ||
| 41 | R_UNLESS(Common::WrappingAdd(offset, size) >= offset, ResultOutOfRange); | ||
| 42 | R_UNLESS(offset + size <= total_size, ResultOutOfRange); | ||
| 43 | R_SUCCEED(); | ||
| 44 | } | ||
| 45 | }; | ||
| 46 | |||
| 47 | class IReadOnlyStorage : public IStorage { | ||
| 48 | public: | ||
| 49 | virtual bool IsWritable() const override { | ||
| 50 | return false; | ||
| 51 | } | ||
| 52 | |||
| 53 | virtual size_t Write(const u8* buffer, size_t size, size_t offset) override { | ||
| 54 | return 0; | ||
| 55 | } | ||
| 56 | }; | ||
| 57 | |||
| 58 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "common/common_funcs.h" | ||
| 7 | |||
| 8 | namespace FileSys { | ||
| 9 | |||
| 10 | struct Int64 { | ||
| 11 | u32 low; | ||
| 12 | u32 high; | ||
| 13 | |||
| 14 | constexpr void Set(s64 v) { | ||
| 15 | this->low = static_cast<u32>((v & static_cast<u64>(0x00000000FFFFFFFFULL)) >> 0); | ||
| 16 | this->high = static_cast<u32>((v & static_cast<u64>(0xFFFFFFFF00000000ULL)) >> 32); | ||
| 17 | } | ||
| 18 | |||
| 19 | constexpr s64 Get() const { | ||
| 20 | return (static_cast<s64>(this->high) << 32) | (static_cast<s64>(this->low)); | ||
| 21 | } | ||
| 22 | |||
| 23 | constexpr Int64& operator=(s64 v) { | ||
| 24 | this->Set(v); | ||
| 25 | return *this; | ||
| 26 | } | ||
| 27 | |||
| 28 | constexpr operator s64() const { | ||
| 29 | return this->Get(); | ||
| 30 | } | ||
| 31 | }; | ||
| 32 | |||
| 33 | struct HashSalt { | ||
| 34 | static constexpr size_t Size = 32; | ||
| 35 | |||
| 36 | std::array<u8, Size> value; | ||
| 37 | }; | ||
| 38 | static_assert(std::is_trivial_v<HashSalt>); | ||
| 39 | static_assert(sizeof(HashSalt) == HashSalt::Size); | ||
| 40 | |||
| 41 | constexpr inline size_t IntegrityMinLayerCount = 2; | ||
| 42 | constexpr inline size_t IntegrityMaxLayerCount = 7; | ||
| 43 | constexpr inline size_t IntegrityLayerCountSave = 5; | ||
| 44 | constexpr inline size_t IntegrityLayerCountSaveDataMeta = 4; | ||
| 45 | |||
| 46 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.h" | ||
| 5 | #include "core/file_sys/fssystem/fssystem_aes_ctr_storage.h" | ||
| 6 | #include "core/file_sys/fssystem/fssystem_nca_header.h" | ||
| 7 | #include "core/file_sys/vfs_offset.h" | ||
| 8 | |||
| 9 | namespace FileSys { | ||
| 10 | |||
| 11 | namespace { | ||
| 12 | |||
| 13 | class SoftwareDecryptor final : public AesCtrCounterExtendedStorage::IDecryptor { | ||
| 14 | public: | ||
| 15 | virtual void Decrypt( | ||
| 16 | u8* buf, size_t buf_size, const std::array<u8, AesCtrCounterExtendedStorage::KeySize>& key, | ||
| 17 | const std::array<u8, AesCtrCounterExtendedStorage::IvSize>& iv) override final; | ||
| 18 | }; | ||
| 19 | |||
| 20 | } // namespace | ||
| 21 | |||
| 22 | Result AesCtrCounterExtendedStorage::CreateSoftwareDecryptor(std::unique_ptr<IDecryptor>* out) { | ||
| 23 | std::unique_ptr<IDecryptor> decryptor = std::make_unique<SoftwareDecryptor>(); | ||
| 24 | R_UNLESS(decryptor != nullptr, ResultAllocationMemoryFailedInAesCtrCounterExtendedStorageA); | ||
| 25 | *out = std::move(decryptor); | ||
| 26 | R_SUCCEED(); | ||
| 27 | } | ||
| 28 | |||
| 29 | Result AesCtrCounterExtendedStorage::Initialize(const void* key, size_t key_size, u32 secure_value, | ||
| 30 | VirtualFile data_storage, | ||
| 31 | VirtualFile table_storage) { | ||
| 32 | // Read and verify the bucket tree header. | ||
| 33 | BucketTree::Header header; | ||
| 34 | table_storage->ReadObject(std::addressof(header), 0); | ||
| 35 | R_TRY(header.Verify()); | ||
| 36 | |||
| 37 | // Determine extents. | ||
| 38 | const auto node_storage_size = QueryNodeStorageSize(header.entry_count); | ||
| 39 | const auto entry_storage_size = QueryEntryStorageSize(header.entry_count); | ||
| 40 | const auto node_storage_offset = QueryHeaderStorageSize(); | ||
| 41 | const auto entry_storage_offset = node_storage_offset + node_storage_size; | ||
| 42 | |||
| 43 | // Create a software decryptor. | ||
| 44 | std::unique_ptr<IDecryptor> sw_decryptor; | ||
| 45 | R_TRY(CreateSoftwareDecryptor(std::addressof(sw_decryptor))); | ||
| 46 | |||
| 47 | // Initialize. | ||
| 48 | R_RETURN(this->Initialize( | ||
| 49 | key, key_size, secure_value, 0, data_storage, | ||
| 50 | std::make_shared<OffsetVfsFile>(table_storage, node_storage_size, node_storage_offset), | ||
| 51 | std::make_shared<OffsetVfsFile>(table_storage, entry_storage_size, entry_storage_offset), | ||
| 52 | header.entry_count, std::move(sw_decryptor))); | ||
| 53 | } | ||
| 54 | |||
| 55 | Result AesCtrCounterExtendedStorage::Initialize(const void* key, size_t key_size, u32 secure_value, | ||
| 56 | s64 counter_offset, VirtualFile data_storage, | ||
| 57 | VirtualFile node_storage, VirtualFile entry_storage, | ||
| 58 | s32 entry_count, | ||
| 59 | std::unique_ptr<IDecryptor>&& decryptor) { | ||
| 60 | // Validate preconditions. | ||
| 61 | ASSERT(key != nullptr); | ||
| 62 | ASSERT(key_size == KeySize); | ||
| 63 | ASSERT(counter_offset >= 0); | ||
| 64 | ASSERT(decryptor != nullptr); | ||
| 65 | |||
| 66 | // Initialize the bucket tree table. | ||
| 67 | if (entry_count > 0) { | ||
| 68 | R_TRY( | ||
| 69 | m_table.Initialize(node_storage, entry_storage, NodeSize, sizeof(Entry), entry_count)); | ||
| 70 | } else { | ||
| 71 | m_table.Initialize(NodeSize, 0); | ||
| 72 | } | ||
| 73 | |||
| 74 | // Set members. | ||
| 75 | m_data_storage = data_storage; | ||
| 76 | std::memcpy(m_key.data(), key, key_size); | ||
| 77 | m_secure_value = secure_value; | ||
| 78 | m_counter_offset = counter_offset; | ||
| 79 | m_decryptor = std::move(decryptor); | ||
| 80 | |||
| 81 | R_SUCCEED(); | ||
| 82 | } | ||
| 83 | |||
| 84 | void AesCtrCounterExtendedStorage::Finalize() { | ||
| 85 | if (this->IsInitialized()) { | ||
| 86 | m_table.Finalize(); | ||
| 87 | m_data_storage = VirtualFile(); | ||
| 88 | } | ||
| 89 | } | ||
| 90 | |||
| 91 | Result AesCtrCounterExtendedStorage::GetEntryList(Entry* out_entries, s32* out_entry_count, | ||
| 92 | s32 entry_count, s64 offset, s64 size) { | ||
| 93 | // Validate pre-conditions. | ||
| 94 | ASSERT(offset >= 0); | ||
| 95 | ASSERT(size >= 0); | ||
| 96 | ASSERT(this->IsInitialized()); | ||
| 97 | |||
| 98 | // Clear the out count. | ||
| 99 | R_UNLESS(out_entry_count != nullptr, ResultNullptrArgument); | ||
| 100 | *out_entry_count = 0; | ||
| 101 | |||
| 102 | // Succeed if there's no range. | ||
| 103 | R_SUCCEED_IF(size == 0); | ||
| 104 | |||
| 105 | // If we have an output array, we need it to be non-null. | ||
| 106 | R_UNLESS(out_entries != nullptr || entry_count == 0, ResultNullptrArgument); | ||
| 107 | |||
| 108 | // Check that our range is valid. | ||
| 109 | BucketTree::Offsets table_offsets; | ||
| 110 | R_TRY(m_table.GetOffsets(std::addressof(table_offsets))); | ||
| 111 | |||
| 112 | R_UNLESS(table_offsets.IsInclude(offset, size), ResultOutOfRange); | ||
| 113 | |||
| 114 | // Find the offset in our tree. | ||
| 115 | BucketTree::Visitor visitor; | ||
| 116 | R_TRY(m_table.Find(std::addressof(visitor), offset)); | ||
| 117 | { | ||
| 118 | const auto entry_offset = visitor.Get<Entry>()->GetOffset(); | ||
| 119 | R_UNLESS(0 <= entry_offset && table_offsets.IsInclude(entry_offset), | ||
| 120 | ResultInvalidAesCtrCounterExtendedEntryOffset); | ||
| 121 | } | ||
| 122 | |||
| 123 | // Prepare to loop over entries. | ||
| 124 | const auto end_offset = offset + static_cast<s64>(size); | ||
| 125 | s32 count = 0; | ||
| 126 | |||
| 127 | auto cur_entry = *visitor.Get<Entry>(); | ||
| 128 | while (cur_entry.GetOffset() < end_offset) { | ||
| 129 | // Try to write the entry to the out list. | ||
| 130 | if (entry_count != 0) { | ||
| 131 | if (count >= entry_count) { | ||
| 132 | break; | ||
| 133 | } | ||
| 134 | std::memcpy(out_entries + count, std::addressof(cur_entry), sizeof(Entry)); | ||
| 135 | } | ||
| 136 | |||
| 137 | count++; | ||
| 138 | |||
| 139 | // Advance. | ||
| 140 | if (visitor.CanMoveNext()) { | ||
| 141 | R_TRY(visitor.MoveNext()); | ||
| 142 | cur_entry = *visitor.Get<Entry>(); | ||
| 143 | } else { | ||
| 144 | break; | ||
| 145 | } | ||
| 146 | } | ||
| 147 | |||
| 148 | // Write the output count. | ||
| 149 | *out_entry_count = count; | ||
| 150 | R_SUCCEED(); | ||
| 151 | } | ||
| 152 | |||
| 153 | size_t AesCtrCounterExtendedStorage::Read(u8* buffer, size_t size, size_t offset) const { | ||
| 154 | // Validate preconditions. | ||
| 155 | ASSERT(this->IsInitialized()); | ||
| 156 | |||
| 157 | // Allow zero size. | ||
| 158 | if (size == 0) { | ||
| 159 | return size; | ||
| 160 | } | ||
| 161 | |||
| 162 | // Validate arguments. | ||
| 163 | ASSERT(buffer != nullptr); | ||
| 164 | ASSERT(Common::IsAligned(offset, BlockSize)); | ||
| 165 | ASSERT(Common::IsAligned(size, BlockSize)); | ||
| 166 | |||
| 167 | BucketTree::Offsets table_offsets; | ||
| 168 | ASSERT(R_SUCCEEDED(m_table.GetOffsets(std::addressof(table_offsets)))); | ||
| 169 | |||
| 170 | ASSERT(table_offsets.IsInclude(offset, size)); | ||
| 171 | |||
| 172 | // Read the data. | ||
| 173 | m_data_storage->Read(buffer, size, offset); | ||
| 174 | |||
| 175 | // Find the offset in our tree. | ||
| 176 | BucketTree::Visitor visitor; | ||
| 177 | ASSERT(R_SUCCEEDED(m_table.Find(std::addressof(visitor), offset))); | ||
| 178 | { | ||
| 179 | const auto entry_offset = visitor.Get<Entry>()->GetOffset(); | ||
| 180 | ASSERT(Common::IsAligned(entry_offset, BlockSize)); | ||
| 181 | ASSERT(0 <= entry_offset && table_offsets.IsInclude(entry_offset)); | ||
| 182 | } | ||
| 183 | |||
| 184 | // Prepare to read in chunks. | ||
| 185 | u8* cur_data = static_cast<u8*>(buffer); | ||
| 186 | auto cur_offset = offset; | ||
| 187 | const auto end_offset = offset + static_cast<s64>(size); | ||
| 188 | |||
| 189 | while (cur_offset < end_offset) { | ||
| 190 | // Get the current entry. | ||
| 191 | const auto cur_entry = *visitor.Get<Entry>(); | ||
| 192 | |||
| 193 | // Get and validate the entry's offset. | ||
| 194 | const auto cur_entry_offset = cur_entry.GetOffset(); | ||
| 195 | ASSERT(static_cast<size_t>(cur_entry_offset) <= cur_offset); | ||
| 196 | |||
| 197 | // Get and validate the next entry offset. | ||
| 198 | s64 next_entry_offset; | ||
| 199 | if (visitor.CanMoveNext()) { | ||
| 200 | ASSERT(R_SUCCEEDED(visitor.MoveNext())); | ||
| 201 | next_entry_offset = visitor.Get<Entry>()->GetOffset(); | ||
| 202 | ASSERT(table_offsets.IsInclude(next_entry_offset)); | ||
| 203 | } else { | ||
| 204 | next_entry_offset = table_offsets.end_offset; | ||
| 205 | } | ||
| 206 | ASSERT(Common::IsAligned(next_entry_offset, BlockSize)); | ||
| 207 | ASSERT(cur_offset < static_cast<size_t>(next_entry_offset)); | ||
| 208 | |||
| 209 | // Get the offset of the entry in the data we read. | ||
| 210 | const auto data_offset = cur_offset - cur_entry_offset; | ||
| 211 | const auto data_size = (next_entry_offset - cur_entry_offset) - data_offset; | ||
| 212 | ASSERT(data_size > 0); | ||
| 213 | |||
| 214 | // Determine how much is left. | ||
| 215 | const auto remaining_size = end_offset - cur_offset; | ||
| 216 | const auto cur_size = static_cast<size_t>(std::min(remaining_size, data_size)); | ||
| 217 | ASSERT(cur_size <= size); | ||
| 218 | |||
| 219 | // If necessary, perform decryption. | ||
| 220 | if (cur_entry.encryption_value == Entry::Encryption::Encrypted) { | ||
| 221 | // Make the CTR for the data we're decrypting. | ||
| 222 | const auto counter_offset = m_counter_offset + cur_entry_offset + data_offset; | ||
| 223 | NcaAesCtrUpperIv upper_iv = { | ||
| 224 | .part = {.generation = static_cast<u32>(cur_entry.generation), | ||
| 225 | .secure_value = m_secure_value}}; | ||
| 226 | |||
| 227 | std::array<u8, IvSize> iv; | ||
| 228 | AesCtrStorage::MakeIv(iv.data(), IvSize, upper_iv.value, counter_offset); | ||
| 229 | |||
| 230 | // Decrypt. | ||
| 231 | m_decryptor->Decrypt(cur_data, cur_size, m_key, iv); | ||
| 232 | } | ||
| 233 | |||
| 234 | // Advance. | ||
| 235 | cur_data += cur_size; | ||
| 236 | cur_offset += cur_size; | ||
| 237 | } | ||
| 238 | |||
| 239 | return size; | ||
| 240 | } | ||
| 241 | |||
| 242 | void SoftwareDecryptor::Decrypt(u8* buf, size_t buf_size, | ||
| 243 | const std::array<u8, AesCtrCounterExtendedStorage::KeySize>& key, | ||
| 244 | const std::array<u8, AesCtrCounterExtendedStorage::IvSize>& iv) { | ||
| 245 | Core::Crypto::AESCipher<Core::Crypto::Key128, AesCtrCounterExtendedStorage::KeySize> cipher( | ||
| 246 | key, Core::Crypto::Mode::CTR); | ||
| 247 | cipher.SetIV(iv); | ||
| 248 | cipher.Transcode(buf, buf_size, buf, Core::Crypto::Op::Decrypt); | ||
| 249 | } | ||
| 250 | |||
| 251 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <optional> | ||
| 7 | |||
| 8 | #include "common/literals.h" | ||
| 9 | #include "core/file_sys/fssystem/fs_i_storage.h" | ||
| 10 | #include "core/file_sys/fssystem/fssystem_bucket_tree.h" | ||
| 11 | |||
| 12 | namespace FileSys { | ||
| 13 | |||
| 14 | using namespace Common::Literals; | ||
| 15 | |||
| 16 | class AesCtrCounterExtendedStorage : public IReadOnlyStorage { | ||
| 17 | YUZU_NON_COPYABLE(AesCtrCounterExtendedStorage); | ||
| 18 | YUZU_NON_MOVEABLE(AesCtrCounterExtendedStorage); | ||
| 19 | |||
| 20 | public: | ||
| 21 | static constexpr size_t BlockSize = 0x10; | ||
| 22 | static constexpr size_t KeySize = 0x10; | ||
| 23 | static constexpr size_t IvSize = 0x10; | ||
| 24 | static constexpr size_t NodeSize = 16_KiB; | ||
| 25 | |||
| 26 | class IDecryptor { | ||
| 27 | public: | ||
| 28 | virtual ~IDecryptor() {} | ||
| 29 | virtual void Decrypt(u8* buf, size_t buf_size, const std::array<u8, KeySize>& key, | ||
| 30 | const std::array<u8, IvSize>& iv) = 0; | ||
| 31 | }; | ||
| 32 | |||
| 33 | struct Entry { | ||
| 34 | enum class Encryption : u8 { | ||
| 35 | Encrypted = 0, | ||
| 36 | NotEncrypted = 1, | ||
| 37 | }; | ||
| 38 | |||
| 39 | std::array<u8, sizeof(s64)> offset; | ||
| 40 | Encryption encryption_value; | ||
| 41 | std::array<u8, 3> reserved; | ||
| 42 | s32 generation; | ||
| 43 | |||
| 44 | void SetOffset(s64 value) { | ||
| 45 | std::memcpy(this->offset.data(), std::addressof(value), sizeof(s64)); | ||
| 46 | } | ||
| 47 | |||
| 48 | s64 GetOffset() const { | ||
| 49 | s64 value; | ||
| 50 | std::memcpy(std::addressof(value), this->offset.data(), sizeof(s64)); | ||
| 51 | return value; | ||
| 52 | } | ||
| 53 | }; | ||
| 54 | static_assert(sizeof(Entry) == 0x10); | ||
| 55 | static_assert(alignof(Entry) == 4); | ||
| 56 | static_assert(std::is_trivial_v<Entry>); | ||
| 57 | |||
| 58 | public: | ||
| 59 | static constexpr s64 QueryHeaderStorageSize() { | ||
| 60 | return BucketTree::QueryHeaderStorageSize(); | ||
| 61 | } | ||
| 62 | |||
| 63 | static constexpr s64 QueryNodeStorageSize(s32 entry_count) { | ||
| 64 | return BucketTree::QueryNodeStorageSize(NodeSize, sizeof(Entry), entry_count); | ||
| 65 | } | ||
| 66 | |||
| 67 | static constexpr s64 QueryEntryStorageSize(s32 entry_count) { | ||
| 68 | return BucketTree::QueryEntryStorageSize(NodeSize, sizeof(Entry), entry_count); | ||
| 69 | } | ||
| 70 | |||
| 71 | static Result CreateSoftwareDecryptor(std::unique_ptr<IDecryptor>* out); | ||
| 72 | |||
| 73 | public: | ||
| 74 | AesCtrCounterExtendedStorage() | ||
| 75 | : m_table(), m_data_storage(), m_secure_value(), m_counter_offset(), m_decryptor() {} | ||
| 76 | virtual ~AesCtrCounterExtendedStorage() { | ||
| 77 | this->Finalize(); | ||
| 78 | } | ||
| 79 | |||
| 80 | Result Initialize(const void* key, size_t key_size, u32 secure_value, s64 counter_offset, | ||
| 81 | VirtualFile data_storage, VirtualFile node_storage, VirtualFile entry_storage, | ||
| 82 | s32 entry_count, std::unique_ptr<IDecryptor>&& decryptor); | ||
| 83 | void Finalize(); | ||
| 84 | |||
| 85 | bool IsInitialized() const { | ||
| 86 | return m_table.IsInitialized(); | ||
| 87 | } | ||
| 88 | |||
| 89 | virtual size_t Read(u8* buffer, size_t size, size_t offset) const override; | ||
| 90 | |||
| 91 | virtual size_t GetSize() const override { | ||
| 92 | BucketTree::Offsets offsets; | ||
| 93 | ASSERT(R_SUCCEEDED(m_table.GetOffsets(std::addressof(offsets)))); | ||
| 94 | |||
| 95 | return offsets.end_offset; | ||
| 96 | } | ||
| 97 | |||
| 98 | Result GetEntryList(Entry* out_entries, s32* out_entry_count, s32 entry_count, s64 offset, | ||
| 99 | s64 size); | ||
| 100 | |||
| 101 | private: | ||
| 102 | Result Initialize(const void* key, size_t key_size, u32 secure_value, VirtualFile data_storage, | ||
| 103 | VirtualFile table_storage); | ||
| 104 | |||
| 105 | private: | ||
| 106 | mutable BucketTree m_table; | ||
| 107 | VirtualFile m_data_storage; | ||
| 108 | std::array<u8, KeySize> m_key; | ||
| 109 | u32 m_secure_value; | ||
| 110 | s64 m_counter_offset; | ||
| 111 | std::unique_ptr<IDecryptor> m_decryptor; | ||
| 112 | }; | ||
| 113 | |||
| 114 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "common/alignment.h" | ||
| 5 | #include "common/swap.h" | ||
| 6 | #include "core/file_sys/fssystem/fssystem_aes_ctr_storage.h" | ||
| 7 | #include "core/file_sys/fssystem/fssystem_pooled_buffer.h" | ||
| 8 | #include "core/file_sys/fssystem/fssystem_utility.h" | ||
| 9 | |||
| 10 | namespace FileSys { | ||
| 11 | |||
| 12 | void AesCtrStorage::MakeIv(void* dst, size_t dst_size, u64 upper, s64 offset) { | ||
| 13 | ASSERT(dst != nullptr); | ||
| 14 | ASSERT(dst_size == IvSize); | ||
| 15 | ASSERT(offset >= 0); | ||
| 16 | |||
| 17 | const uintptr_t out_addr = reinterpret_cast<uintptr_t>(dst); | ||
| 18 | |||
| 19 | *reinterpret_cast<u64_be*>(out_addr + 0) = upper; | ||
| 20 | *reinterpret_cast<s64_be*>(out_addr + sizeof(u64)) = static_cast<s64>(offset / BlockSize); | ||
| 21 | } | ||
| 22 | |||
| 23 | AesCtrStorage::AesCtrStorage(VirtualFile base, const void* key, size_t key_size, const void* iv, | ||
| 24 | size_t iv_size) | ||
| 25 | : m_base_storage(std::move(base)) { | ||
| 26 | ASSERT(m_base_storage != nullptr); | ||
| 27 | ASSERT(key != nullptr); | ||
| 28 | ASSERT(iv != nullptr); | ||
| 29 | ASSERT(key_size == KeySize); | ||
| 30 | ASSERT(iv_size == IvSize); | ||
| 31 | |||
| 32 | std::memcpy(m_key.data(), key, KeySize); | ||
| 33 | std::memcpy(m_iv.data(), iv, IvSize); | ||
| 34 | |||
| 35 | m_cipher.emplace(m_key, Core::Crypto::Mode::CTR); | ||
| 36 | } | ||
| 37 | |||
| 38 | size_t AesCtrStorage::Read(u8* buffer, size_t size, size_t offset) const { | ||
| 39 | // Allow zero-size reads. | ||
| 40 | if (size == 0) { | ||
| 41 | return size; | ||
| 42 | } | ||
| 43 | |||
| 44 | // Ensure buffer is valid. | ||
| 45 | ASSERT(buffer != nullptr); | ||
| 46 | |||
| 47 | // We can only read at block aligned offsets. | ||
| 48 | ASSERT(Common::IsAligned(offset, BlockSize)); | ||
| 49 | ASSERT(Common::IsAligned(size, BlockSize)); | ||
| 50 | |||
| 51 | // Read the data. | ||
| 52 | m_base_storage->Read(buffer, size, offset); | ||
| 53 | |||
| 54 | // Setup the counter. | ||
| 55 | std::array<u8, IvSize> ctr; | ||
| 56 | std::memcpy(ctr.data(), m_iv.data(), IvSize); | ||
| 57 | AddCounter(ctr.data(), IvSize, offset / BlockSize); | ||
| 58 | |||
| 59 | // Decrypt. | ||
| 60 | m_cipher->SetIV(ctr); | ||
| 61 | m_cipher->Transcode(buffer, size, buffer, Core::Crypto::Op::Decrypt); | ||
| 62 | |||
| 63 | return size; | ||
| 64 | } | ||
| 65 | |||
| 66 | size_t AesCtrStorage::Write(const u8* buffer, size_t size, size_t offset) { | ||
| 67 | // Allow zero-size writes. | ||
| 68 | if (size == 0) { | ||
| 69 | return size; | ||
| 70 | } | ||
| 71 | |||
| 72 | // Ensure buffer is valid. | ||
| 73 | ASSERT(buffer != nullptr); | ||
| 74 | |||
| 75 | // We can only write at block aligned offsets. | ||
| 76 | ASSERT(Common::IsAligned(offset, BlockSize)); | ||
| 77 | ASSERT(Common::IsAligned(size, BlockSize)); | ||
| 78 | |||
| 79 | // Get a pooled buffer. | ||
| 80 | PooledBuffer pooled_buffer; | ||
| 81 | const bool use_work_buffer = true; | ||
| 82 | if (use_work_buffer) { | ||
| 83 | pooled_buffer.Allocate(size, BlockSize); | ||
| 84 | } | ||
| 85 | |||
| 86 | // Setup the counter. | ||
| 87 | std::array<u8, IvSize> ctr; | ||
| 88 | std::memcpy(ctr.data(), m_iv.data(), IvSize); | ||
| 89 | AddCounter(ctr.data(), IvSize, offset / BlockSize); | ||
| 90 | |||
| 91 | // Loop until all data is written. | ||
| 92 | size_t remaining = size; | ||
| 93 | s64 cur_offset = 0; | ||
| 94 | while (remaining > 0) { | ||
| 95 | // Determine data we're writing and where. | ||
| 96 | const size_t write_size = | ||
| 97 | use_work_buffer ? std::min(pooled_buffer.GetSize(), remaining) : remaining; | ||
| 98 | |||
| 99 | void* write_buf; | ||
| 100 | if (use_work_buffer) { | ||
| 101 | write_buf = pooled_buffer.GetBuffer(); | ||
| 102 | } else { | ||
| 103 | write_buf = const_cast<u8*>(buffer); | ||
| 104 | } | ||
| 105 | |||
| 106 | // Encrypt the data. | ||
| 107 | m_cipher->SetIV(ctr); | ||
| 108 | m_cipher->Transcode(buffer, write_size, reinterpret_cast<u8*>(write_buf), | ||
| 109 | Core::Crypto::Op::Encrypt); | ||
| 110 | |||
| 111 | // Write the encrypted data. | ||
| 112 | m_base_storage->Write(reinterpret_cast<u8*>(write_buf), write_size, offset + cur_offset); | ||
| 113 | |||
| 114 | // Advance. | ||
| 115 | cur_offset += write_size; | ||
| 116 | remaining -= write_size; | ||
| 117 | if (remaining > 0) { | ||
| 118 | AddCounter(ctr.data(), IvSize, write_size / BlockSize); | ||
| 119 | } | ||
| 120 | } | ||
| 121 | |||
| 122 | return size; | ||
| 123 | } | ||
| 124 | |||
| 125 | size_t AesCtrStorage::GetSize() const { | ||
| 126 | return m_base_storage->GetSize(); | ||
| 127 | } | ||
| 128 | |||
| 129 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <optional> | ||
| 7 | |||
| 8 | #include "core/crypto/aes_util.h" | ||
| 9 | #include "core/crypto/key_manager.h" | ||
| 10 | #include "core/file_sys/errors.h" | ||
| 11 | #include "core/file_sys/fssystem/fs_i_storage.h" | ||
| 12 | #include "core/file_sys/vfs.h" | ||
| 13 | |||
| 14 | namespace FileSys { | ||
| 15 | |||
| 16 | class AesCtrStorage : public IStorage { | ||
| 17 | YUZU_NON_COPYABLE(AesCtrStorage); | ||
| 18 | YUZU_NON_MOVEABLE(AesCtrStorage); | ||
| 19 | |||
| 20 | public: | ||
| 21 | static constexpr size_t BlockSize = 0x10; | ||
| 22 | static constexpr size_t KeySize = 0x10; | ||
| 23 | static constexpr size_t IvSize = 0x10; | ||
| 24 | |||
| 25 | public: | ||
| 26 | static void MakeIv(void* dst, size_t dst_size, u64 upper, s64 offset); | ||
| 27 | |||
| 28 | public: | ||
| 29 | AesCtrStorage(VirtualFile base, const void* key, size_t key_size, const void* iv, | ||
| 30 | size_t iv_size); | ||
| 31 | |||
| 32 | virtual size_t Read(u8* buffer, size_t size, size_t offset) const override; | ||
| 33 | virtual size_t Write(const u8* buffer, size_t size, size_t offset) override; | ||
| 34 | virtual size_t GetSize() const override; | ||
| 35 | |||
| 36 | private: | ||
| 37 | VirtualFile m_base_storage; | ||
| 38 | std::array<u8, KeySize> m_key; | ||
| 39 | std::array<u8, IvSize> m_iv; | ||
| 40 | mutable std::optional<Core::Crypto::AESCipher<Core::Crypto::Key128>> m_cipher; | ||
| 41 | }; | ||
| 42 | |||
| 43 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "common/alignment.h" | ||
| 5 | #include "common/swap.h" | ||
| 6 | #include "core/file_sys/errors.h" | ||
| 7 | #include "core/file_sys/fssystem/fssystem_aes_xts_storage.h" | ||
| 8 | #include "core/file_sys/fssystem/fssystem_pooled_buffer.h" | ||
| 9 | #include "core/file_sys/fssystem/fssystem_utility.h" | ||
| 10 | |||
| 11 | namespace FileSys { | ||
| 12 | |||
| 13 | void AesXtsStorage::MakeAesXtsIv(void* dst, size_t dst_size, s64 offset, size_t block_size) { | ||
| 14 | ASSERT(dst != nullptr); | ||
| 15 | ASSERT(dst_size == IvSize); | ||
| 16 | ASSERT(offset >= 0); | ||
| 17 | |||
| 18 | const uintptr_t out_addr = reinterpret_cast<uintptr_t>(dst); | ||
| 19 | |||
| 20 | *reinterpret_cast<s64_be*>(out_addr + sizeof(s64)) = offset / block_size; | ||
| 21 | } | ||
| 22 | |||
| 23 | AesXtsStorage::AesXtsStorage(VirtualFile base, const void* key1, const void* key2, size_t key_size, | ||
| 24 | const void* iv, size_t iv_size, size_t block_size) | ||
| 25 | : m_base_storage(std::move(base)), m_block_size(block_size), m_mutex() { | ||
| 26 | ASSERT(m_base_storage != nullptr); | ||
| 27 | ASSERT(key1 != nullptr); | ||
| 28 | ASSERT(key2 != nullptr); | ||
| 29 | ASSERT(iv != nullptr); | ||
| 30 | ASSERT(key_size == KeySize); | ||
| 31 | ASSERT(iv_size == IvSize); | ||
| 32 | ASSERT(Common::IsAligned(m_block_size, AesBlockSize)); | ||
| 33 | |||
| 34 | std::memcpy(m_key.data() + 0, key1, KeySize); | ||
| 35 | std::memcpy(m_key.data() + 0x10, key2, KeySize); | ||
| 36 | std::memcpy(m_iv.data(), iv, IvSize); | ||
| 37 | |||
| 38 | m_cipher.emplace(m_key, Core::Crypto::Mode::XTS); | ||
| 39 | } | ||
| 40 | |||
| 41 | size_t AesXtsStorage::Read(u8* buffer, size_t size, size_t offset) const { | ||
| 42 | // Allow zero-size reads. | ||
| 43 | if (size == 0) { | ||
| 44 | return size; | ||
| 45 | } | ||
| 46 | |||
| 47 | // Ensure buffer is valid. | ||
| 48 | ASSERT(buffer != nullptr); | ||
| 49 | |||
| 50 | // We can only read at block aligned offsets. | ||
| 51 | ASSERT(Common::IsAligned(offset, AesBlockSize)); | ||
| 52 | ASSERT(Common::IsAligned(size, AesBlockSize)); | ||
| 53 | |||
| 54 | // Read the data. | ||
| 55 | m_base_storage->Read(buffer, size, offset); | ||
| 56 | |||
| 57 | // Setup the counter. | ||
| 58 | std::array<u8, IvSize> ctr; | ||
| 59 | std::memcpy(ctr.data(), m_iv.data(), IvSize); | ||
| 60 | AddCounter(ctr.data(), IvSize, offset / m_block_size); | ||
| 61 | |||
| 62 | // Handle any unaligned data before the start. | ||
| 63 | size_t processed_size = 0; | ||
| 64 | if ((offset % m_block_size) != 0) { | ||
| 65 | // Determine the size of the pre-data read. | ||
| 66 | const size_t skip_size = | ||
| 67 | static_cast<size_t>(offset - Common::AlignDown(offset, m_block_size)); | ||
| 68 | const size_t data_size = std::min(size, m_block_size - skip_size); | ||
| 69 | |||
| 70 | // Decrypt into a pooled buffer. | ||
| 71 | { | ||
| 72 | PooledBuffer tmp_buf(m_block_size, m_block_size); | ||
| 73 | ASSERT(tmp_buf.GetSize() >= m_block_size); | ||
| 74 | |||
| 75 | std::memset(tmp_buf.GetBuffer(), 0, skip_size); | ||
| 76 | std::memcpy(tmp_buf.GetBuffer() + skip_size, buffer, data_size); | ||
| 77 | |||
| 78 | m_cipher->SetIV(ctr); | ||
| 79 | m_cipher->Transcode(tmp_buf.GetBuffer(), m_block_size, tmp_buf.GetBuffer(), | ||
| 80 | Core::Crypto::Op::Decrypt); | ||
| 81 | |||
| 82 | std::memcpy(buffer, tmp_buf.GetBuffer() + skip_size, data_size); | ||
| 83 | } | ||
| 84 | |||
| 85 | AddCounter(ctr.data(), IvSize, 1); | ||
| 86 | processed_size += data_size; | ||
| 87 | ASSERT(processed_size == std::min(size, m_block_size - skip_size)); | ||
| 88 | } | ||
| 89 | |||
| 90 | // Decrypt aligned chunks. | ||
| 91 | char* cur = reinterpret_cast<char*>(buffer) + processed_size; | ||
| 92 | size_t remaining = size - processed_size; | ||
| 93 | while (remaining > 0) { | ||
| 94 | const size_t cur_size = std::min(m_block_size, remaining); | ||
| 95 | |||
| 96 | m_cipher->SetIV(ctr); | ||
| 97 | m_cipher->Transcode(cur, cur_size, cur, Core::Crypto::Op::Decrypt); | ||
| 98 | |||
| 99 | remaining -= cur_size; | ||
| 100 | cur += cur_size; | ||
| 101 | |||
| 102 | AddCounter(ctr.data(), IvSize, 1); | ||
| 103 | } | ||
| 104 | |||
| 105 | return size; | ||
| 106 | } | ||
| 107 | |||
| 108 | size_t AesXtsStorage::GetSize() const { | ||
| 109 | return m_base_storage->GetSize(); | ||
| 110 | } | ||
| 111 | |||
| 112 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <optional> | ||
| 7 | |||
| 8 | #include "core/crypto/aes_util.h" | ||
| 9 | #include "core/crypto/key_manager.h" | ||
| 10 | #include "core/file_sys/fssystem/fs_i_storage.h" | ||
| 11 | |||
| 12 | namespace FileSys { | ||
| 13 | |||
| 14 | class AesXtsStorage : public IReadOnlyStorage { | ||
| 15 | YUZU_NON_COPYABLE(AesXtsStorage); | ||
| 16 | YUZU_NON_MOVEABLE(AesXtsStorage); | ||
| 17 | |||
| 18 | public: | ||
| 19 | static constexpr size_t AesBlockSize = 0x10; | ||
| 20 | static constexpr size_t KeySize = 0x20; | ||
| 21 | static constexpr size_t IvSize = 0x10; | ||
| 22 | |||
| 23 | public: | ||
| 24 | static void MakeAesXtsIv(void* dst, size_t dst_size, s64 offset, size_t block_size); | ||
| 25 | |||
| 26 | public: | ||
| 27 | AesXtsStorage(VirtualFile base, const void* key1, const void* key2, size_t key_size, | ||
| 28 | const void* iv, size_t iv_size, size_t block_size); | ||
| 29 | |||
| 30 | virtual size_t Read(u8* buffer, size_t size, size_t offset) const override; | ||
| 31 | virtual size_t GetSize() const override; | ||
| 32 | |||
| 33 | private: | ||
| 34 | VirtualFile m_base_storage; | ||
| 35 | std::array<u8, KeySize> m_key; | ||
| 36 | std::array<u8, IvSize> m_iv; | ||
| 37 | const size_t m_block_size; | ||
| 38 | std::mutex m_mutex; | ||
| 39 | mutable std::optional<Core::Crypto::AESCipher<Core::Crypto::Key256>> m_cipher; | ||
| 40 | }; | ||
| 41 | |||
| 42 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "common/alignment.h" | ||
| 7 | #include "core/file_sys/errors.h" | ||
| 8 | #include "core/file_sys/fssystem/fs_i_storage.h" | ||
| 9 | #include "core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.h" | ||
| 10 | #include "core/file_sys/fssystem/fssystem_pooled_buffer.h" | ||
| 11 | |||
| 12 | namespace FileSys { | ||
| 13 | |||
| 14 | template <size_t DataAlign_, size_t BufferAlign_> | ||
| 15 | class AlignmentMatchingStorage : public IStorage { | ||
| 16 | YUZU_NON_COPYABLE(AlignmentMatchingStorage); | ||
| 17 | YUZU_NON_MOVEABLE(AlignmentMatchingStorage); | ||
| 18 | |||
| 19 | public: | ||
| 20 | static constexpr size_t DataAlign = DataAlign_; | ||
| 21 | static constexpr size_t BufferAlign = BufferAlign_; | ||
| 22 | |||
| 23 | static constexpr size_t DataAlignMax = 0x200; | ||
| 24 | static_assert(DataAlign <= DataAlignMax); | ||
| 25 | static_assert(Common::IsPowerOfTwo(DataAlign)); | ||
| 26 | static_assert(Common::IsPowerOfTwo(BufferAlign)); | ||
| 27 | |||
| 28 | private: | ||
| 29 | VirtualFile m_base_storage; | ||
| 30 | s64 m_base_storage_size; | ||
| 31 | |||
| 32 | public: | ||
| 33 | explicit AlignmentMatchingStorage(VirtualFile bs) : m_base_storage(std::move(bs)) {} | ||
| 34 | |||
| 35 | virtual size_t Read(u8* buffer, size_t size, size_t offset) const override { | ||
| 36 | // Allocate a work buffer on stack. | ||
| 37 | alignas(DataAlignMax) std::array<char, DataAlign> work_buf; | ||
| 38 | |||
| 39 | // Succeed if zero size. | ||
| 40 | if (size == 0) { | ||
| 41 | return size; | ||
| 42 | } | ||
| 43 | |||
| 44 | // Validate arguments. | ||
| 45 | ASSERT(buffer != nullptr); | ||
| 46 | |||
| 47 | s64 bs_size = this->GetSize(); | ||
| 48 | ASSERT(R_SUCCEEDED(IStorage::CheckAccessRange(offset, size, bs_size))); | ||
| 49 | |||
| 50 | return AlignmentMatchingStorageImpl::Read(m_base_storage, work_buf.data(), work_buf.size(), | ||
| 51 | DataAlign, BufferAlign, offset, buffer, size); | ||
| 52 | } | ||
| 53 | |||
| 54 | virtual size_t Write(const u8* buffer, size_t size, size_t offset) override { | ||
| 55 | // Allocate a work buffer on stack. | ||
| 56 | alignas(DataAlignMax) std::array<char, DataAlign> work_buf; | ||
| 57 | |||
| 58 | // Succeed if zero size. | ||
| 59 | if (size == 0) { | ||
| 60 | return size; | ||
| 61 | } | ||
| 62 | |||
| 63 | // Validate arguments. | ||
| 64 | ASSERT(buffer != nullptr); | ||
| 65 | |||
| 66 | s64 bs_size = this->GetSize(); | ||
| 67 | ASSERT(R_SUCCEEDED(IStorage::CheckAccessRange(offset, size, bs_size))); | ||
| 68 | |||
| 69 | return AlignmentMatchingStorageImpl::Write(m_base_storage, work_buf.data(), work_buf.size(), | ||
| 70 | DataAlign, BufferAlign, offset, buffer, size); | ||
| 71 | } | ||
| 72 | |||
| 73 | virtual size_t GetSize() const override { | ||
| 74 | return m_base_storage->GetSize(); | ||
| 75 | } | ||
| 76 | }; | ||
| 77 | |||
| 78 | template <size_t BufferAlign_> | ||
| 79 | class AlignmentMatchingStoragePooledBuffer : public IStorage { | ||
| 80 | YUZU_NON_COPYABLE(AlignmentMatchingStoragePooledBuffer); | ||
| 81 | YUZU_NON_MOVEABLE(AlignmentMatchingStoragePooledBuffer); | ||
| 82 | |||
| 83 | public: | ||
| 84 | static constexpr size_t BufferAlign = BufferAlign_; | ||
| 85 | |||
| 86 | static_assert(Common::IsPowerOfTwo(BufferAlign)); | ||
| 87 | |||
| 88 | private: | ||
| 89 | VirtualFile m_base_storage; | ||
| 90 | s64 m_base_storage_size; | ||
| 91 | size_t m_data_align; | ||
| 92 | |||
| 93 | public: | ||
| 94 | explicit AlignmentMatchingStoragePooledBuffer(VirtualFile bs, size_t da) | ||
| 95 | : m_base_storage(std::move(bs)), m_data_align(da) { | ||
| 96 | ASSERT(Common::IsPowerOfTwo(da)); | ||
| 97 | } | ||
| 98 | |||
| 99 | virtual size_t Read(u8* buffer, size_t size, size_t offset) const override { | ||
| 100 | // Succeed if zero size. | ||
| 101 | if (size == 0) { | ||
| 102 | return size; | ||
| 103 | } | ||
| 104 | |||
| 105 | // Validate arguments. | ||
| 106 | ASSERT(buffer != nullptr); | ||
| 107 | |||
| 108 | s64 bs_size = this->GetSize(); | ||
| 109 | ASSERT(R_SUCCEEDED(IStorage::CheckAccessRange(offset, size, bs_size))); | ||
| 110 | |||
| 111 | // Allocate a pooled buffer. | ||
| 112 | PooledBuffer pooled_buffer; | ||
| 113 | pooled_buffer.AllocateParticularlyLarge(m_data_align, m_data_align); | ||
| 114 | |||
| 115 | return AlignmentMatchingStorageImpl::Read(m_base_storage, pooled_buffer.GetBuffer(), | ||
| 116 | pooled_buffer.GetSize(), m_data_align, | ||
| 117 | BufferAlign, offset, buffer, size); | ||
| 118 | } | ||
| 119 | |||
| 120 | virtual size_t Write(const u8* buffer, size_t size, size_t offset) override { | ||
| 121 | // Succeed if zero size. | ||
| 122 | if (size == 0) { | ||
| 123 | return size; | ||
| 124 | } | ||
| 125 | |||
| 126 | // Validate arguments. | ||
| 127 | ASSERT(buffer != nullptr); | ||
| 128 | |||
| 129 | s64 bs_size = this->GetSize(); | ||
| 130 | ASSERT(R_SUCCEEDED(IStorage::CheckAccessRange(offset, size, bs_size))); | ||
| 131 | |||
| 132 | // Allocate a pooled buffer. | ||
| 133 | PooledBuffer pooled_buffer; | ||
| 134 | pooled_buffer.AllocateParticularlyLarge(m_data_align, m_data_align); | ||
| 135 | |||
| 136 | return AlignmentMatchingStorageImpl::Write(m_base_storage, pooled_buffer.GetBuffer(), | ||
| 137 | pooled_buffer.GetSize(), m_data_align, | ||
| 138 | BufferAlign, offset, buffer, size); | ||
| 139 | } | ||
| 140 | |||
| 141 | virtual size_t GetSize() const override { | ||
| 142 | return m_base_storage->GetSize(); | ||
| 143 | } | ||
| 144 | }; | ||
| 145 | |||
| 146 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "common/alignment.h" | ||
| 5 | #include "core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.h" | ||
| 6 | |||
| 7 | namespace FileSys { | ||
| 8 | |||
| 9 | namespace { | ||
| 10 | |||
| 11 | template <typename T> | ||
| 12 | constexpr size_t GetRoundDownDifference(T x, size_t align) { | ||
| 13 | return static_cast<size_t>(x - Common::AlignDown(x, align)); | ||
| 14 | } | ||
| 15 | |||
| 16 | template <typename T> | ||
| 17 | constexpr size_t GetRoundUpDifference(T x, size_t align) { | ||
| 18 | return static_cast<size_t>(Common::AlignUp(x, align) - x); | ||
| 19 | } | ||
| 20 | |||
| 21 | template <typename T> | ||
| 22 | size_t GetRoundUpDifference(T* x, size_t align) { | ||
| 23 | return GetRoundUpDifference(reinterpret_cast<uintptr_t>(x), align); | ||
| 24 | } | ||
| 25 | |||
| 26 | } // namespace | ||
| 27 | |||
| 28 | size_t AlignmentMatchingStorageImpl::Read(VirtualFile base_storage, char* work_buf, | ||
| 29 | size_t work_buf_size, size_t data_alignment, | ||
| 30 | size_t buffer_alignment, s64 offset, u8* buffer, | ||
| 31 | size_t size) { | ||
| 32 | // Check preconditions. | ||
| 33 | ASSERT(work_buf_size >= data_alignment); | ||
| 34 | |||
| 35 | // Succeed if zero size. | ||
| 36 | if (size == 0) { | ||
| 37 | return size; | ||
| 38 | } | ||
| 39 | |||
| 40 | // Validate arguments. | ||
| 41 | ASSERT(buffer != nullptr); | ||
| 42 | |||
| 43 | // Determine extents. | ||
| 44 | u8* aligned_core_buffer; | ||
| 45 | s64 core_offset; | ||
| 46 | size_t core_size; | ||
| 47 | size_t buffer_gap; | ||
| 48 | size_t offset_gap; | ||
| 49 | s64 covered_offset; | ||
| 50 | |||
| 51 | const size_t offset_round_up_difference = GetRoundUpDifference(offset, data_alignment); | ||
| 52 | if (Common::IsAligned(reinterpret_cast<uintptr_t>(buffer) + offset_round_up_difference, | ||
| 53 | buffer_alignment)) { | ||
| 54 | aligned_core_buffer = buffer + offset_round_up_difference; | ||
| 55 | |||
| 56 | core_offset = Common::AlignUp(offset, data_alignment); | ||
| 57 | core_size = (size < offset_round_up_difference) | ||
| 58 | ? 0 | ||
| 59 | : Common::AlignDown(size - offset_round_up_difference, data_alignment); | ||
| 60 | buffer_gap = 0; | ||
| 61 | offset_gap = 0; | ||
| 62 | |||
| 63 | covered_offset = core_size > 0 ? core_offset : offset; | ||
| 64 | } else { | ||
| 65 | const size_t buffer_round_up_difference = GetRoundUpDifference(buffer, buffer_alignment); | ||
| 66 | |||
| 67 | aligned_core_buffer = buffer + buffer_round_up_difference; | ||
| 68 | |||
| 69 | core_offset = Common::AlignDown(offset, data_alignment); | ||
| 70 | core_size = (size < buffer_round_up_difference) | ||
| 71 | ? 0 | ||
| 72 | : Common::AlignDown(size - buffer_round_up_difference, data_alignment); | ||
| 73 | buffer_gap = buffer_round_up_difference; | ||
| 74 | offset_gap = GetRoundDownDifference(offset, data_alignment); | ||
| 75 | |||
| 76 | covered_offset = offset; | ||
| 77 | } | ||
| 78 | |||
| 79 | // Read the core portion. | ||
| 80 | if (core_size > 0) { | ||
| 81 | base_storage->Read(aligned_core_buffer, core_size, core_offset); | ||
| 82 | |||
| 83 | if (offset_gap != 0 || buffer_gap != 0) { | ||
| 84 | std::memmove(aligned_core_buffer - buffer_gap, aligned_core_buffer + offset_gap, | ||
| 85 | core_size - offset_gap); | ||
| 86 | core_size -= offset_gap; | ||
| 87 | } | ||
| 88 | } | ||
| 89 | |||
| 90 | // Handle the head portion. | ||
| 91 | if (offset < covered_offset) { | ||
| 92 | const s64 head_offset = Common::AlignDown(offset, data_alignment); | ||
| 93 | const size_t head_size = static_cast<size_t>(covered_offset - offset); | ||
| 94 | |||
| 95 | ASSERT(GetRoundDownDifference(offset, data_alignment) + head_size <= work_buf_size); | ||
| 96 | |||
| 97 | base_storage->Read(reinterpret_cast<u8*>(work_buf), data_alignment, head_offset); | ||
| 98 | std::memcpy(buffer, work_buf + GetRoundDownDifference(offset, data_alignment), head_size); | ||
| 99 | } | ||
| 100 | |||
| 101 | // Handle the tail portion. | ||
| 102 | s64 tail_offset = covered_offset + core_size; | ||
| 103 | size_t remaining_tail_size = static_cast<size_t>((offset + size) - tail_offset); | ||
| 104 | while (remaining_tail_size > 0) { | ||
| 105 | const auto aligned_tail_offset = Common::AlignDown(tail_offset, data_alignment); | ||
| 106 | const auto cur_size = | ||
| 107 | std::min(static_cast<size_t>(aligned_tail_offset + data_alignment - tail_offset), | ||
| 108 | remaining_tail_size); | ||
| 109 | base_storage->Read(reinterpret_cast<u8*>(work_buf), data_alignment, aligned_tail_offset); | ||
| 110 | |||
| 111 | ASSERT((tail_offset - offset) + cur_size <= size); | ||
| 112 | ASSERT((tail_offset - aligned_tail_offset) + cur_size <= data_alignment); | ||
| 113 | std::memcpy(reinterpret_cast<char*>(buffer) + (tail_offset - offset), | ||
| 114 | work_buf + (tail_offset - aligned_tail_offset), cur_size); | ||
| 115 | |||
| 116 | remaining_tail_size -= cur_size; | ||
| 117 | tail_offset += cur_size; | ||
| 118 | } | ||
| 119 | |||
| 120 | return size; | ||
| 121 | } | ||
| 122 | |||
| 123 | size_t AlignmentMatchingStorageImpl::Write(VirtualFile base_storage, char* work_buf, | ||
| 124 | size_t work_buf_size, size_t data_alignment, | ||
| 125 | size_t buffer_alignment, s64 offset, const u8* buffer, | ||
| 126 | size_t size) { | ||
| 127 | // Check preconditions. | ||
| 128 | ASSERT(work_buf_size >= data_alignment); | ||
| 129 | |||
| 130 | // Succeed if zero size. | ||
| 131 | if (size == 0) { | ||
| 132 | return size; | ||
| 133 | } | ||
| 134 | |||
| 135 | // Validate arguments. | ||
| 136 | ASSERT(buffer != nullptr); | ||
| 137 | |||
| 138 | // Determine extents. | ||
| 139 | const u8* aligned_core_buffer; | ||
| 140 | s64 core_offset; | ||
| 141 | size_t core_size; | ||
| 142 | s64 covered_offset; | ||
| 143 | |||
| 144 | const size_t offset_round_up_difference = GetRoundUpDifference(offset, data_alignment); | ||
| 145 | if (Common::IsAligned(reinterpret_cast<uintptr_t>(buffer) + offset_round_up_difference, | ||
| 146 | buffer_alignment)) { | ||
| 147 | aligned_core_buffer = buffer + offset_round_up_difference; | ||
| 148 | |||
| 149 | core_offset = Common::AlignUp(offset, data_alignment); | ||
| 150 | core_size = (size < offset_round_up_difference) | ||
| 151 | ? 0 | ||
| 152 | : Common::AlignDown(size - offset_round_up_difference, data_alignment); | ||
| 153 | |||
| 154 | covered_offset = core_size > 0 ? core_offset : offset; | ||
| 155 | } else { | ||
| 156 | aligned_core_buffer = nullptr; | ||
| 157 | |||
| 158 | core_offset = Common::AlignDown(offset, data_alignment); | ||
| 159 | core_size = 0; | ||
| 160 | |||
| 161 | covered_offset = offset; | ||
| 162 | } | ||
| 163 | |||
| 164 | // Write the core portion. | ||
| 165 | if (core_size > 0) { | ||
| 166 | base_storage->Write(aligned_core_buffer, core_size, core_offset); | ||
| 167 | } | ||
| 168 | |||
| 169 | // Handle the head portion. | ||
| 170 | if (offset < covered_offset) { | ||
| 171 | const s64 head_offset = Common::AlignDown(offset, data_alignment); | ||
| 172 | const size_t head_size = static_cast<size_t>(covered_offset - offset); | ||
| 173 | |||
| 174 | ASSERT((offset - head_offset) + head_size <= data_alignment); | ||
| 175 | |||
| 176 | base_storage->Read(reinterpret_cast<u8*>(work_buf), data_alignment, head_offset); | ||
| 177 | std::memcpy(work_buf + (offset - head_offset), buffer, head_size); | ||
| 178 | base_storage->Write(reinterpret_cast<u8*>(work_buf), data_alignment, head_offset); | ||
| 179 | } | ||
| 180 | |||
| 181 | // Handle the tail portion. | ||
| 182 | s64 tail_offset = covered_offset + core_size; | ||
| 183 | size_t remaining_tail_size = static_cast<size_t>((offset + size) - tail_offset); | ||
| 184 | while (remaining_tail_size > 0) { | ||
| 185 | ASSERT(static_cast<size_t>(tail_offset - offset) < size); | ||
| 186 | |||
| 187 | const auto aligned_tail_offset = Common::AlignDown(tail_offset, data_alignment); | ||
| 188 | const auto cur_size = | ||
| 189 | std::min(static_cast<size_t>(aligned_tail_offset + data_alignment - tail_offset), | ||
| 190 | remaining_tail_size); | ||
| 191 | |||
| 192 | base_storage->Read(reinterpret_cast<u8*>(work_buf), data_alignment, aligned_tail_offset); | ||
| 193 | std::memcpy(work_buf + GetRoundDownDifference(tail_offset, data_alignment), | ||
| 194 | buffer + (tail_offset - offset), cur_size); | ||
| 195 | base_storage->Write(reinterpret_cast<u8*>(work_buf), data_alignment, aligned_tail_offset); | ||
| 196 | |||
| 197 | remaining_tail_size -= cur_size; | ||
| 198 | tail_offset += cur_size; | ||
| 199 | } | ||
| 200 | |||
| 201 | return size; | ||
| 202 | } | ||
| 203 | |||
| 204 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "core/file_sys/errors.h" | ||
| 7 | #include "core/file_sys/fssystem/fs_i_storage.h" | ||
| 8 | |||
| 9 | namespace FileSys { | ||
| 10 | |||
| 11 | class AlignmentMatchingStorageImpl { | ||
| 12 | public: | ||
| 13 | static size_t Read(VirtualFile base_storage, char* work_buf, size_t work_buf_size, | ||
| 14 | size_t data_alignment, size_t buffer_alignment, s64 offset, u8* buffer, | ||
| 15 | size_t size); | ||
| 16 | static size_t Write(VirtualFile base_storage, char* work_buf, size_t work_buf_size, | ||
| 17 | size_t data_alignment, size_t buffer_alignment, s64 offset, | ||
| 18 | const u8* buffer, size_t size); | ||
| 19 | }; | ||
| 20 | |||
| 21 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "core/file_sys/errors.h" | ||
| 5 | #include "core/file_sys/fssystem/fssystem_bucket_tree.h" | ||
| 6 | #include "core/file_sys/fssystem/fssystem_bucket_tree_utils.h" | ||
| 7 | #include "core/file_sys/fssystem/fssystem_pooled_buffer.h" | ||
| 8 | |||
| 9 | namespace FileSys { | ||
| 10 | |||
| 11 | namespace { | ||
| 12 | |||
| 13 | using Node = impl::BucketTreeNode<const s64*>; | ||
| 14 | static_assert(sizeof(Node) == sizeof(BucketTree::NodeHeader)); | ||
| 15 | static_assert(std::is_trivial_v<Node>); | ||
| 16 | |||
| 17 | constexpr inline s32 NodeHeaderSize = sizeof(BucketTree::NodeHeader); | ||
| 18 | |||
| 19 | class StorageNode { | ||
| 20 | private: | ||
| 21 | class Offset { | ||
| 22 | public: | ||
| 23 | using difference_type = s64; | ||
| 24 | |||
| 25 | private: | ||
| 26 | s64 m_offset; | ||
| 27 | s32 m_stride; | ||
| 28 | |||
| 29 | public: | ||
| 30 | constexpr Offset(s64 offset, s32 stride) : m_offset(offset), m_stride(stride) {} | ||
| 31 | |||
| 32 | constexpr Offset& operator++() { | ||
| 33 | m_offset += m_stride; | ||
| 34 | return *this; | ||
| 35 | } | ||
| 36 | constexpr Offset operator++(int) { | ||
| 37 | Offset ret(*this); | ||
| 38 | m_offset += m_stride; | ||
| 39 | return ret; | ||
| 40 | } | ||
| 41 | |||
| 42 | constexpr Offset& operator--() { | ||
| 43 | m_offset -= m_stride; | ||
| 44 | return *this; | ||
| 45 | } | ||
| 46 | constexpr Offset operator--(int) { | ||
| 47 | Offset ret(*this); | ||
| 48 | m_offset -= m_stride; | ||
| 49 | return ret; | ||
| 50 | } | ||
| 51 | |||
| 52 | constexpr difference_type operator-(const Offset& rhs) const { | ||
| 53 | return (m_offset - rhs.m_offset) / m_stride; | ||
| 54 | } | ||
| 55 | |||
| 56 | constexpr Offset operator+(difference_type ofs) const { | ||
| 57 | return Offset(m_offset + ofs * m_stride, m_stride); | ||
| 58 | } | ||
| 59 | constexpr Offset operator-(difference_type ofs) const { | ||
| 60 | return Offset(m_offset - ofs * m_stride, m_stride); | ||
| 61 | } | ||
| 62 | |||
| 63 | constexpr Offset& operator+=(difference_type ofs) { | ||
| 64 | m_offset += ofs * m_stride; | ||
| 65 | return *this; | ||
| 66 | } | ||
| 67 | constexpr Offset& operator-=(difference_type ofs) { | ||
| 68 | m_offset -= ofs * m_stride; | ||
| 69 | return *this; | ||
| 70 | } | ||
| 71 | |||
| 72 | constexpr bool operator==(const Offset& rhs) const { | ||
| 73 | return m_offset == rhs.m_offset; | ||
| 74 | } | ||
| 75 | constexpr bool operator!=(const Offset& rhs) const { | ||
| 76 | return m_offset != rhs.m_offset; | ||
| 77 | } | ||
| 78 | |||
| 79 | constexpr s64 Get() const { | ||
| 80 | return m_offset; | ||
| 81 | } | ||
| 82 | }; | ||
| 83 | |||
| 84 | private: | ||
| 85 | const Offset m_start; | ||
| 86 | const s32 m_count; | ||
| 87 | s32 m_index; | ||
| 88 | |||
| 89 | public: | ||
| 90 | StorageNode(size_t size, s32 count) | ||
| 91 | : m_start(NodeHeaderSize, static_cast<s32>(size)), m_count(count), m_index(-1) {} | ||
| 92 | StorageNode(s64 ofs, size_t size, s32 count) | ||
| 93 | : m_start(NodeHeaderSize + ofs, static_cast<s32>(size)), m_count(count), m_index(-1) {} | ||
| 94 | |||
| 95 | s32 GetIndex() const { | ||
| 96 | return m_index; | ||
| 97 | } | ||
| 98 | |||
| 99 | void Find(const char* buffer, s64 virtual_address) { | ||
| 100 | s32 end = m_count; | ||
| 101 | auto pos = m_start; | ||
| 102 | |||
| 103 | while (end > 0) { | ||
| 104 | auto half = end / 2; | ||
| 105 | auto mid = pos + half; | ||
| 106 | |||
| 107 | s64 offset = 0; | ||
| 108 | std::memcpy(std::addressof(offset), buffer + mid.Get(), sizeof(s64)); | ||
| 109 | |||
| 110 | if (offset <= virtual_address) { | ||
| 111 | pos = mid + 1; | ||
| 112 | end -= half + 1; | ||
| 113 | } else { | ||
| 114 | end = half; | ||
| 115 | } | ||
| 116 | } | ||
| 117 | |||
| 118 | m_index = static_cast<s32>(pos - m_start) - 1; | ||
| 119 | } | ||
| 120 | |||
| 121 | Result Find(VirtualFile storage, s64 virtual_address) { | ||
| 122 | s32 end = m_count; | ||
| 123 | auto pos = m_start; | ||
| 124 | |||
| 125 | while (end > 0) { | ||
| 126 | auto half = end / 2; | ||
| 127 | auto mid = pos + half; | ||
| 128 | |||
| 129 | s64 offset = 0; | ||
| 130 | storage->ReadObject(std::addressof(offset), mid.Get()); | ||
| 131 | |||
| 132 | if (offset <= virtual_address) { | ||
| 133 | pos = mid + 1; | ||
| 134 | end -= half + 1; | ||
| 135 | } else { | ||
| 136 | end = half; | ||
| 137 | } | ||
| 138 | } | ||
| 139 | |||
| 140 | m_index = static_cast<s32>(pos - m_start) - 1; | ||
| 141 | R_SUCCEED(); | ||
| 142 | } | ||
| 143 | }; | ||
| 144 | |||
| 145 | } // namespace | ||
| 146 | |||
| 147 | void BucketTree::Header::Format(s32 entry_count_) { | ||
| 148 | ASSERT(entry_count_ >= 0); | ||
| 149 | |||
| 150 | this->magic = Magic; | ||
| 151 | this->version = Version; | ||
| 152 | this->entry_count = entry_count_; | ||
| 153 | this->reserved = 0; | ||
| 154 | } | ||
| 155 | |||
| 156 | Result BucketTree::Header::Verify() const { | ||
| 157 | R_UNLESS(this->magic == Magic, ResultInvalidBucketTreeSignature); | ||
| 158 | R_UNLESS(this->entry_count >= 0, ResultInvalidBucketTreeEntryCount); | ||
| 159 | R_UNLESS(this->version <= Version, ResultUnsupportedVersion); | ||
| 160 | R_SUCCEED(); | ||
| 161 | } | ||
| 162 | |||
| 163 | Result BucketTree::NodeHeader::Verify(s32 node_index, size_t node_size, size_t entry_size) const { | ||
| 164 | R_UNLESS(this->index == node_index, ResultInvalidBucketTreeNodeIndex); | ||
| 165 | R_UNLESS(entry_size != 0 && node_size >= entry_size + NodeHeaderSize, ResultInvalidSize); | ||
| 166 | |||
| 167 | const size_t max_entry_count = (node_size - NodeHeaderSize) / entry_size; | ||
| 168 | R_UNLESS(this->count > 0 && static_cast<size_t>(this->count) <= max_entry_count, | ||
| 169 | ResultInvalidBucketTreeNodeEntryCount); | ||
| 170 | R_UNLESS(this->offset >= 0, ResultInvalidBucketTreeNodeOffset); | ||
| 171 | |||
| 172 | R_SUCCEED(); | ||
| 173 | } | ||
| 174 | |||
| 175 | Result BucketTree::Initialize(VirtualFile node_storage, VirtualFile entry_storage, size_t node_size, | ||
| 176 | size_t entry_size, s32 entry_count) { | ||
| 177 | // Validate preconditions. | ||
| 178 | ASSERT(entry_size >= sizeof(s64)); | ||
| 179 | ASSERT(node_size >= entry_size + sizeof(NodeHeader)); | ||
| 180 | ASSERT(NodeSizeMin <= node_size && node_size <= NodeSizeMax); | ||
| 181 | ASSERT(Common::IsPowerOfTwo(node_size)); | ||
| 182 | ASSERT(!this->IsInitialized()); | ||
| 183 | |||
| 184 | // Ensure valid entry count. | ||
| 185 | R_UNLESS(entry_count > 0, ResultInvalidArgument); | ||
| 186 | |||
| 187 | // Allocate node. | ||
| 188 | R_UNLESS(m_node_l1.Allocate(node_size), ResultBufferAllocationFailed); | ||
| 189 | ON_RESULT_FAILURE { | ||
| 190 | m_node_l1.Free(node_size); | ||
| 191 | }; | ||
| 192 | |||
| 193 | // Read node. | ||
| 194 | node_storage->Read(reinterpret_cast<u8*>(m_node_l1.Get()), node_size); | ||
| 195 | |||
| 196 | // Verify node. | ||
| 197 | R_TRY(m_node_l1->Verify(0, node_size, sizeof(s64))); | ||
| 198 | |||
| 199 | // Validate offsets. | ||
| 200 | const auto offset_count = GetOffsetCount(node_size); | ||
| 201 | const auto entry_set_count = GetEntrySetCount(node_size, entry_size, entry_count); | ||
| 202 | const auto* const node = m_node_l1.Get<Node>(); | ||
| 203 | |||
| 204 | s64 start_offset; | ||
| 205 | if (offset_count < entry_set_count && node->GetCount() < offset_count) { | ||
| 206 | start_offset = *node->GetEnd(); | ||
| 207 | } else { | ||
| 208 | start_offset = *node->GetBegin(); | ||
| 209 | } | ||
| 210 | const auto end_offset = node->GetEndOffset(); | ||
| 211 | |||
| 212 | R_UNLESS(0 <= start_offset && start_offset <= node->GetBeginOffset(), | ||
| 213 | ResultInvalidBucketTreeEntryOffset); | ||
| 214 | R_UNLESS(start_offset < end_offset, ResultInvalidBucketTreeEntryOffset); | ||
| 215 | |||
| 216 | // Set member variables. | ||
| 217 | m_node_storage = node_storage; | ||
| 218 | m_entry_storage = entry_storage; | ||
| 219 | m_node_size = node_size; | ||
| 220 | m_entry_size = entry_size; | ||
| 221 | m_entry_count = entry_count; | ||
| 222 | m_offset_count = offset_count; | ||
| 223 | m_entry_set_count = entry_set_count; | ||
| 224 | |||
| 225 | m_offset_cache.offsets.start_offset = start_offset; | ||
| 226 | m_offset_cache.offsets.end_offset = end_offset; | ||
| 227 | m_offset_cache.is_initialized = true; | ||
| 228 | |||
| 229 | // We succeeded. | ||
| 230 | R_SUCCEED(); | ||
| 231 | } | ||
| 232 | |||
| 233 | void BucketTree::Initialize(size_t node_size, s64 end_offset) { | ||
| 234 | ASSERT(NodeSizeMin <= node_size && node_size <= NodeSizeMax); | ||
| 235 | ASSERT(Common::IsPowerOfTwo(node_size)); | ||
| 236 | ASSERT(end_offset > 0); | ||
| 237 | ASSERT(!this->IsInitialized()); | ||
| 238 | |||
| 239 | m_node_size = node_size; | ||
| 240 | |||
| 241 | m_offset_cache.offsets.start_offset = 0; | ||
| 242 | m_offset_cache.offsets.end_offset = end_offset; | ||
| 243 | m_offset_cache.is_initialized = true; | ||
| 244 | } | ||
| 245 | |||
| 246 | void BucketTree::Finalize() { | ||
| 247 | if (this->IsInitialized()) { | ||
| 248 | m_node_storage = VirtualFile(); | ||
| 249 | m_entry_storage = VirtualFile(); | ||
| 250 | m_node_l1.Free(m_node_size); | ||
| 251 | m_node_size = 0; | ||
| 252 | m_entry_size = 0; | ||
| 253 | m_entry_count = 0; | ||
| 254 | m_offset_count = 0; | ||
| 255 | m_entry_set_count = 0; | ||
| 256 | |||
| 257 | m_offset_cache.offsets.start_offset = 0; | ||
| 258 | m_offset_cache.offsets.end_offset = 0; | ||
| 259 | m_offset_cache.is_initialized = false; | ||
| 260 | } | ||
| 261 | } | ||
| 262 | |||
| 263 | Result BucketTree::Find(Visitor* visitor, s64 virtual_address) { | ||
| 264 | ASSERT(visitor != nullptr); | ||
| 265 | ASSERT(this->IsInitialized()); | ||
| 266 | |||
| 267 | R_UNLESS(virtual_address >= 0, ResultInvalidOffset); | ||
| 268 | R_UNLESS(!this->IsEmpty(), ResultOutOfRange); | ||
| 269 | |||
| 270 | BucketTree::Offsets offsets; | ||
| 271 | R_TRY(this->GetOffsets(std::addressof(offsets))); | ||
| 272 | |||
| 273 | R_TRY(visitor->Initialize(this, offsets)); | ||
| 274 | |||
| 275 | R_RETURN(visitor->Find(virtual_address)); | ||
| 276 | } | ||
| 277 | |||
| 278 | Result BucketTree::InvalidateCache() { | ||
| 279 | // Reset our offsets. | ||
| 280 | m_offset_cache.is_initialized = false; | ||
| 281 | |||
| 282 | R_SUCCEED(); | ||
| 283 | } | ||
| 284 | |||
| 285 | Result BucketTree::EnsureOffsetCache() { | ||
| 286 | // If we already have an offset cache, we're good. | ||
| 287 | R_SUCCEED_IF(m_offset_cache.is_initialized); | ||
| 288 | |||
| 289 | // Acquire exclusive right to edit the offset cache. | ||
| 290 | std::scoped_lock lk(m_offset_cache.mutex); | ||
| 291 | |||
| 292 | // Check again, to be sure. | ||
| 293 | R_SUCCEED_IF(m_offset_cache.is_initialized); | ||
| 294 | |||
| 295 | // Read/verify L1. | ||
| 296 | m_node_storage->Read(reinterpret_cast<u8*>(m_node_l1.Get()), m_node_size); | ||
| 297 | R_TRY(m_node_l1->Verify(0, m_node_size, sizeof(s64))); | ||
| 298 | |||
| 299 | // Get the node. | ||
| 300 | auto* const node = m_node_l1.Get<Node>(); | ||
| 301 | |||
| 302 | s64 start_offset; | ||
| 303 | if (m_offset_count < m_entry_set_count && node->GetCount() < m_offset_count) { | ||
| 304 | start_offset = *node->GetEnd(); | ||
| 305 | } else { | ||
| 306 | start_offset = *node->GetBegin(); | ||
| 307 | } | ||
| 308 | const auto end_offset = node->GetEndOffset(); | ||
| 309 | |||
| 310 | R_UNLESS(0 <= start_offset && start_offset <= node->GetBeginOffset(), | ||
| 311 | ResultInvalidBucketTreeEntryOffset); | ||
| 312 | R_UNLESS(start_offset < end_offset, ResultInvalidBucketTreeEntryOffset); | ||
| 313 | |||
| 314 | m_offset_cache.offsets.start_offset = start_offset; | ||
| 315 | m_offset_cache.offsets.end_offset = end_offset; | ||
| 316 | m_offset_cache.is_initialized = true; | ||
| 317 | |||
| 318 | R_SUCCEED(); | ||
| 319 | } | ||
| 320 | |||
| 321 | Result BucketTree::Visitor::Initialize(const BucketTree* tree, const BucketTree::Offsets& offsets) { | ||
| 322 | ASSERT(tree != nullptr); | ||
| 323 | ASSERT(m_tree == nullptr || m_tree == tree); | ||
| 324 | |||
| 325 | if (m_entry == nullptr) { | ||
| 326 | m_entry = ::operator new(tree->m_entry_size); | ||
| 327 | R_UNLESS(m_entry != nullptr, ResultBufferAllocationFailed); | ||
| 328 | |||
| 329 | m_tree = tree; | ||
| 330 | m_offsets = offsets; | ||
| 331 | } | ||
| 332 | |||
| 333 | R_SUCCEED(); | ||
| 334 | } | ||
| 335 | |||
| 336 | Result BucketTree::Visitor::MoveNext() { | ||
| 337 | R_UNLESS(this->IsValid(), ResultOutOfRange); | ||
| 338 | |||
| 339 | // Invalidate our index, and read the header for the next index. | ||
| 340 | auto entry_index = m_entry_index + 1; | ||
| 341 | if (entry_index == m_entry_set.info.count) { | ||
| 342 | const auto entry_set_index = m_entry_set.info.index + 1; | ||
| 343 | R_UNLESS(entry_set_index < m_entry_set_count, ResultOutOfRange); | ||
| 344 | |||
| 345 | m_entry_index = -1; | ||
| 346 | |||
| 347 | const auto end = m_entry_set.info.end; | ||
| 348 | |||
| 349 | const auto entry_set_size = m_tree->m_node_size; | ||
| 350 | const auto entry_set_offset = entry_set_index * static_cast<s64>(entry_set_size); | ||
| 351 | |||
| 352 | m_tree->m_entry_storage->ReadObject(std::addressof(m_entry_set), entry_set_offset); | ||
| 353 | R_TRY(m_entry_set.header.Verify(entry_set_index, entry_set_size, m_tree->m_entry_size)); | ||
| 354 | |||
| 355 | R_UNLESS(m_entry_set.info.start == end && m_entry_set.info.start < m_entry_set.info.end, | ||
| 356 | ResultInvalidBucketTreeEntrySetOffset); | ||
| 357 | |||
| 358 | entry_index = 0; | ||
| 359 | } else { | ||
| 360 | m_entry_index = -1; | ||
| 361 | } | ||
| 362 | |||
| 363 | // Read the new entry. | ||
| 364 | const auto entry_size = m_tree->m_entry_size; | ||
| 365 | const auto entry_offset = impl::GetBucketTreeEntryOffset( | ||
| 366 | m_entry_set.info.index, m_tree->m_node_size, entry_size, entry_index); | ||
| 367 | m_tree->m_entry_storage->Read(reinterpret_cast<u8*>(m_entry), entry_size, entry_offset); | ||
| 368 | |||
| 369 | // Note that we changed index. | ||
| 370 | m_entry_index = entry_index; | ||
| 371 | R_SUCCEED(); | ||
| 372 | } | ||
| 373 | |||
| 374 | Result BucketTree::Visitor::MovePrevious() { | ||
| 375 | R_UNLESS(this->IsValid(), ResultOutOfRange); | ||
| 376 | |||
| 377 | // Invalidate our index, and read the header for the previous index. | ||
| 378 | auto entry_index = m_entry_index; | ||
| 379 | if (entry_index == 0) { | ||
| 380 | R_UNLESS(m_entry_set.info.index > 0, ResultOutOfRange); | ||
| 381 | |||
| 382 | m_entry_index = -1; | ||
| 383 | |||
| 384 | const auto start = m_entry_set.info.start; | ||
| 385 | |||
| 386 | const auto entry_set_size = m_tree->m_node_size; | ||
| 387 | const auto entry_set_index = m_entry_set.info.index - 1; | ||
| 388 | const auto entry_set_offset = entry_set_index * static_cast<s64>(entry_set_size); | ||
| 389 | |||
| 390 | m_tree->m_entry_storage->ReadObject(std::addressof(m_entry_set), entry_set_offset); | ||
| 391 | R_TRY(m_entry_set.header.Verify(entry_set_index, entry_set_size, m_tree->m_entry_size)); | ||
| 392 | |||
| 393 | R_UNLESS(m_entry_set.info.end == start && m_entry_set.info.start < m_entry_set.info.end, | ||
| 394 | ResultInvalidBucketTreeEntrySetOffset); | ||
| 395 | |||
| 396 | entry_index = m_entry_set.info.count; | ||
| 397 | } else { | ||
| 398 | m_entry_index = -1; | ||
| 399 | } | ||
| 400 | |||
| 401 | --entry_index; | ||
| 402 | |||
| 403 | // Read the new entry. | ||
| 404 | const auto entry_size = m_tree->m_entry_size; | ||
| 405 | const auto entry_offset = impl::GetBucketTreeEntryOffset( | ||
| 406 | m_entry_set.info.index, m_tree->m_node_size, entry_size, entry_index); | ||
| 407 | m_tree->m_entry_storage->Read(reinterpret_cast<u8*>(m_entry), entry_size, entry_offset); | ||
| 408 | |||
| 409 | // Note that we changed index. | ||
| 410 | m_entry_index = entry_index; | ||
| 411 | R_SUCCEED(); | ||
| 412 | } | ||
| 413 | |||
| 414 | Result BucketTree::Visitor::Find(s64 virtual_address) { | ||
| 415 | ASSERT(m_tree != nullptr); | ||
| 416 | |||
| 417 | // Get the node. | ||
| 418 | const auto* const node = m_tree->m_node_l1.Get<Node>(); | ||
| 419 | R_UNLESS(virtual_address < node->GetEndOffset(), ResultOutOfRange); | ||
| 420 | |||
| 421 | // Get the entry set index. | ||
| 422 | s32 entry_set_index = -1; | ||
| 423 | if (m_tree->IsExistOffsetL2OnL1() && virtual_address < node->GetBeginOffset()) { | ||
| 424 | const auto start = node->GetEnd(); | ||
| 425 | const auto end = node->GetBegin() + m_tree->m_offset_count; | ||
| 426 | |||
| 427 | auto pos = std::upper_bound(start, end, virtual_address); | ||
| 428 | R_UNLESS(start < pos, ResultOutOfRange); | ||
| 429 | --pos; | ||
| 430 | |||
| 431 | entry_set_index = static_cast<s32>(pos - start); | ||
| 432 | } else { | ||
| 433 | const auto start = node->GetBegin(); | ||
| 434 | const auto end = node->GetEnd(); | ||
| 435 | |||
| 436 | auto pos = std::upper_bound(start, end, virtual_address); | ||
| 437 | R_UNLESS(start < pos, ResultOutOfRange); | ||
| 438 | --pos; | ||
| 439 | |||
| 440 | if (m_tree->IsExistL2()) { | ||
| 441 | const auto node_index = static_cast<s32>(pos - start); | ||
| 442 | R_UNLESS(0 <= node_index && node_index < m_tree->m_offset_count, | ||
| 443 | ResultInvalidBucketTreeNodeOffset); | ||
| 444 | |||
| 445 | R_TRY(this->FindEntrySet(std::addressof(entry_set_index), virtual_address, node_index)); | ||
| 446 | } else { | ||
| 447 | entry_set_index = static_cast<s32>(pos - start); | ||
| 448 | } | ||
| 449 | } | ||
| 450 | |||
| 451 | // Validate the entry set index. | ||
| 452 | R_UNLESS(0 <= entry_set_index && entry_set_index < m_tree->m_entry_set_count, | ||
| 453 | ResultInvalidBucketTreeNodeOffset); | ||
| 454 | |||
| 455 | // Find the entry. | ||
| 456 | R_TRY(this->FindEntry(virtual_address, entry_set_index)); | ||
| 457 | |||
| 458 | // Set count. | ||
| 459 | m_entry_set_count = m_tree->m_entry_set_count; | ||
| 460 | R_SUCCEED(); | ||
| 461 | } | ||
| 462 | |||
| 463 | Result BucketTree::Visitor::FindEntrySet(s32* out_index, s64 virtual_address, s32 node_index) { | ||
| 464 | const auto node_size = m_tree->m_node_size; | ||
| 465 | |||
| 466 | PooledBuffer pool(node_size, 1); | ||
| 467 | if (node_size <= pool.GetSize()) { | ||
| 468 | R_RETURN( | ||
| 469 | this->FindEntrySetWithBuffer(out_index, virtual_address, node_index, pool.GetBuffer())); | ||
| 470 | } else { | ||
| 471 | pool.Deallocate(); | ||
| 472 | R_RETURN(this->FindEntrySetWithoutBuffer(out_index, virtual_address, node_index)); | ||
| 473 | } | ||
| 474 | } | ||
| 475 | |||
| 476 | Result BucketTree::Visitor::FindEntrySetWithBuffer(s32* out_index, s64 virtual_address, | ||
| 477 | s32 node_index, char* buffer) { | ||
| 478 | // Calculate node extents. | ||
| 479 | const auto node_size = m_tree->m_node_size; | ||
| 480 | const auto node_offset = (node_index + 1) * static_cast<s64>(node_size); | ||
| 481 | VirtualFile storage = m_tree->m_node_storage; | ||
| 482 | |||
| 483 | // Read the node. | ||
| 484 | storage->Read(reinterpret_cast<u8*>(buffer), node_size, node_offset); | ||
| 485 | |||
| 486 | // Validate the header. | ||
| 487 | NodeHeader header; | ||
| 488 | std::memcpy(std::addressof(header), buffer, NodeHeaderSize); | ||
| 489 | R_TRY(header.Verify(node_index, node_size, sizeof(s64))); | ||
| 490 | |||
| 491 | // Create the node, and find. | ||
| 492 | StorageNode node(sizeof(s64), header.count); | ||
| 493 | node.Find(buffer, virtual_address); | ||
| 494 | R_UNLESS(node.GetIndex() >= 0, ResultInvalidBucketTreeVirtualOffset); | ||
| 495 | |||
| 496 | // Return the index. | ||
| 497 | *out_index = static_cast<s32>(m_tree->GetEntrySetIndex(header.index, node.GetIndex())); | ||
| 498 | R_SUCCEED(); | ||
| 499 | } | ||
| 500 | |||
| 501 | Result BucketTree::Visitor::FindEntrySetWithoutBuffer(s32* out_index, s64 virtual_address, | ||
| 502 | s32 node_index) { | ||
| 503 | // Calculate node extents. | ||
| 504 | const auto node_size = m_tree->m_node_size; | ||
| 505 | const auto node_offset = (node_index + 1) * static_cast<s64>(node_size); | ||
| 506 | VirtualFile storage = m_tree->m_node_storage; | ||
| 507 | |||
| 508 | // Read and validate the header. | ||
| 509 | NodeHeader header; | ||
| 510 | storage->ReadObject(std::addressof(header), node_offset); | ||
| 511 | R_TRY(header.Verify(node_index, node_size, sizeof(s64))); | ||
| 512 | |||
| 513 | // Create the node, and find. | ||
| 514 | StorageNode node(node_offset, sizeof(s64), header.count); | ||
| 515 | R_TRY(node.Find(storage, virtual_address)); | ||
| 516 | R_UNLESS(node.GetIndex() >= 0, ResultOutOfRange); | ||
| 517 | |||
| 518 | // Return the index. | ||
| 519 | *out_index = static_cast<s32>(m_tree->GetEntrySetIndex(header.index, node.GetIndex())); | ||
| 520 | R_SUCCEED(); | ||
| 521 | } | ||
| 522 | |||
| 523 | Result BucketTree::Visitor::FindEntry(s64 virtual_address, s32 entry_set_index) { | ||
| 524 | const auto entry_set_size = m_tree->m_node_size; | ||
| 525 | |||
| 526 | PooledBuffer pool(entry_set_size, 1); | ||
| 527 | if (entry_set_size <= pool.GetSize()) { | ||
| 528 | R_RETURN(this->FindEntryWithBuffer(virtual_address, entry_set_index, pool.GetBuffer())); | ||
| 529 | } else { | ||
| 530 | pool.Deallocate(); | ||
| 531 | R_RETURN(this->FindEntryWithoutBuffer(virtual_address, entry_set_index)); | ||
| 532 | } | ||
| 533 | } | ||
| 534 | |||
| 535 | Result BucketTree::Visitor::FindEntryWithBuffer(s64 virtual_address, s32 entry_set_index, | ||
| 536 | char* buffer) { | ||
| 537 | // Calculate entry set extents. | ||
| 538 | const auto entry_size = m_tree->m_entry_size; | ||
| 539 | const auto entry_set_size = m_tree->m_node_size; | ||
| 540 | const auto entry_set_offset = entry_set_index * static_cast<s64>(entry_set_size); | ||
| 541 | VirtualFile storage = m_tree->m_entry_storage; | ||
| 542 | |||
| 543 | // Read the entry set. | ||
| 544 | storage->Read(reinterpret_cast<u8*>(buffer), entry_set_size, entry_set_offset); | ||
| 545 | |||
| 546 | // Validate the entry_set. | ||
| 547 | EntrySetHeader entry_set; | ||
| 548 | std::memcpy(std::addressof(entry_set), buffer, sizeof(EntrySetHeader)); | ||
| 549 | R_TRY(entry_set.header.Verify(entry_set_index, entry_set_size, entry_size)); | ||
| 550 | |||
| 551 | // Create the node, and find. | ||
| 552 | StorageNode node(entry_size, entry_set.info.count); | ||
| 553 | node.Find(buffer, virtual_address); | ||
| 554 | R_UNLESS(node.GetIndex() >= 0, ResultOutOfRange); | ||
| 555 | |||
| 556 | // Copy the data into entry. | ||
| 557 | const auto entry_index = node.GetIndex(); | ||
| 558 | const auto entry_offset = impl::GetBucketTreeEntryOffset(0, entry_size, entry_index); | ||
| 559 | std::memcpy(m_entry, buffer + entry_offset, entry_size); | ||
| 560 | |||
| 561 | // Set our entry set/index. | ||
| 562 | m_entry_set = entry_set; | ||
| 563 | m_entry_index = entry_index; | ||
| 564 | |||
| 565 | R_SUCCEED(); | ||
| 566 | } | ||
| 567 | |||
| 568 | Result BucketTree::Visitor::FindEntryWithoutBuffer(s64 virtual_address, s32 entry_set_index) { | ||
| 569 | // Calculate entry set extents. | ||
| 570 | const auto entry_size = m_tree->m_entry_size; | ||
| 571 | const auto entry_set_size = m_tree->m_node_size; | ||
| 572 | const auto entry_set_offset = entry_set_index * static_cast<s64>(entry_set_size); | ||
| 573 | VirtualFile storage = m_tree->m_entry_storage; | ||
| 574 | |||
| 575 | // Read and validate the entry_set. | ||
| 576 | EntrySetHeader entry_set; | ||
| 577 | storage->ReadObject(std::addressof(entry_set), entry_set_offset); | ||
| 578 | R_TRY(entry_set.header.Verify(entry_set_index, entry_set_size, entry_size)); | ||
| 579 | |||
| 580 | // Create the node, and find. | ||
| 581 | StorageNode node(entry_set_offset, entry_size, entry_set.info.count); | ||
| 582 | R_TRY(node.Find(storage, virtual_address)); | ||
| 583 | R_UNLESS(node.GetIndex() >= 0, ResultOutOfRange); | ||
| 584 | |||
| 585 | // Copy the data into entry. | ||
| 586 | const auto entry_index = node.GetIndex(); | ||
| 587 | const auto entry_offset = | ||
| 588 | impl::GetBucketTreeEntryOffset(entry_set_offset, entry_size, entry_index); | ||
| 589 | storage->Read(reinterpret_cast<u8*>(m_entry), entry_size, entry_offset); | ||
| 590 | |||
| 591 | // Set our entry set/index. | ||
| 592 | m_entry_set = entry_set; | ||
| 593 | m_entry_index = entry_index; | ||
| 594 | |||
| 595 | R_SUCCEED(); | ||
| 596 | } | ||
| 597 | |||
| 598 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <mutex> | ||
| 7 | |||
| 8 | #include "common/alignment.h" | ||
| 9 | #include "common/common_funcs.h" | ||
| 10 | #include "common/common_types.h" | ||
| 11 | #include "common/literals.h" | ||
| 12 | |||
| 13 | #include "core/file_sys/vfs.h" | ||
| 14 | #include "core/hle/result.h" | ||
| 15 | |||
| 16 | namespace FileSys { | ||
| 17 | |||
| 18 | using namespace Common::Literals; | ||
| 19 | |||
| 20 | class BucketTree { | ||
| 21 | YUZU_NON_COPYABLE(BucketTree); | ||
| 22 | YUZU_NON_MOVEABLE(BucketTree); | ||
| 23 | |||
| 24 | public: | ||
| 25 | static constexpr u32 Magic = Common::MakeMagic('B', 'K', 'T', 'R'); | ||
| 26 | static constexpr u32 Version = 1; | ||
| 27 | |||
| 28 | static constexpr size_t NodeSizeMin = 1_KiB; | ||
| 29 | static constexpr size_t NodeSizeMax = 512_KiB; | ||
| 30 | |||
| 31 | public: | ||
| 32 | class Visitor; | ||
| 33 | |||
| 34 | struct Header { | ||
| 35 | u32 magic; | ||
| 36 | u32 version; | ||
| 37 | s32 entry_count; | ||
| 38 | s32 reserved; | ||
| 39 | |||
| 40 | void Format(s32 entry_count); | ||
| 41 | Result Verify() const; | ||
| 42 | }; | ||
| 43 | static_assert(std::is_trivial_v<Header>); | ||
| 44 | static_assert(sizeof(Header) == 0x10); | ||
| 45 | |||
| 46 | struct NodeHeader { | ||
| 47 | s32 index; | ||
| 48 | s32 count; | ||
| 49 | s64 offset; | ||
| 50 | |||
| 51 | Result Verify(s32 node_index, size_t node_size, size_t entry_size) const; | ||
| 52 | }; | ||
| 53 | static_assert(std::is_trivial_v<NodeHeader>); | ||
| 54 | static_assert(sizeof(NodeHeader) == 0x10); | ||
| 55 | |||
| 56 | struct Offsets { | ||
| 57 | s64 start_offset; | ||
| 58 | s64 end_offset; | ||
| 59 | |||
| 60 | constexpr bool IsInclude(s64 offset) const { | ||
| 61 | return this->start_offset <= offset && offset < this->end_offset; | ||
| 62 | } | ||
| 63 | |||
| 64 | constexpr bool IsInclude(s64 offset, s64 size) const { | ||
| 65 | return size > 0 && this->start_offset <= offset && size <= (this->end_offset - offset); | ||
| 66 | } | ||
| 67 | }; | ||
| 68 | static_assert(std::is_trivial_v<Offsets>); | ||
| 69 | static_assert(sizeof(Offsets) == 0x10); | ||
| 70 | |||
| 71 | struct OffsetCache { | ||
| 72 | Offsets offsets; | ||
| 73 | std::mutex mutex; | ||
| 74 | bool is_initialized; | ||
| 75 | |||
| 76 | OffsetCache() : offsets{-1, -1}, mutex(), is_initialized(false) {} | ||
| 77 | }; | ||
| 78 | |||
| 79 | class ContinuousReadingInfo { | ||
| 80 | public: | ||
| 81 | constexpr ContinuousReadingInfo() : m_read_size(), m_skip_count(), m_done() {} | ||
| 82 | |||
| 83 | constexpr void Reset() { | ||
| 84 | m_read_size = 0; | ||
| 85 | m_skip_count = 0; | ||
| 86 | m_done = false; | ||
| 87 | } | ||
| 88 | |||
| 89 | constexpr void SetSkipCount(s32 count) { | ||
| 90 | ASSERT(count >= 0); | ||
| 91 | m_skip_count = count; | ||
| 92 | } | ||
| 93 | constexpr s32 GetSkipCount() const { | ||
| 94 | return m_skip_count; | ||
| 95 | } | ||
| 96 | constexpr bool CheckNeedScan() { | ||
| 97 | return (--m_skip_count) <= 0; | ||
| 98 | } | ||
| 99 | |||
| 100 | constexpr void Done() { | ||
| 101 | m_read_size = 0; | ||
| 102 | m_done = true; | ||
| 103 | } | ||
| 104 | constexpr bool IsDone() const { | ||
| 105 | return m_done; | ||
| 106 | } | ||
| 107 | |||
| 108 | constexpr void SetReadSize(size_t size) { | ||
| 109 | m_read_size = size; | ||
| 110 | } | ||
| 111 | constexpr size_t GetReadSize() const { | ||
| 112 | return m_read_size; | ||
| 113 | } | ||
| 114 | constexpr bool CanDo() const { | ||
| 115 | return m_read_size > 0; | ||
| 116 | } | ||
| 117 | |||
| 118 | private: | ||
| 119 | size_t m_read_size; | ||
| 120 | s32 m_skip_count; | ||
| 121 | bool m_done; | ||
| 122 | }; | ||
| 123 | |||
| 124 | private: | ||
| 125 | class NodeBuffer { | ||
| 126 | YUZU_NON_COPYABLE(NodeBuffer); | ||
| 127 | |||
| 128 | public: | ||
| 129 | NodeBuffer() : m_header() {} | ||
| 130 | |||
| 131 | ~NodeBuffer() { | ||
| 132 | ASSERT(m_header == nullptr); | ||
| 133 | } | ||
| 134 | |||
| 135 | NodeBuffer(NodeBuffer&& rhs) : m_header(rhs.m_header) { | ||
| 136 | rhs.m_header = nullptr; | ||
| 137 | } | ||
| 138 | |||
| 139 | NodeBuffer& operator=(NodeBuffer&& rhs) { | ||
| 140 | if (this != std::addressof(rhs)) { | ||
| 141 | ASSERT(m_header == nullptr); | ||
| 142 | |||
| 143 | m_header = rhs.m_header; | ||
| 144 | |||
| 145 | rhs.m_header = nullptr; | ||
| 146 | } | ||
| 147 | return *this; | ||
| 148 | } | ||
| 149 | |||
| 150 | bool Allocate(size_t node_size) { | ||
| 151 | ASSERT(m_header == nullptr); | ||
| 152 | |||
| 153 | m_header = ::operator new(node_size, std::align_val_t{sizeof(s64)}); | ||
| 154 | |||
| 155 | // ASSERT(Common::IsAligned(m_header, sizeof(s64))); | ||
| 156 | |||
| 157 | return m_header != nullptr; | ||
| 158 | } | ||
| 159 | |||
| 160 | void Free(size_t node_size) { | ||
| 161 | if (m_header) { | ||
| 162 | ::operator delete(m_header, std::align_val_t{sizeof(s64)}); | ||
| 163 | m_header = nullptr; | ||
| 164 | } | ||
| 165 | } | ||
| 166 | |||
| 167 | void FillZero(size_t node_size) const { | ||
| 168 | if (m_header) { | ||
| 169 | std::memset(m_header, 0, node_size); | ||
| 170 | } | ||
| 171 | } | ||
| 172 | |||
| 173 | NodeHeader* Get() const { | ||
| 174 | return reinterpret_cast<NodeHeader*>(m_header); | ||
| 175 | } | ||
| 176 | |||
| 177 | NodeHeader* operator->() const { | ||
| 178 | return this->Get(); | ||
| 179 | } | ||
| 180 | |||
| 181 | template <typename T> | ||
| 182 | T* Get() const { | ||
| 183 | static_assert(std::is_trivial_v<T>); | ||
| 184 | static_assert(sizeof(T) == sizeof(NodeHeader)); | ||
| 185 | return reinterpret_cast<T*>(m_header); | ||
| 186 | } | ||
| 187 | |||
| 188 | private: | ||
| 189 | void* m_header; | ||
| 190 | }; | ||
| 191 | |||
| 192 | private: | ||
| 193 | static constexpr s32 GetEntryCount(size_t node_size, size_t entry_size) { | ||
| 194 | return static_cast<s32>((node_size - sizeof(NodeHeader)) / entry_size); | ||
| 195 | } | ||
| 196 | |||
| 197 | static constexpr s32 GetOffsetCount(size_t node_size) { | ||
| 198 | return static_cast<s32>((node_size - sizeof(NodeHeader)) / sizeof(s64)); | ||
| 199 | } | ||
| 200 | |||
| 201 | static constexpr s32 GetEntrySetCount(size_t node_size, size_t entry_size, s32 entry_count) { | ||
| 202 | const s32 entry_count_per_node = GetEntryCount(node_size, entry_size); | ||
| 203 | return Common::DivideUp(entry_count, entry_count_per_node); | ||
| 204 | } | ||
| 205 | |||
| 206 | static constexpr s32 GetNodeL2Count(size_t node_size, size_t entry_size, s32 entry_count) { | ||
| 207 | const s32 offset_count_per_node = GetOffsetCount(node_size); | ||
| 208 | const s32 entry_set_count = GetEntrySetCount(node_size, entry_size, entry_count); | ||
| 209 | |||
| 210 | if (entry_set_count <= offset_count_per_node) { | ||
| 211 | return 0; | ||
| 212 | } | ||
| 213 | |||
| 214 | const s32 node_l2_count = Common::DivideUp(entry_set_count, offset_count_per_node); | ||
| 215 | ASSERT(node_l2_count <= offset_count_per_node); | ||
| 216 | |||
| 217 | return Common::DivideUp(entry_set_count - (offset_count_per_node - (node_l2_count - 1)), | ||
| 218 | offset_count_per_node); | ||
| 219 | } | ||
| 220 | |||
| 221 | public: | ||
| 222 | BucketTree() | ||
| 223 | : m_node_storage(), m_entry_storage(), m_node_l1(), m_node_size(), m_entry_size(), | ||
| 224 | m_entry_count(), m_offset_count(), m_entry_set_count(), m_offset_cache() {} | ||
| 225 | ~BucketTree() { | ||
| 226 | this->Finalize(); | ||
| 227 | } | ||
| 228 | |||
| 229 | Result Initialize(VirtualFile node_storage, VirtualFile entry_storage, size_t node_size, | ||
| 230 | size_t entry_size, s32 entry_count); | ||
| 231 | void Initialize(size_t node_size, s64 end_offset); | ||
| 232 | void Finalize(); | ||
| 233 | |||
| 234 | bool IsInitialized() const { | ||
| 235 | return m_node_size > 0; | ||
| 236 | } | ||
| 237 | bool IsEmpty() const { | ||
| 238 | return m_entry_size == 0; | ||
| 239 | } | ||
| 240 | |||
| 241 | Result Find(Visitor* visitor, s64 virtual_address); | ||
| 242 | Result InvalidateCache(); | ||
| 243 | |||
| 244 | s32 GetEntryCount() const { | ||
| 245 | return m_entry_count; | ||
| 246 | } | ||
| 247 | |||
| 248 | Result GetOffsets(Offsets* out) { | ||
| 249 | // Ensure we have an offset cache. | ||
| 250 | R_TRY(this->EnsureOffsetCache()); | ||
| 251 | |||
| 252 | // Set the output. | ||
| 253 | *out = m_offset_cache.offsets; | ||
| 254 | R_SUCCEED(); | ||
| 255 | } | ||
| 256 | |||
| 257 | public: | ||
| 258 | static constexpr s64 QueryHeaderStorageSize() { | ||
| 259 | return sizeof(Header); | ||
| 260 | } | ||
| 261 | |||
| 262 | static constexpr s64 QueryNodeStorageSize(size_t node_size, size_t entry_size, | ||
| 263 | s32 entry_count) { | ||
| 264 | ASSERT(entry_size >= sizeof(s64)); | ||
| 265 | ASSERT(node_size >= entry_size + sizeof(NodeHeader)); | ||
| 266 | ASSERT(NodeSizeMin <= node_size && node_size <= NodeSizeMax); | ||
| 267 | ASSERT(Common::IsPowerOfTwo(node_size)); | ||
| 268 | ASSERT(entry_count >= 0); | ||
| 269 | |||
| 270 | if (entry_count <= 0) { | ||
| 271 | return 0; | ||
| 272 | } | ||
| 273 | return (1 + GetNodeL2Count(node_size, entry_size, entry_count)) * | ||
| 274 | static_cast<s64>(node_size); | ||
| 275 | } | ||
| 276 | |||
| 277 | static constexpr s64 QueryEntryStorageSize(size_t node_size, size_t entry_size, | ||
| 278 | s32 entry_count) { | ||
| 279 | ASSERT(entry_size >= sizeof(s64)); | ||
| 280 | ASSERT(node_size >= entry_size + sizeof(NodeHeader)); | ||
| 281 | ASSERT(NodeSizeMin <= node_size && node_size <= NodeSizeMax); | ||
| 282 | ASSERT(Common::IsPowerOfTwo(node_size)); | ||
| 283 | ASSERT(entry_count >= 0); | ||
| 284 | |||
| 285 | if (entry_count <= 0) { | ||
| 286 | return 0; | ||
| 287 | } | ||
| 288 | return GetEntrySetCount(node_size, entry_size, entry_count) * static_cast<s64>(node_size); | ||
| 289 | } | ||
| 290 | |||
| 291 | private: | ||
| 292 | template <typename EntryType> | ||
| 293 | struct ContinuousReadingParam { | ||
| 294 | s64 offset; | ||
| 295 | size_t size; | ||
| 296 | NodeHeader entry_set; | ||
| 297 | s32 entry_index; | ||
| 298 | Offsets offsets; | ||
| 299 | EntryType entry; | ||
| 300 | }; | ||
| 301 | |||
| 302 | private: | ||
| 303 | template <typename EntryType> | ||
| 304 | Result ScanContinuousReading(ContinuousReadingInfo* out_info, | ||
| 305 | const ContinuousReadingParam<EntryType>& param) const; | ||
| 306 | |||
| 307 | bool IsExistL2() const { | ||
| 308 | return m_offset_count < m_entry_set_count; | ||
| 309 | } | ||
| 310 | bool IsExistOffsetL2OnL1() const { | ||
| 311 | return this->IsExistL2() && m_node_l1->count < m_offset_count; | ||
| 312 | } | ||
| 313 | |||
| 314 | s64 GetEntrySetIndex(s32 node_index, s32 offset_index) const { | ||
| 315 | return (m_offset_count - m_node_l1->count) + (m_offset_count * node_index) + offset_index; | ||
| 316 | } | ||
| 317 | |||
| 318 | Result EnsureOffsetCache(); | ||
| 319 | |||
| 320 | private: | ||
| 321 | mutable VirtualFile m_node_storage; | ||
| 322 | mutable VirtualFile m_entry_storage; | ||
| 323 | NodeBuffer m_node_l1; | ||
| 324 | size_t m_node_size; | ||
| 325 | size_t m_entry_size; | ||
| 326 | s32 m_entry_count; | ||
| 327 | s32 m_offset_count; | ||
| 328 | s32 m_entry_set_count; | ||
| 329 | OffsetCache m_offset_cache; | ||
| 330 | }; | ||
| 331 | |||
| 332 | class BucketTree::Visitor { | ||
| 333 | YUZU_NON_COPYABLE(Visitor); | ||
| 334 | YUZU_NON_MOVEABLE(Visitor); | ||
| 335 | |||
| 336 | public: | ||
| 337 | constexpr Visitor() | ||
| 338 | : m_tree(), m_entry(), m_entry_index(-1), m_entry_set_count(), m_entry_set{} {} | ||
| 339 | ~Visitor() { | ||
| 340 | if (m_entry != nullptr) { | ||
| 341 | ::operator delete(m_entry, m_tree->m_entry_size); | ||
| 342 | m_tree = nullptr; | ||
| 343 | m_entry = nullptr; | ||
| 344 | } | ||
| 345 | } | ||
| 346 | |||
| 347 | bool IsValid() const { | ||
| 348 | return m_entry_index >= 0; | ||
| 349 | } | ||
| 350 | bool CanMoveNext() const { | ||
| 351 | return this->IsValid() && (m_entry_index + 1 < m_entry_set.info.count || | ||
| 352 | m_entry_set.info.index + 1 < m_entry_set_count); | ||
| 353 | } | ||
| 354 | bool CanMovePrevious() const { | ||
| 355 | return this->IsValid() && (m_entry_index > 0 || m_entry_set.info.index > 0); | ||
| 356 | } | ||
| 357 | |||
| 358 | Result MoveNext(); | ||
| 359 | Result MovePrevious(); | ||
| 360 | |||
| 361 | template <typename EntryType> | ||
| 362 | Result ScanContinuousReading(ContinuousReadingInfo* out_info, s64 offset, size_t size) const; | ||
| 363 | |||
| 364 | const void* Get() const { | ||
| 365 | ASSERT(this->IsValid()); | ||
| 366 | return m_entry; | ||
| 367 | } | ||
| 368 | |||
| 369 | template <typename T> | ||
| 370 | const T* Get() const { | ||
| 371 | ASSERT(this->IsValid()); | ||
| 372 | return reinterpret_cast<const T*>(m_entry); | ||
| 373 | } | ||
| 374 | |||
| 375 | const BucketTree* GetTree() const { | ||
| 376 | return m_tree; | ||
| 377 | } | ||
| 378 | |||
| 379 | private: | ||
| 380 | Result Initialize(const BucketTree* tree, const BucketTree::Offsets& offsets); | ||
| 381 | |||
| 382 | Result Find(s64 virtual_address); | ||
| 383 | |||
| 384 | Result FindEntrySet(s32* out_index, s64 virtual_address, s32 node_index); | ||
| 385 | Result FindEntrySetWithBuffer(s32* out_index, s64 virtual_address, s32 node_index, | ||
| 386 | char* buffer); | ||
| 387 | Result FindEntrySetWithoutBuffer(s32* out_index, s64 virtual_address, s32 node_index); | ||
| 388 | |||
| 389 | Result FindEntry(s64 virtual_address, s32 entry_set_index); | ||
| 390 | Result FindEntryWithBuffer(s64 virtual_address, s32 entry_set_index, char* buffer); | ||
| 391 | Result FindEntryWithoutBuffer(s64 virtual_address, s32 entry_set_index); | ||
| 392 | |||
| 393 | private: | ||
| 394 | friend class BucketTree; | ||
| 395 | |||
| 396 | union EntrySetHeader { | ||
| 397 | NodeHeader header; | ||
| 398 | struct Info { | ||
| 399 | s32 index; | ||
| 400 | s32 count; | ||
| 401 | s64 end; | ||
| 402 | s64 start; | ||
| 403 | } info; | ||
| 404 | static_assert(std::is_trivial_v<Info>); | ||
| 405 | }; | ||
| 406 | static_assert(std::is_trivial_v<EntrySetHeader>); | ||
| 407 | |||
| 408 | const BucketTree* m_tree; | ||
| 409 | BucketTree::Offsets m_offsets; | ||
| 410 | void* m_entry; | ||
| 411 | s32 m_entry_index; | ||
| 412 | s32 m_entry_set_count; | ||
| 413 | EntrySetHeader m_entry_set; | ||
| 414 | }; | ||
| 415 | |||
| 416 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "core/file_sys/errors.h" | ||
| 7 | #include "core/file_sys/fssystem/fssystem_bucket_tree.h" | ||
| 8 | #include "core/file_sys/fssystem/fssystem_bucket_tree_utils.h" | ||
| 9 | #include "core/file_sys/fssystem/fssystem_pooled_buffer.h" | ||
| 10 | |||
| 11 | namespace FileSys { | ||
| 12 | |||
| 13 | template <typename EntryType> | ||
| 14 | Result BucketTree::ScanContinuousReading(ContinuousReadingInfo* out_info, | ||
| 15 | const ContinuousReadingParam<EntryType>& param) const { | ||
| 16 | static_assert(std::is_trivial_v<ContinuousReadingParam<EntryType>>); | ||
| 17 | |||
| 18 | // Validate our preconditions. | ||
| 19 | ASSERT(this->IsInitialized()); | ||
| 20 | ASSERT(out_info != nullptr); | ||
| 21 | ASSERT(m_entry_size == sizeof(EntryType)); | ||
| 22 | |||
| 23 | // Reset the output. | ||
| 24 | out_info->Reset(); | ||
| 25 | |||
| 26 | // If there's nothing to read, we're done. | ||
| 27 | R_SUCCEED_IF(param.size == 0); | ||
| 28 | |||
| 29 | // If we're reading a fragment, we're done. | ||
| 30 | R_SUCCEED_IF(param.entry.IsFragment()); | ||
| 31 | |||
| 32 | // Validate the first entry. | ||
| 33 | auto entry = param.entry; | ||
| 34 | auto cur_offset = param.offset; | ||
| 35 | R_UNLESS(entry.GetVirtualOffset() <= cur_offset, ResultOutOfRange); | ||
| 36 | |||
| 37 | // Create a pooled buffer for our scan. | ||
| 38 | PooledBuffer pool(m_node_size, 1); | ||
| 39 | char* buffer = nullptr; | ||
| 40 | |||
| 41 | s64 entry_storage_size = m_entry_storage->GetSize(); | ||
| 42 | |||
| 43 | // Read the node. | ||
| 44 | if (m_node_size <= pool.GetSize()) { | ||
| 45 | buffer = pool.GetBuffer(); | ||
| 46 | const auto ofs = param.entry_set.index * static_cast<s64>(m_node_size); | ||
| 47 | R_UNLESS(m_node_size + ofs <= static_cast<size_t>(entry_storage_size), | ||
| 48 | ResultInvalidBucketTreeNodeEntryCount); | ||
| 49 | |||
| 50 | m_entry_storage->Read(reinterpret_cast<u8*>(buffer), m_node_size, ofs); | ||
| 51 | } | ||
| 52 | |||
| 53 | // Calculate extents. | ||
| 54 | const auto end_offset = cur_offset + static_cast<s64>(param.size); | ||
| 55 | s64 phys_offset = entry.GetPhysicalOffset(); | ||
| 56 | |||
| 57 | // Start merge tracking. | ||
| 58 | s64 merge_size = 0; | ||
| 59 | s64 readable_size = 0; | ||
| 60 | bool merged = false; | ||
| 61 | |||
| 62 | // Iterate. | ||
| 63 | auto entry_index = param.entry_index; | ||
| 64 | for (const auto entry_count = param.entry_set.count; entry_index < entry_count; ++entry_index) { | ||
| 65 | // If we're past the end, we're done. | ||
| 66 | if (end_offset <= cur_offset) { | ||
| 67 | break; | ||
| 68 | } | ||
| 69 | |||
| 70 | // Validate the entry offset. | ||
| 71 | const auto entry_offset = entry.GetVirtualOffset(); | ||
| 72 | R_UNLESS(entry_offset <= cur_offset, ResultInvalidIndirectEntryOffset); | ||
| 73 | |||
| 74 | // Get the next entry. | ||
| 75 | EntryType next_entry = {}; | ||
| 76 | s64 next_entry_offset; | ||
| 77 | |||
| 78 | if (entry_index + 1 < entry_count) { | ||
| 79 | if (buffer != nullptr) { | ||
| 80 | const auto ofs = impl::GetBucketTreeEntryOffset(0, m_entry_size, entry_index + 1); | ||
| 81 | std::memcpy(std::addressof(next_entry), buffer + ofs, m_entry_size); | ||
| 82 | } else { | ||
| 83 | const auto ofs = impl::GetBucketTreeEntryOffset(param.entry_set.index, m_node_size, | ||
| 84 | m_entry_size, entry_index + 1); | ||
| 85 | m_entry_storage->ReadObject(std::addressof(next_entry), ofs); | ||
| 86 | } | ||
| 87 | |||
| 88 | next_entry_offset = next_entry.GetVirtualOffset(); | ||
| 89 | R_UNLESS(param.offsets.IsInclude(next_entry_offset), ResultInvalidIndirectEntryOffset); | ||
| 90 | } else { | ||
| 91 | next_entry_offset = param.entry_set.offset; | ||
| 92 | } | ||
| 93 | |||
| 94 | // Validate the next entry offset. | ||
| 95 | R_UNLESS(cur_offset < next_entry_offset, ResultInvalidIndirectEntryOffset); | ||
| 96 | |||
| 97 | // Determine the much data there is. | ||
| 98 | const auto data_size = next_entry_offset - cur_offset; | ||
| 99 | ASSERT(data_size > 0); | ||
| 100 | |||
| 101 | // Determine how much data we should read. | ||
| 102 | const auto remaining_size = end_offset - cur_offset; | ||
| 103 | const size_t read_size = static_cast<size_t>(std::min(data_size, remaining_size)); | ||
| 104 | ASSERT(read_size <= param.size); | ||
| 105 | |||
| 106 | // Update our merge tracking. | ||
| 107 | if (entry.IsFragment()) { | ||
| 108 | // If we can't merge, stop looping. | ||
| 109 | if (EntryType::FragmentSizeMax <= read_size || remaining_size <= data_size) { | ||
| 110 | break; | ||
| 111 | } | ||
| 112 | |||
| 113 | // Otherwise, add the current size to the merge size. | ||
| 114 | merge_size += read_size; | ||
| 115 | } else { | ||
| 116 | // If we can't merge, stop looping. | ||
| 117 | if (phys_offset != entry.GetPhysicalOffset()) { | ||
| 118 | break; | ||
| 119 | } | ||
| 120 | |||
| 121 | // Add the size to the readable amount. | ||
| 122 | readable_size += merge_size + read_size; | ||
| 123 | ASSERT(readable_size <= static_cast<s64>(param.size)); | ||
| 124 | |||
| 125 | // Update whether we've merged. | ||
| 126 | merged |= merge_size > 0; | ||
| 127 | merge_size = 0; | ||
| 128 | } | ||
| 129 | |||
| 130 | // Advance. | ||
| 131 | cur_offset += read_size; | ||
| 132 | ASSERT(cur_offset <= end_offset); | ||
| 133 | |||
| 134 | phys_offset += next_entry_offset - entry_offset; | ||
| 135 | entry = next_entry; | ||
| 136 | } | ||
| 137 | |||
| 138 | // If we merged, set our readable size. | ||
| 139 | if (merged) { | ||
| 140 | out_info->SetReadSize(static_cast<size_t>(readable_size)); | ||
| 141 | } | ||
| 142 | out_info->SetSkipCount(entry_index - param.entry_index); | ||
| 143 | |||
| 144 | R_SUCCEED(); | ||
| 145 | } | ||
| 146 | |||
| 147 | template <typename EntryType> | ||
| 148 | Result BucketTree::Visitor::ScanContinuousReading(ContinuousReadingInfo* out_info, s64 offset, | ||
| 149 | size_t size) const { | ||
| 150 | static_assert(std::is_trivial_v<EntryType>); | ||
| 151 | ASSERT(this->IsValid()); | ||
| 152 | |||
| 153 | // Create our parameters. | ||
| 154 | ContinuousReadingParam<EntryType> param = { | ||
| 155 | .offset = offset, | ||
| 156 | .size = size, | ||
| 157 | .entry_set = m_entry_set.header, | ||
| 158 | .entry_index = m_entry_index, | ||
| 159 | .offsets{}, | ||
| 160 | .entry{}, | ||
| 161 | }; | ||
| 162 | std::memcpy(std::addressof(param.offsets), std::addressof(m_offsets), | ||
| 163 | sizeof(BucketTree::Offsets)); | ||
| 164 | std::memcpy(std::addressof(param.entry), m_entry, sizeof(EntryType)); | ||
| 165 | |||
| 166 | // Scan. | ||
| 167 | R_RETURN(m_tree->ScanContinuousReading<EntryType>(out_info, param)); | ||
| 168 | } | ||
| 169 | |||
| 170 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "core/file_sys/fssystem/fssystem_bucket_tree.h" | ||
| 7 | |||
| 8 | namespace FileSys::impl { | ||
| 9 | |||
| 10 | class SafeValue { | ||
| 11 | public: | ||
| 12 | static s64 GetInt64(const void* ptr) { | ||
| 13 | s64 value; | ||
| 14 | std::memcpy(std::addressof(value), ptr, sizeof(s64)); | ||
| 15 | return value; | ||
| 16 | } | ||
| 17 | |||
| 18 | static s64 GetInt64(const s64* ptr) { | ||
| 19 | return GetInt64(static_cast<const void*>(ptr)); | ||
| 20 | } | ||
| 21 | |||
| 22 | static s64 GetInt64(const s64& v) { | ||
| 23 | return GetInt64(std::addressof(v)); | ||
| 24 | } | ||
| 25 | |||
| 26 | static void SetInt64(void* dst, const void* src) { | ||
| 27 | std::memcpy(dst, src, sizeof(s64)); | ||
| 28 | } | ||
| 29 | |||
| 30 | static void SetInt64(void* dst, const s64* src) { | ||
| 31 | return SetInt64(dst, static_cast<const void*>(src)); | ||
| 32 | } | ||
| 33 | |||
| 34 | static void SetInt64(void* dst, const s64& v) { | ||
| 35 | return SetInt64(dst, std::addressof(v)); | ||
| 36 | } | ||
| 37 | }; | ||
| 38 | |||
| 39 | template <typename IteratorType> | ||
| 40 | struct BucketTreeNode { | ||
| 41 | using Header = BucketTree::NodeHeader; | ||
| 42 | |||
| 43 | Header header; | ||
| 44 | |||
| 45 | s32 GetCount() const { | ||
| 46 | return this->header.count; | ||
| 47 | } | ||
| 48 | |||
| 49 | void* GetArray() { | ||
| 50 | return std::addressof(this->header) + 1; | ||
| 51 | } | ||
| 52 | template <typename T> | ||
| 53 | T* GetArray() { | ||
| 54 | return reinterpret_cast<T*>(this->GetArray()); | ||
| 55 | } | ||
| 56 | const void* GetArray() const { | ||
| 57 | return std::addressof(this->header) + 1; | ||
| 58 | } | ||
| 59 | template <typename T> | ||
| 60 | const T* GetArray() const { | ||
| 61 | return reinterpret_cast<const T*>(this->GetArray()); | ||
| 62 | } | ||
| 63 | |||
| 64 | s64 GetBeginOffset() const { | ||
| 65 | return *this->GetArray<s64>(); | ||
| 66 | } | ||
| 67 | s64 GetEndOffset() const { | ||
| 68 | return this->header.offset; | ||
| 69 | } | ||
| 70 | |||
| 71 | IteratorType GetBegin() { | ||
| 72 | return IteratorType(this->GetArray<s64>()); | ||
| 73 | } | ||
| 74 | IteratorType GetEnd() { | ||
| 75 | return IteratorType(this->GetArray<s64>()) + this->header.count; | ||
| 76 | } | ||
| 77 | IteratorType GetBegin() const { | ||
| 78 | return IteratorType(this->GetArray<s64>()); | ||
| 79 | } | ||
| 80 | IteratorType GetEnd() const { | ||
| 81 | return IteratorType(this->GetArray<s64>()) + this->header.count; | ||
| 82 | } | ||
| 83 | |||
| 84 | IteratorType GetBegin(size_t entry_size) { | ||
| 85 | return IteratorType(this->GetArray(), entry_size); | ||
| 86 | } | ||
| 87 | IteratorType GetEnd(size_t entry_size) { | ||
| 88 | return IteratorType(this->GetArray(), entry_size) + this->header.count; | ||
| 89 | } | ||
| 90 | IteratorType GetBegin(size_t entry_size) const { | ||
| 91 | return IteratorType(this->GetArray(), entry_size); | ||
| 92 | } | ||
| 93 | IteratorType GetEnd(size_t entry_size) const { | ||
| 94 | return IteratorType(this->GetArray(), entry_size) + this->header.count; | ||
| 95 | } | ||
| 96 | }; | ||
| 97 | |||
| 98 | constexpr inline s64 GetBucketTreeEntryOffset(s64 entry_set_offset, size_t entry_size, | ||
| 99 | s32 entry_index) { | ||
| 100 | return entry_set_offset + sizeof(BucketTree::NodeHeader) + | ||
| 101 | entry_index * static_cast<s64>(entry_size); | ||
| 102 | } | ||
| 103 | |||
| 104 | constexpr inline s64 GetBucketTreeEntryOffset(s32 entry_set_index, size_t node_size, | ||
| 105 | size_t entry_size, s32 entry_index) { | ||
| 106 | return GetBucketTreeEntryOffset(entry_set_index * static_cast<s64>(node_size), entry_size, | ||
| 107 | entry_index); | ||
| 108 | } | ||
| 109 | |||
| 110 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "common/literals.h" | ||
| 7 | |||
| 8 | #include "core/file_sys/errors.h" | ||
| 9 | #include "core/file_sys/fssystem/fs_i_storage.h" | ||
| 10 | #include "core/file_sys/fssystem/fssystem_bucket_tree.h" | ||
| 11 | #include "core/file_sys/fssystem/fssystem_compression_common.h" | ||
| 12 | #include "core/file_sys/fssystem/fssystem_pooled_buffer.h" | ||
| 13 | #include "core/file_sys/vfs.h" | ||
| 14 | |||
| 15 | namespace FileSys { | ||
| 16 | |||
| 17 | using namespace Common::Literals; | ||
| 18 | |||
| 19 | class CompressedStorage : public IReadOnlyStorage { | ||
| 20 | YUZU_NON_COPYABLE(CompressedStorage); | ||
| 21 | YUZU_NON_MOVEABLE(CompressedStorage); | ||
| 22 | |||
| 23 | public: | ||
| 24 | static constexpr size_t NodeSize = 16_KiB; | ||
| 25 | |||
| 26 | struct Entry { | ||
| 27 | s64 virt_offset; | ||
| 28 | s64 phys_offset; | ||
| 29 | CompressionType compression_type; | ||
| 30 | s32 phys_size; | ||
| 31 | |||
| 32 | s64 GetPhysicalSize() const { | ||
| 33 | return this->phys_size; | ||
| 34 | } | ||
| 35 | }; | ||
| 36 | static_assert(std::is_trivial_v<Entry>); | ||
| 37 | static_assert(sizeof(Entry) == 0x18); | ||
| 38 | |||
| 39 | public: | ||
| 40 | static constexpr s64 QueryNodeStorageSize(s32 entry_count) { | ||
| 41 | return BucketTree::QueryNodeStorageSize(NodeSize, sizeof(Entry), entry_count); | ||
| 42 | } | ||
| 43 | |||
| 44 | static constexpr s64 QueryEntryStorageSize(s32 entry_count) { | ||
| 45 | return BucketTree::QueryEntryStorageSize(NodeSize, sizeof(Entry), entry_count); | ||
| 46 | } | ||
| 47 | |||
| 48 | private: | ||
| 49 | class CompressedStorageCore { | ||
| 50 | YUZU_NON_COPYABLE(CompressedStorageCore); | ||
| 51 | YUZU_NON_MOVEABLE(CompressedStorageCore); | ||
| 52 | |||
| 53 | public: | ||
| 54 | CompressedStorageCore() : m_table(), m_data_storage() {} | ||
| 55 | |||
| 56 | ~CompressedStorageCore() { | ||
| 57 | this->Finalize(); | ||
| 58 | } | ||
| 59 | |||
| 60 | public: | ||
| 61 | Result Initialize(VirtualFile data_storage, VirtualFile node_storage, | ||
| 62 | VirtualFile entry_storage, s32 bktr_entry_count, size_t block_size_max, | ||
| 63 | size_t continuous_reading_size_max, | ||
| 64 | GetDecompressorFunction get_decompressor) { | ||
| 65 | // Check pre-conditions. | ||
| 66 | ASSERT(0 < block_size_max); | ||
| 67 | ASSERT(block_size_max <= continuous_reading_size_max); | ||
| 68 | ASSERT(get_decompressor != nullptr); | ||
| 69 | |||
| 70 | // Initialize our entry table. | ||
| 71 | R_TRY(m_table.Initialize(node_storage, entry_storage, NodeSize, sizeof(Entry), | ||
| 72 | bktr_entry_count)); | ||
| 73 | |||
| 74 | // Set our other fields. | ||
| 75 | m_block_size_max = block_size_max; | ||
| 76 | m_continuous_reading_size_max = continuous_reading_size_max; | ||
| 77 | m_data_storage = data_storage; | ||
| 78 | m_get_decompressor_function = get_decompressor; | ||
| 79 | |||
| 80 | R_SUCCEED(); | ||
| 81 | } | ||
| 82 | |||
| 83 | void Finalize() { | ||
| 84 | if (this->IsInitialized()) { | ||
| 85 | m_table.Finalize(); | ||
| 86 | m_data_storage = VirtualFile(); | ||
| 87 | } | ||
| 88 | } | ||
| 89 | |||
| 90 | VirtualFile GetDataStorage() { | ||
| 91 | return m_data_storage; | ||
| 92 | } | ||
| 93 | |||
| 94 | Result GetDataStorageSize(s64* out) { | ||
| 95 | // Check pre-conditions. | ||
| 96 | ASSERT(out != nullptr); | ||
| 97 | |||
| 98 | // Get size. | ||
| 99 | *out = m_data_storage->GetSize(); | ||
| 100 | |||
| 101 | R_SUCCEED(); | ||
| 102 | } | ||
| 103 | |||
| 104 | BucketTree& GetEntryTable() { | ||
| 105 | return m_table; | ||
| 106 | } | ||
| 107 | |||
| 108 | Result GetEntryList(Entry* out_entries, s32* out_read_count, s32 max_entry_count, | ||
| 109 | s64 offset, s64 size) { | ||
| 110 | // Check pre-conditions. | ||
| 111 | ASSERT(offset >= 0); | ||
| 112 | ASSERT(size >= 0); | ||
| 113 | ASSERT(this->IsInitialized()); | ||
| 114 | |||
| 115 | // Check that we can output the count. | ||
| 116 | R_UNLESS(out_read_count != nullptr, ResultNullptrArgument); | ||
| 117 | |||
| 118 | // Check that we have anything to read at all. | ||
| 119 | R_SUCCEED_IF(size == 0); | ||
| 120 | |||
| 121 | // Check that either we have a buffer, or this is to determine how many we need. | ||
| 122 | if (max_entry_count != 0) { | ||
| 123 | R_UNLESS(out_entries != nullptr, ResultNullptrArgument); | ||
| 124 | } | ||
| 125 | |||
| 126 | // Get the table offsets. | ||
| 127 | BucketTree::Offsets table_offsets; | ||
| 128 | R_TRY(m_table.GetOffsets(std::addressof(table_offsets))); | ||
| 129 | |||
| 130 | // Validate arguments. | ||
| 131 | R_UNLESS(table_offsets.IsInclude(offset, size), ResultOutOfRange); | ||
| 132 | |||
| 133 | // Find the offset in our tree. | ||
| 134 | BucketTree::Visitor visitor; | ||
| 135 | R_TRY(m_table.Find(std::addressof(visitor), offset)); | ||
| 136 | { | ||
| 137 | const auto entry_offset = visitor.Get<Entry>()->virt_offset; | ||
| 138 | R_UNLESS(0 <= entry_offset && table_offsets.IsInclude(entry_offset), | ||
| 139 | ResultUnexpectedInCompressedStorageA); | ||
| 140 | } | ||
| 141 | |||
| 142 | // Get the entries. | ||
| 143 | const auto end_offset = offset + size; | ||
| 144 | s32 read_count = 0; | ||
| 145 | while (visitor.Get<Entry>()->virt_offset < end_offset) { | ||
| 146 | // If we should be setting the output, do so. | ||
| 147 | if (max_entry_count != 0) { | ||
| 148 | // Ensure we only read as many entries as we can. | ||
| 149 | if (read_count >= max_entry_count) { | ||
| 150 | break; | ||
| 151 | } | ||
| 152 | |||
| 153 | // Set the current output entry. | ||
| 154 | out_entries[read_count] = *visitor.Get<Entry>(); | ||
| 155 | } | ||
| 156 | |||
| 157 | // Increase the read count. | ||
| 158 | ++read_count; | ||
| 159 | |||
| 160 | // If we're at the end, we're done. | ||
| 161 | if (!visitor.CanMoveNext()) { | ||
| 162 | break; | ||
| 163 | } | ||
| 164 | |||
| 165 | // Move to the next entry. | ||
| 166 | R_TRY(visitor.MoveNext()); | ||
| 167 | } | ||
| 168 | |||
| 169 | // Set the output read count. | ||
| 170 | *out_read_count = read_count; | ||
| 171 | R_SUCCEED(); | ||
| 172 | } | ||
| 173 | |||
| 174 | Result GetSize(s64* out) { | ||
| 175 | // Check pre-conditions. | ||
| 176 | ASSERT(out != nullptr); | ||
| 177 | |||
| 178 | // Get our table offsets. | ||
| 179 | BucketTree::Offsets offsets; | ||
| 180 | R_TRY(m_table.GetOffsets(std::addressof(offsets))); | ||
| 181 | |||
| 182 | // Set the output. | ||
| 183 | *out = offsets.end_offset; | ||
| 184 | R_SUCCEED(); | ||
| 185 | } | ||
| 186 | |||
| 187 | Result OperatePerEntry(s64 offset, s64 size, auto f) { | ||
| 188 | // Check pre-conditions. | ||
| 189 | ASSERT(offset >= 0); | ||
| 190 | ASSERT(size >= 0); | ||
| 191 | ASSERT(this->IsInitialized()); | ||
| 192 | |||
| 193 | // Succeed if there's nothing to operate on. | ||
| 194 | R_SUCCEED_IF(size == 0); | ||
| 195 | |||
| 196 | // Get the table offsets. | ||
| 197 | BucketTree::Offsets table_offsets; | ||
| 198 | R_TRY(m_table.GetOffsets(std::addressof(table_offsets))); | ||
| 199 | |||
| 200 | // Validate arguments. | ||
| 201 | R_UNLESS(table_offsets.IsInclude(offset, size), ResultOutOfRange); | ||
| 202 | |||
| 203 | // Find the offset in our tree. | ||
| 204 | BucketTree::Visitor visitor; | ||
| 205 | R_TRY(m_table.Find(std::addressof(visitor), offset)); | ||
| 206 | { | ||
| 207 | const auto entry_offset = visitor.Get<Entry>()->virt_offset; | ||
| 208 | R_UNLESS(0 <= entry_offset && table_offsets.IsInclude(entry_offset), | ||
| 209 | ResultUnexpectedInCompressedStorageA); | ||
| 210 | } | ||
| 211 | |||
| 212 | // Prepare to operate in chunks. | ||
| 213 | auto cur_offset = offset; | ||
| 214 | const auto end_offset = offset + static_cast<s64>(size); | ||
| 215 | |||
| 216 | while (cur_offset < end_offset) { | ||
| 217 | // Get the current entry. | ||
| 218 | const auto cur_entry = *visitor.Get<Entry>(); | ||
| 219 | |||
| 220 | // Get and validate the entry's offset. | ||
| 221 | const auto cur_entry_offset = cur_entry.virt_offset; | ||
| 222 | R_UNLESS(cur_entry_offset <= cur_offset, ResultUnexpectedInCompressedStorageA); | ||
| 223 | |||
| 224 | // Get and validate the next entry offset. | ||
| 225 | s64 next_entry_offset; | ||
| 226 | if (visitor.CanMoveNext()) { | ||
| 227 | R_TRY(visitor.MoveNext()); | ||
| 228 | next_entry_offset = visitor.Get<Entry>()->virt_offset; | ||
| 229 | R_UNLESS(table_offsets.IsInclude(next_entry_offset), | ||
| 230 | ResultUnexpectedInCompressedStorageA); | ||
| 231 | } else { | ||
| 232 | next_entry_offset = table_offsets.end_offset; | ||
| 233 | } | ||
| 234 | R_UNLESS(cur_offset < next_entry_offset, ResultUnexpectedInCompressedStorageA); | ||
| 235 | |||
| 236 | // Get the offset of the entry in the data we read. | ||
| 237 | const auto data_offset = cur_offset - cur_entry_offset; | ||
| 238 | const auto data_size = (next_entry_offset - cur_entry_offset); | ||
| 239 | ASSERT(data_size > 0); | ||
| 240 | |||
| 241 | // Determine how much is left. | ||
| 242 | const auto remaining_size = end_offset - cur_offset; | ||
| 243 | const auto cur_size = std::min<s64>(remaining_size, data_size - data_offset); | ||
| 244 | ASSERT(cur_size <= size); | ||
| 245 | |||
| 246 | // Get the data storage size. | ||
| 247 | s64 storage_size = m_data_storage->GetSize(); | ||
| 248 | |||
| 249 | // Check that our read remains naively physically in bounds. | ||
| 250 | R_UNLESS(0 <= cur_entry.phys_offset && cur_entry.phys_offset <= storage_size, | ||
| 251 | ResultUnexpectedInCompressedStorageC); | ||
| 252 | |||
| 253 | // If we have any compression, verify that we remain physically in bounds. | ||
| 254 | if (cur_entry.compression_type != CompressionType::None) { | ||
| 255 | R_UNLESS(cur_entry.phys_offset + cur_entry.GetPhysicalSize() <= storage_size, | ||
| 256 | ResultUnexpectedInCompressedStorageC); | ||
| 257 | } | ||
| 258 | |||
| 259 | // Check that block alignment requirements are met. | ||
| 260 | if (CompressionTypeUtility::IsBlockAlignmentRequired(cur_entry.compression_type)) { | ||
| 261 | R_UNLESS(Common::IsAligned(cur_entry.phys_offset, CompressionBlockAlignment), | ||
| 262 | ResultUnexpectedInCompressedStorageA); | ||
| 263 | } | ||
| 264 | |||
| 265 | // Invoke the operator. | ||
| 266 | bool is_continuous = true; | ||
| 267 | R_TRY( | ||
| 268 | f(std::addressof(is_continuous), cur_entry, data_size, data_offset, cur_size)); | ||
| 269 | |||
| 270 | // If not continuous, we're done. | ||
| 271 | if (!is_continuous) { | ||
| 272 | break; | ||
| 273 | } | ||
| 274 | |||
| 275 | // Advance. | ||
| 276 | cur_offset += cur_size; | ||
| 277 | } | ||
| 278 | |||
| 279 | R_SUCCEED(); | ||
| 280 | } | ||
| 281 | |||
| 282 | public: | ||
| 283 | using ReadImplFunction = std::function<Result(void*, size_t)>; | ||
| 284 | using ReadFunction = std::function<Result(size_t, const ReadImplFunction&)>; | ||
| 285 | |||
| 286 | public: | ||
| 287 | Result Read(s64 offset, s64 size, const ReadFunction& read_func) { | ||
| 288 | // Check pre-conditions. | ||
| 289 | ASSERT(offset >= 0); | ||
| 290 | ASSERT(this->IsInitialized()); | ||
| 291 | |||
| 292 | // Succeed immediately, if we have nothing to read. | ||
| 293 | R_SUCCEED_IF(size == 0); | ||
| 294 | |||
| 295 | // Declare read lambda. | ||
| 296 | constexpr int EntriesCountMax = 0x80; | ||
| 297 | struct Entries { | ||
| 298 | CompressionType compression_type; | ||
| 299 | u32 gap_from_prev; | ||
| 300 | u32 physical_size; | ||
| 301 | u32 virtual_size; | ||
| 302 | }; | ||
| 303 | std::array<Entries, EntriesCountMax> entries; | ||
| 304 | s32 entry_count = 0; | ||
| 305 | Entry prev_entry = { | ||
| 306 | .virt_offset = -1, | ||
| 307 | .phys_offset{}, | ||
| 308 | .compression_type{}, | ||
| 309 | .phys_size{}, | ||
| 310 | }; | ||
| 311 | bool will_allocate_pooled_buffer = false; | ||
| 312 | s64 required_access_physical_offset = 0; | ||
| 313 | s64 required_access_physical_size = 0; | ||
| 314 | |||
| 315 | auto PerformRequiredRead = [&]() -> Result { | ||
| 316 | // If there are no entries, we have nothing to do. | ||
| 317 | R_SUCCEED_IF(entry_count == 0); | ||
| 318 | |||
| 319 | // Get the remaining size in a convenient form. | ||
| 320 | const size_t total_required_size = | ||
| 321 | static_cast<size_t>(required_access_physical_size); | ||
| 322 | |||
| 323 | // Perform the read based on whether we need to allocate a buffer. | ||
| 324 | if (will_allocate_pooled_buffer) { | ||
| 325 | // Allocate a pooled buffer. | ||
| 326 | PooledBuffer pooled_buffer; | ||
| 327 | if (pooled_buffer.GetAllocatableSizeMax() >= total_required_size) { | ||
| 328 | pooled_buffer.Allocate(total_required_size, m_block_size_max); | ||
| 329 | } else { | ||
| 330 | pooled_buffer.AllocateParticularlyLarge( | ||
| 331 | std::min<size_t>( | ||
| 332 | total_required_size, | ||
| 333 | PooledBuffer::GetAllocatableParticularlyLargeSizeMax()), | ||
| 334 | m_block_size_max); | ||
| 335 | } | ||
| 336 | |||
| 337 | // Read each of the entries. | ||
| 338 | for (s32 entry_idx = 0; entry_idx < entry_count; ++entry_idx) { | ||
| 339 | // Determine the current read size. | ||
| 340 | bool will_use_pooled_buffer = false; | ||
| 341 | const size_t cur_read_size = [&]() -> size_t { | ||
| 342 | if (const size_t target_entry_size = | ||
| 343 | static_cast<size_t>(entries[entry_idx].physical_size) + | ||
| 344 | static_cast<size_t>(entries[entry_idx].gap_from_prev); | ||
| 345 | target_entry_size <= pooled_buffer.GetSize()) { | ||
| 346 | // We'll be using the pooled buffer. | ||
| 347 | will_use_pooled_buffer = true; | ||
| 348 | |||
| 349 | // Determine how much we can read. | ||
| 350 | const size_t max_size = std::min<size_t>( | ||
| 351 | required_access_physical_size, pooled_buffer.GetSize()); | ||
| 352 | |||
| 353 | size_t read_size = 0; | ||
| 354 | for (auto n = entry_idx; n < entry_count; ++n) { | ||
| 355 | const size_t cur_entry_size = | ||
| 356 | static_cast<size_t>(entries[n].physical_size) + | ||
| 357 | static_cast<size_t>(entries[n].gap_from_prev); | ||
| 358 | if (read_size + cur_entry_size > max_size) { | ||
| 359 | break; | ||
| 360 | } | ||
| 361 | |||
| 362 | read_size += cur_entry_size; | ||
| 363 | } | ||
| 364 | |||
| 365 | return read_size; | ||
| 366 | } else { | ||
| 367 | // If we don't fit, we must be uncompressed. | ||
| 368 | ASSERT(entries[entry_idx].compression_type == | ||
| 369 | CompressionType::None); | ||
| 370 | |||
| 371 | // We can perform the whole of an uncompressed read directly. | ||
| 372 | return entries[entry_idx].virtual_size; | ||
| 373 | } | ||
| 374 | }(); | ||
| 375 | |||
| 376 | // Perform the read based on whether or not we'll use the pooled buffer. | ||
| 377 | if (will_use_pooled_buffer) { | ||
| 378 | // Read the compressed data into the pooled buffer. | ||
| 379 | auto* const buffer = pooled_buffer.GetBuffer(); | ||
| 380 | m_data_storage->Read(reinterpret_cast<u8*>(buffer), cur_read_size, | ||
| 381 | required_access_physical_offset); | ||
| 382 | |||
| 383 | // Decompress the data. | ||
| 384 | size_t buffer_offset; | ||
| 385 | for (buffer_offset = 0; | ||
| 386 | entry_idx < entry_count && | ||
| 387 | ((static_cast<size_t>(entries[entry_idx].physical_size) + | ||
| 388 | static_cast<size_t>(entries[entry_idx].gap_from_prev)) == 0 || | ||
| 389 | buffer_offset < cur_read_size); | ||
| 390 | buffer_offset += entries[entry_idx++].physical_size) { | ||
| 391 | // Advance by the relevant gap. | ||
| 392 | buffer_offset += entries[entry_idx].gap_from_prev; | ||
| 393 | |||
| 394 | const auto compression_type = entries[entry_idx].compression_type; | ||
| 395 | switch (compression_type) { | ||
| 396 | case CompressionType::None: { | ||
| 397 | // Check that we can remain within bounds. | ||
| 398 | ASSERT(buffer_offset + entries[entry_idx].virtual_size <= | ||
| 399 | cur_read_size); | ||
| 400 | |||
| 401 | // Perform no decompression. | ||
| 402 | R_TRY(read_func( | ||
| 403 | entries[entry_idx].virtual_size, | ||
| 404 | [&](void* dst, size_t dst_size) -> Result { | ||
| 405 | // Check that the size is valid. | ||
| 406 | ASSERT(dst_size == entries[entry_idx].virtual_size); | ||
| 407 | |||
| 408 | // We have no compression, so just copy the data | ||
| 409 | // out. | ||
| 410 | std::memcpy(dst, buffer + buffer_offset, | ||
| 411 | entries[entry_idx].virtual_size); | ||
| 412 | R_SUCCEED(); | ||
| 413 | })); | ||
| 414 | |||
| 415 | break; | ||
| 416 | } | ||
| 417 | case CompressionType::Zeros: { | ||
| 418 | // Check that we can remain within bounds. | ||
| 419 | ASSERT(buffer_offset <= cur_read_size); | ||
| 420 | |||
| 421 | // Zero the memory. | ||
| 422 | R_TRY(read_func( | ||
| 423 | entries[entry_idx].virtual_size, | ||
| 424 | [&](void* dst, size_t dst_size) -> Result { | ||
| 425 | // Check that the size is valid. | ||
| 426 | ASSERT(dst_size == entries[entry_idx].virtual_size); | ||
| 427 | |||
| 428 | // The data is zeroes, so zero the buffer. | ||
| 429 | std::memset(dst, 0, entries[entry_idx].virtual_size); | ||
| 430 | R_SUCCEED(); | ||
| 431 | })); | ||
| 432 | |||
| 433 | break; | ||
| 434 | } | ||
| 435 | default: { | ||
| 436 | // Check that we can remain within bounds. | ||
| 437 | ASSERT(buffer_offset + entries[entry_idx].physical_size <= | ||
| 438 | cur_read_size); | ||
| 439 | |||
| 440 | // Get the decompressor. | ||
| 441 | const auto decompressor = | ||
| 442 | this->GetDecompressor(compression_type); | ||
| 443 | R_UNLESS(decompressor != nullptr, | ||
| 444 | ResultUnexpectedInCompressedStorageB); | ||
| 445 | |||
| 446 | // Decompress the data. | ||
| 447 | R_TRY(read_func(entries[entry_idx].virtual_size, | ||
| 448 | [&](void* dst, size_t dst_size) -> Result { | ||
| 449 | // Check that the size is valid. | ||
| 450 | ASSERT(dst_size == | ||
| 451 | entries[entry_idx].virtual_size); | ||
| 452 | |||
| 453 | // Perform the decompression. | ||
| 454 | R_RETURN(decompressor( | ||
| 455 | dst, entries[entry_idx].virtual_size, | ||
| 456 | buffer + buffer_offset, | ||
| 457 | entries[entry_idx].physical_size)); | ||
| 458 | })); | ||
| 459 | |||
| 460 | break; | ||
| 461 | } | ||
| 462 | } | ||
| 463 | } | ||
| 464 | |||
| 465 | // Check that we processed the correct amount of data. | ||
| 466 | ASSERT(buffer_offset == cur_read_size); | ||
| 467 | } else { | ||
| 468 | // Account for the gap from the previous entry. | ||
| 469 | required_access_physical_offset += entries[entry_idx].gap_from_prev; | ||
| 470 | required_access_physical_size -= entries[entry_idx].gap_from_prev; | ||
| 471 | |||
| 472 | // We don't need the buffer (as the data is uncompressed), so just | ||
| 473 | // execute the read. | ||
| 474 | R_TRY( | ||
| 475 | read_func(cur_read_size, [&](void* dst, size_t dst_size) -> Result { | ||
| 476 | // Check that the size is valid. | ||
| 477 | ASSERT(dst_size == cur_read_size); | ||
| 478 | |||
| 479 | // Perform the read. | ||
| 480 | m_data_storage->Read(reinterpret_cast<u8*>(dst), cur_read_size, | ||
| 481 | required_access_physical_offset); | ||
| 482 | |||
| 483 | R_SUCCEED(); | ||
| 484 | })); | ||
| 485 | } | ||
| 486 | |||
| 487 | // Advance on. | ||
| 488 | required_access_physical_offset += cur_read_size; | ||
| 489 | required_access_physical_size -= cur_read_size; | ||
| 490 | } | ||
| 491 | |||
| 492 | // Verify that we have nothing remaining to read. | ||
| 493 | ASSERT(required_access_physical_size == 0); | ||
| 494 | |||
| 495 | R_SUCCEED(); | ||
| 496 | } else { | ||
| 497 | // We don't need a buffer, so just execute the read. | ||
| 498 | R_TRY(read_func(total_required_size, [&](void* dst, size_t dst_size) -> Result { | ||
| 499 | // Check that the size is valid. | ||
| 500 | ASSERT(dst_size == total_required_size); | ||
| 501 | |||
| 502 | // Perform the read. | ||
| 503 | m_data_storage->Read(reinterpret_cast<u8*>(dst), total_required_size, | ||
| 504 | required_access_physical_offset); | ||
| 505 | |||
| 506 | R_SUCCEED(); | ||
| 507 | })); | ||
| 508 | } | ||
| 509 | |||
| 510 | R_SUCCEED(); | ||
| 511 | }; | ||
| 512 | |||
| 513 | R_TRY(this->OperatePerEntry( | ||
| 514 | offset, size, | ||
| 515 | [&](bool* out_continuous, const Entry& entry, s64 virtual_data_size, | ||
| 516 | s64 data_offset, s64 read_size) -> Result { | ||
| 517 | // Determine the physical extents. | ||
| 518 | s64 physical_offset, physical_size; | ||
| 519 | if (CompressionTypeUtility::IsRandomAccessible(entry.compression_type)) { | ||
| 520 | physical_offset = entry.phys_offset + data_offset; | ||
| 521 | physical_size = read_size; | ||
| 522 | } else { | ||
| 523 | physical_offset = entry.phys_offset; | ||
| 524 | physical_size = entry.GetPhysicalSize(); | ||
| 525 | } | ||
| 526 | |||
| 527 | // If we have a pending data storage operation, perform it if we have to. | ||
| 528 | const s64 required_access_physical_end = | ||
| 529 | required_access_physical_offset + required_access_physical_size; | ||
| 530 | if (required_access_physical_size > 0) { | ||
| 531 | const bool required_by_gap = | ||
| 532 | !(required_access_physical_end <= physical_offset && | ||
| 533 | physical_offset <= Common::AlignUp(required_access_physical_end, | ||
| 534 | CompressionBlockAlignment)); | ||
| 535 | const bool required_by_continuous_size = | ||
| 536 | ((physical_size + physical_offset) - required_access_physical_end) + | ||
| 537 | required_access_physical_size > | ||
| 538 | static_cast<s64>(m_continuous_reading_size_max); | ||
| 539 | const bool required_by_entry_count = entry_count == EntriesCountMax; | ||
| 540 | if (required_by_gap || required_by_continuous_size || | ||
| 541 | required_by_entry_count) { | ||
| 542 | // Check that our planned access is sane. | ||
| 543 | ASSERT(!will_allocate_pooled_buffer || | ||
| 544 | required_access_physical_size <= | ||
| 545 | static_cast<s64>(m_continuous_reading_size_max)); | ||
| 546 | |||
| 547 | // Perform the required read. | ||
| 548 | const Result rc = PerformRequiredRead(); | ||
| 549 | if (R_FAILED(rc)) { | ||
| 550 | R_THROW(rc); | ||
| 551 | } | ||
| 552 | |||
| 553 | // Reset our requirements. | ||
| 554 | prev_entry.virt_offset = -1; | ||
| 555 | required_access_physical_size = 0; | ||
| 556 | entry_count = 0; | ||
| 557 | will_allocate_pooled_buffer = false; | ||
| 558 | } | ||
| 559 | } | ||
| 560 | |||
| 561 | // Sanity check that we're within bounds on entries. | ||
| 562 | ASSERT(entry_count < EntriesCountMax); | ||
| 563 | |||
| 564 | // Determine if a buffer allocation is needed. | ||
| 565 | if (entry.compression_type != CompressionType::None || | ||
| 566 | (prev_entry.virt_offset >= 0 && | ||
| 567 | entry.virt_offset - prev_entry.virt_offset != | ||
| 568 | entry.phys_offset - prev_entry.phys_offset)) { | ||
| 569 | will_allocate_pooled_buffer = true; | ||
| 570 | } | ||
| 571 | |||
| 572 | // If we need to access the data storage, update our required access parameters. | ||
| 573 | if (CompressionTypeUtility::IsDataStorageAccessRequired( | ||
| 574 | entry.compression_type)) { | ||
| 575 | // If the data is compressed, ensure the access is sane. | ||
| 576 | if (entry.compression_type != CompressionType::None) { | ||
| 577 | R_UNLESS(data_offset == 0, ResultInvalidOffset); | ||
| 578 | R_UNLESS(virtual_data_size == read_size, ResultInvalidSize); | ||
| 579 | R_UNLESS(entry.GetPhysicalSize() <= static_cast<s64>(m_block_size_max), | ||
| 580 | ResultUnexpectedInCompressedStorageD); | ||
| 581 | } | ||
| 582 | |||
| 583 | // Update the required access parameters. | ||
| 584 | s64 gap_from_prev; | ||
| 585 | if (required_access_physical_size > 0) { | ||
| 586 | gap_from_prev = physical_offset - required_access_physical_end; | ||
| 587 | } else { | ||
| 588 | gap_from_prev = 0; | ||
| 589 | required_access_physical_offset = physical_offset; | ||
| 590 | } | ||
| 591 | required_access_physical_size += physical_size + gap_from_prev; | ||
| 592 | |||
| 593 | // Create an entry to access the data storage. | ||
| 594 | entries[entry_count++] = { | ||
| 595 | .compression_type = entry.compression_type, | ||
| 596 | .gap_from_prev = static_cast<u32>(gap_from_prev), | ||
| 597 | .physical_size = static_cast<u32>(physical_size), | ||
| 598 | .virtual_size = static_cast<u32>(read_size), | ||
| 599 | }; | ||
| 600 | } else { | ||
| 601 | // Verify that we're allowed to be operating on the non-data-storage-access | ||
| 602 | // type. | ||
| 603 | R_UNLESS(entry.compression_type == CompressionType::Zeros, | ||
| 604 | ResultUnexpectedInCompressedStorageB); | ||
| 605 | |||
| 606 | // If we have entries, create a fake entry for the zero region. | ||
| 607 | if (entry_count != 0) { | ||
| 608 | // We need to have a physical size. | ||
| 609 | R_UNLESS(entry.GetPhysicalSize() != 0, | ||
| 610 | ResultUnexpectedInCompressedStorageD); | ||
| 611 | |||
| 612 | // Create a fake entry. | ||
| 613 | entries[entry_count++] = { | ||
| 614 | .compression_type = CompressionType::Zeros, | ||
| 615 | .gap_from_prev = 0, | ||
| 616 | .physical_size = 0, | ||
| 617 | .virtual_size = static_cast<u32>(read_size), | ||
| 618 | }; | ||
| 619 | } else { | ||
| 620 | // We have no entries, so we can just perform the read. | ||
| 621 | const Result rc = | ||
| 622 | read_func(static_cast<size_t>(read_size), | ||
| 623 | [&](void* dst, size_t dst_size) -> Result { | ||
| 624 | // Check the space we should zero is correct. | ||
| 625 | ASSERT(dst_size == static_cast<size_t>(read_size)); | ||
| 626 | |||
| 627 | // Zero the memory. | ||
| 628 | std::memset(dst, 0, read_size); | ||
| 629 | R_SUCCEED(); | ||
| 630 | }); | ||
| 631 | if (R_FAILED(rc)) { | ||
| 632 | R_THROW(rc); | ||
| 633 | } | ||
| 634 | } | ||
| 635 | } | ||
| 636 | |||
| 637 | // Set the previous entry. | ||
| 638 | prev_entry = entry; | ||
| 639 | |||
| 640 | // We're continuous. | ||
| 641 | *out_continuous = true; | ||
| 642 | R_SUCCEED(); | ||
| 643 | })); | ||
| 644 | |||
| 645 | // If we still have a pending access, perform it. | ||
| 646 | if (required_access_physical_size != 0) { | ||
| 647 | R_TRY(PerformRequiredRead()); | ||
| 648 | } | ||
| 649 | |||
| 650 | R_SUCCEED(); | ||
| 651 | } | ||
| 652 | |||
| 653 | private: | ||
| 654 | DecompressorFunction GetDecompressor(CompressionType type) const { | ||
| 655 | // Check that we can get a decompressor for the type. | ||
| 656 | if (CompressionTypeUtility::IsUnknownType(type)) { | ||
| 657 | return nullptr; | ||
| 658 | } | ||
| 659 | |||
| 660 | // Get the decompressor. | ||
| 661 | return m_get_decompressor_function(type); | ||
| 662 | } | ||
| 663 | |||
| 664 | bool IsInitialized() const { | ||
| 665 | return m_table.IsInitialized(); | ||
| 666 | } | ||
| 667 | |||
| 668 | private: | ||
| 669 | size_t m_block_size_max; | ||
| 670 | size_t m_continuous_reading_size_max; | ||
| 671 | BucketTree m_table; | ||
| 672 | VirtualFile m_data_storage; | ||
| 673 | GetDecompressorFunction m_get_decompressor_function; | ||
| 674 | }; | ||
| 675 | |||
| 676 | class CacheManager { | ||
| 677 | YUZU_NON_COPYABLE(CacheManager); | ||
| 678 | YUZU_NON_MOVEABLE(CacheManager); | ||
| 679 | |||
| 680 | private: | ||
| 681 | struct AccessRange { | ||
| 682 | s64 virtual_offset; | ||
| 683 | s64 virtual_size; | ||
| 684 | u32 physical_size; | ||
| 685 | bool is_block_alignment_required; | ||
| 686 | |||
| 687 | s64 GetEndVirtualOffset() const { | ||
| 688 | return this->virtual_offset + this->virtual_size; | ||
| 689 | } | ||
| 690 | }; | ||
| 691 | static_assert(std::is_trivial_v<AccessRange>); | ||
| 692 | |||
| 693 | public: | ||
| 694 | CacheManager() = default; | ||
| 695 | |||
| 696 | public: | ||
| 697 | Result Initialize(s64 storage_size, size_t cache_size_0, size_t cache_size_1, | ||
| 698 | size_t max_cache_entries) { | ||
| 699 | // Set our fields. | ||
| 700 | m_storage_size = storage_size; | ||
| 701 | |||
| 702 | R_SUCCEED(); | ||
| 703 | } | ||
| 704 | |||
| 705 | Result Read(CompressedStorageCore& core, s64 offset, void* buffer, size_t size) { | ||
| 706 | // If we have nothing to read, succeed. | ||
| 707 | R_SUCCEED_IF(size == 0); | ||
| 708 | |||
| 709 | // Check that we have a buffer to read into. | ||
| 710 | R_UNLESS(buffer != nullptr, ResultNullptrArgument); | ||
| 711 | |||
| 712 | // Check that the read is in bounds. | ||
| 713 | R_UNLESS(offset <= m_storage_size, ResultInvalidOffset); | ||
| 714 | |||
| 715 | // Determine how much we can read. | ||
| 716 | const size_t read_size = std::min<size_t>(size, m_storage_size - offset); | ||
| 717 | |||
| 718 | // Create head/tail ranges. | ||
| 719 | AccessRange head_range = {}; | ||
| 720 | AccessRange tail_range = {}; | ||
| 721 | bool is_tail_set = false; | ||
| 722 | |||
| 723 | // Operate to determine the head range. | ||
| 724 | R_TRY(core.OperatePerEntry( | ||
| 725 | offset, 1, | ||
| 726 | [&](bool* out_continuous, const Entry& entry, s64 virtual_data_size, | ||
| 727 | s64 data_offset, s64 data_read_size) -> Result { | ||
| 728 | // Set the head range. | ||
| 729 | head_range = { | ||
| 730 | .virtual_offset = entry.virt_offset, | ||
| 731 | .virtual_size = virtual_data_size, | ||
| 732 | .physical_size = static_cast<u32>(entry.phys_size), | ||
| 733 | .is_block_alignment_required = | ||
| 734 | CompressionTypeUtility::IsBlockAlignmentRequired( | ||
| 735 | entry.compression_type), | ||
| 736 | }; | ||
| 737 | |||
| 738 | // If required, set the tail range. | ||
| 739 | if (static_cast<s64>(offset + read_size) <= | ||
| 740 | entry.virt_offset + virtual_data_size) { | ||
| 741 | tail_range = { | ||
| 742 | .virtual_offset = entry.virt_offset, | ||
| 743 | .virtual_size = virtual_data_size, | ||
| 744 | .physical_size = static_cast<u32>(entry.phys_size), | ||
| 745 | .is_block_alignment_required = | ||
| 746 | CompressionTypeUtility::IsBlockAlignmentRequired( | ||
| 747 | entry.compression_type), | ||
| 748 | }; | ||
| 749 | is_tail_set = true; | ||
| 750 | } | ||
| 751 | |||
| 752 | // We only want to determine the head range, so we're not continuous. | ||
| 753 | *out_continuous = false; | ||
| 754 | R_SUCCEED(); | ||
| 755 | })); | ||
| 756 | |||
| 757 | // If necessary, determine the tail range. | ||
| 758 | if (!is_tail_set) { | ||
| 759 | R_TRY(core.OperatePerEntry( | ||
| 760 | offset + read_size - 1, 1, | ||
| 761 | [&](bool* out_continuous, const Entry& entry, s64 virtual_data_size, | ||
| 762 | s64 data_offset, s64 data_read_size) -> Result { | ||
| 763 | // Set the tail range. | ||
| 764 | tail_range = { | ||
| 765 | .virtual_offset = entry.virt_offset, | ||
| 766 | .virtual_size = virtual_data_size, | ||
| 767 | .physical_size = static_cast<u32>(entry.phys_size), | ||
| 768 | .is_block_alignment_required = | ||
| 769 | CompressionTypeUtility::IsBlockAlignmentRequired( | ||
| 770 | entry.compression_type), | ||
| 771 | }; | ||
| 772 | |||
| 773 | // We only want to determine the tail range, so we're not continuous. | ||
| 774 | *out_continuous = false; | ||
| 775 | R_SUCCEED(); | ||
| 776 | })); | ||
| 777 | } | ||
| 778 | |||
| 779 | // Begin performing the accesses. | ||
| 780 | s64 cur_offset = offset; | ||
| 781 | size_t cur_size = read_size; | ||
| 782 | char* cur_dst = static_cast<char*>(buffer); | ||
| 783 | |||
| 784 | // Determine our alignment. | ||
| 785 | const bool head_unaligned = head_range.is_block_alignment_required && | ||
| 786 | (cur_offset != head_range.virtual_offset || | ||
| 787 | static_cast<s64>(cur_size) < head_range.virtual_size); | ||
| 788 | const bool tail_unaligned = [&]() -> bool { | ||
| 789 | if (tail_range.is_block_alignment_required) { | ||
| 790 | if (static_cast<s64>(cur_size + cur_offset) == | ||
| 791 | tail_range.GetEndVirtualOffset()) { | ||
| 792 | return false; | ||
| 793 | } else if (!head_unaligned) { | ||
| 794 | return true; | ||
| 795 | } else { | ||
| 796 | return head_range.GetEndVirtualOffset() < | ||
| 797 | static_cast<s64>(cur_size + cur_offset); | ||
| 798 | } | ||
| 799 | } else { | ||
| 800 | return false; | ||
| 801 | } | ||
| 802 | }(); | ||
| 803 | |||
| 804 | // Determine start/end offsets. | ||
| 805 | const s64 start_offset = | ||
| 806 | head_range.is_block_alignment_required ? head_range.virtual_offset : cur_offset; | ||
| 807 | const s64 end_offset = tail_range.is_block_alignment_required | ||
| 808 | ? tail_range.GetEndVirtualOffset() | ||
| 809 | : cur_offset + cur_size; | ||
| 810 | |||
| 811 | // Perform the read. | ||
| 812 | bool is_burst_reading = false; | ||
| 813 | R_TRY(core.Read( | ||
| 814 | start_offset, end_offset - start_offset, | ||
| 815 | [&](size_t size_buffer_required, | ||
| 816 | const CompressedStorageCore::ReadImplFunction& read_impl) -> Result { | ||
| 817 | // Determine whether we're burst reading. | ||
| 818 | const AccessRange* unaligned_range = nullptr; | ||
| 819 | if (!is_burst_reading) { | ||
| 820 | // Check whether we're using head, tail, or none as unaligned. | ||
| 821 | if (head_unaligned && head_range.virtual_offset <= cur_offset && | ||
| 822 | cur_offset < head_range.GetEndVirtualOffset()) { | ||
| 823 | unaligned_range = std::addressof(head_range); | ||
| 824 | } else if (tail_unaligned && tail_range.virtual_offset <= cur_offset && | ||
| 825 | cur_offset < tail_range.GetEndVirtualOffset()) { | ||
| 826 | unaligned_range = std::addressof(tail_range); | ||
| 827 | } else { | ||
| 828 | is_burst_reading = true; | ||
| 829 | } | ||
| 830 | } | ||
| 831 | ASSERT((is_burst_reading ^ (unaligned_range != nullptr))); | ||
| 832 | |||
| 833 | // Perform reading by burst, or not. | ||
| 834 | if (is_burst_reading) { | ||
| 835 | // Check that the access is valid for burst reading. | ||
| 836 | ASSERT(size_buffer_required <= cur_size); | ||
| 837 | |||
| 838 | // Perform the read. | ||
| 839 | Result rc = read_impl(cur_dst, size_buffer_required); | ||
| 840 | if (R_FAILED(rc)) { | ||
| 841 | R_THROW(rc); | ||
| 842 | } | ||
| 843 | |||
| 844 | // Advance. | ||
| 845 | cur_dst += size_buffer_required; | ||
| 846 | cur_offset += size_buffer_required; | ||
| 847 | cur_size -= size_buffer_required; | ||
| 848 | |||
| 849 | // Determine whether we're going to continue burst reading. | ||
| 850 | const s64 offset_aligned = | ||
| 851 | tail_unaligned ? tail_range.virtual_offset : end_offset; | ||
| 852 | ASSERT(cur_offset <= offset_aligned); | ||
| 853 | |||
| 854 | if (offset_aligned <= cur_offset) { | ||
| 855 | is_burst_reading = false; | ||
| 856 | } | ||
| 857 | } else { | ||
| 858 | // We're not burst reading, so we have some unaligned range. | ||
| 859 | ASSERT(unaligned_range != nullptr); | ||
| 860 | |||
| 861 | // Check that the size is correct. | ||
| 862 | ASSERT(size_buffer_required == | ||
| 863 | static_cast<size_t>(unaligned_range->virtual_size)); | ||
| 864 | |||
| 865 | // Get a pooled buffer for our read. | ||
| 866 | PooledBuffer pooled_buffer; | ||
| 867 | pooled_buffer.Allocate(size_buffer_required, size_buffer_required); | ||
| 868 | |||
| 869 | // Perform read. | ||
| 870 | Result rc = read_impl(pooled_buffer.GetBuffer(), size_buffer_required); | ||
| 871 | if (R_FAILED(rc)) { | ||
| 872 | R_THROW(rc); | ||
| 873 | } | ||
| 874 | |||
| 875 | // Copy the data we read to the destination. | ||
| 876 | const size_t skip_size = cur_offset - unaligned_range->virtual_offset; | ||
| 877 | const size_t copy_size = std::min<size_t>( | ||
| 878 | cur_size, unaligned_range->GetEndVirtualOffset() - cur_offset); | ||
| 879 | |||
| 880 | std::memcpy(cur_dst, pooled_buffer.GetBuffer() + skip_size, copy_size); | ||
| 881 | |||
| 882 | // Advance. | ||
| 883 | cur_dst += copy_size; | ||
| 884 | cur_offset += copy_size; | ||
| 885 | cur_size -= copy_size; | ||
| 886 | } | ||
| 887 | |||
| 888 | R_SUCCEED(); | ||
| 889 | })); | ||
| 890 | |||
| 891 | R_SUCCEED(); | ||
| 892 | } | ||
| 893 | |||
| 894 | private: | ||
| 895 | s64 m_storage_size = 0; | ||
| 896 | }; | ||
| 897 | |||
| 898 | public: | ||
| 899 | CompressedStorage() = default; | ||
| 900 | virtual ~CompressedStorage() { | ||
| 901 | this->Finalize(); | ||
| 902 | } | ||
| 903 | |||
| 904 | Result Initialize(VirtualFile data_storage, VirtualFile node_storage, VirtualFile entry_storage, | ||
| 905 | s32 bktr_entry_count, size_t block_size_max, | ||
| 906 | size_t continuous_reading_size_max, GetDecompressorFunction get_decompressor, | ||
| 907 | size_t cache_size_0, size_t cache_size_1, s32 max_cache_entries) { | ||
| 908 | // Initialize our core. | ||
| 909 | R_TRY(m_core.Initialize(data_storage, node_storage, entry_storage, bktr_entry_count, | ||
| 910 | block_size_max, continuous_reading_size_max, get_decompressor)); | ||
| 911 | |||
| 912 | // Get our core size. | ||
| 913 | s64 core_size = 0; | ||
| 914 | R_TRY(m_core.GetSize(std::addressof(core_size))); | ||
| 915 | |||
| 916 | // Initialize our cache manager. | ||
| 917 | R_TRY(m_cache_manager.Initialize(core_size, cache_size_0, cache_size_1, max_cache_entries)); | ||
| 918 | |||
| 919 | R_SUCCEED(); | ||
| 920 | } | ||
| 921 | |||
| 922 | void Finalize() { | ||
| 923 | m_core.Finalize(); | ||
| 924 | } | ||
| 925 | |||
| 926 | VirtualFile GetDataStorage() { | ||
| 927 | return m_core.GetDataStorage(); | ||
| 928 | } | ||
| 929 | |||
| 930 | Result GetDataStorageSize(s64* out) { | ||
| 931 | R_RETURN(m_core.GetDataStorageSize(out)); | ||
| 932 | } | ||
| 933 | |||
| 934 | Result GetEntryList(Entry* out_entries, s32* out_read_count, s32 max_entry_count, s64 offset, | ||
| 935 | s64 size) { | ||
| 936 | R_RETURN(m_core.GetEntryList(out_entries, out_read_count, max_entry_count, offset, size)); | ||
| 937 | } | ||
| 938 | |||
| 939 | BucketTree& GetEntryTable() { | ||
| 940 | return m_core.GetEntryTable(); | ||
| 941 | } | ||
| 942 | |||
| 943 | public: | ||
| 944 | virtual size_t GetSize() const override { | ||
| 945 | s64 ret{}; | ||
| 946 | m_core.GetSize(&ret); | ||
| 947 | return ret; | ||
| 948 | } | ||
| 949 | |||
| 950 | virtual size_t Read(u8* buffer, size_t size, size_t offset) const override { | ||
| 951 | if (R_SUCCEEDED(m_cache_manager.Read(m_core, offset, buffer, size))) { | ||
| 952 | return size; | ||
| 953 | } else { | ||
| 954 | return 0; | ||
| 955 | } | ||
| 956 | } | ||
| 957 | |||
| 958 | private: | ||
| 959 | mutable CompressedStorageCore m_core; | ||
| 960 | mutable CacheManager m_cache_manager; | ||
| 961 | }; | ||
| 962 | |||
| 963 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "core/hle/result.h" | ||
| 7 | |||
| 8 | namespace FileSys { | ||
| 9 | |||
| 10 | enum class CompressionType : u8 { | ||
| 11 | None = 0, | ||
| 12 | Zeros = 1, | ||
| 13 | Two = 2, | ||
| 14 | Lz4 = 3, | ||
| 15 | Unknown = 4, | ||
| 16 | }; | ||
| 17 | |||
| 18 | using DecompressorFunction = Result (*)(void*, size_t, const void*, size_t); | ||
| 19 | using GetDecompressorFunction = DecompressorFunction (*)(CompressionType); | ||
| 20 | |||
| 21 | constexpr s64 CompressionBlockAlignment = 0x10; | ||
| 22 | |||
| 23 | namespace CompressionTypeUtility { | ||
| 24 | |||
| 25 | constexpr bool IsBlockAlignmentRequired(CompressionType type) { | ||
| 26 | return type != CompressionType::None && type != CompressionType::Zeros; | ||
| 27 | } | ||
| 28 | |||
| 29 | constexpr bool IsDataStorageAccessRequired(CompressionType type) { | ||
| 30 | return type != CompressionType::Zeros; | ||
| 31 | } | ||
| 32 | |||
| 33 | constexpr bool IsRandomAccessible(CompressionType type) { | ||
| 34 | return type == CompressionType::None; | ||
| 35 | } | ||
| 36 | |||
| 37 | constexpr bool IsUnknownType(CompressionType type) { | ||
| 38 | return type >= CompressionType::Unknown; | ||
| 39 | } | ||
| 40 | |||
| 41 | } // namespace CompressionTypeUtility | ||
| 42 | |||
| 43 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "common/lz4_compression.h" | ||
| 5 | #include "core/file_sys/fssystem/fssystem_compression_configuration.h" | ||
| 6 | |||
| 7 | namespace FileSys { | ||
| 8 | |||
| 9 | namespace { | ||
| 10 | |||
| 11 | Result DecompressLz4(void* dst, size_t dst_size, const void* src, size_t src_size) { | ||
| 12 | auto result = Common::Compression::DecompressDataLZ4(dst, dst_size, src, src_size); | ||
| 13 | R_UNLESS(static_cast<size_t>(result) == dst_size, ResultUnexpectedInCompressedStorageC); | ||
| 14 | R_SUCCEED(); | ||
| 15 | } | ||
| 16 | |||
| 17 | constexpr DecompressorFunction GetNcaDecompressorFunction(CompressionType type) { | ||
| 18 | switch (type) { | ||
| 19 | case CompressionType::Lz4: | ||
| 20 | return DecompressLz4; | ||
| 21 | default: | ||
| 22 | return nullptr; | ||
| 23 | } | ||
| 24 | } | ||
| 25 | |||
| 26 | } // namespace | ||
| 27 | |||
| 28 | const NcaCompressionConfiguration& GetNcaCompressionConfiguration() { | ||
| 29 | static const NcaCompressionConfiguration configuration = { | ||
| 30 | .get_decompressor = GetNcaDecompressorFunction, | ||
| 31 | }; | ||
| 32 | |||
| 33 | return configuration; | ||
| 34 | } | ||
| 35 | |||
| 36 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "core/file_sys/fssystem/fssystem_nca_file_system_driver.h" | ||
| 7 | |||
| 8 | namespace FileSys { | ||
| 9 | |||
| 10 | const NcaCompressionConfiguration& GetNcaCompressionConfiguration(); | ||
| 11 | |||
| 12 | } | ||
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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "core/crypto/aes_util.h" | ||
| 5 | #include "core/crypto/key_manager.h" | ||
| 6 | #include "core/file_sys/fssystem/fssystem_crypto_configuration.h" | ||
| 7 | |||
| 8 | namespace FileSys { | ||
| 9 | |||
| 10 | namespace { | ||
| 11 | |||
| 12 | void GenerateKey(void* dst_key, size_t dst_key_size, const void* src_key, size_t src_key_size, | ||
| 13 | s32 key_type) { | ||
| 14 | if (key_type == static_cast<s32>(KeyType::ZeroKey)) { | ||
| 15 | std::memset(dst_key, 0, dst_key_size); | ||
| 16 | return; | ||
| 17 | } | ||
| 18 | |||
| 19 | if (key_type == static_cast<s32>(KeyType::InvalidKey) || | ||
| 20 | key_type < static_cast<s32>(KeyType::ZeroKey) || | ||
| 21 | key_type >= static_cast<s32>(KeyType::NcaExternalKey)) { | ||
| 22 | std::memset(dst_key, 0xFF, dst_key_size); | ||
| 23 | return; | ||
| 24 | } | ||
| 25 | |||
| 26 | const auto& instance = Core::Crypto::KeyManager::Instance(); | ||
| 27 | |||
| 28 | if (key_type == static_cast<s32>(KeyType::NcaHeaderKey1) || | ||
| 29 | key_type == static_cast<s32>(KeyType::NcaHeaderKey2)) { | ||
| 30 | const s32 key_index = static_cast<s32>(KeyType::NcaHeaderKey2) == key_type; | ||
| 31 | const auto key = instance.GetKey(Core::Crypto::S256KeyType::Header); | ||
| 32 | std::memcpy(dst_key, key.data() + key_index * 0x10, std::min(dst_key_size, key.size() / 2)); | ||
| 33 | return; | ||
| 34 | } | ||
| 35 | |||
| 36 | const s32 key_generation = | ||
| 37 | std::max(key_type / NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount, 1) - 1; | ||
| 38 | const s32 key_index = key_type % NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount; | ||
| 39 | |||
| 40 | Core::Crypto::AESCipher<Core::Crypto::Key128> cipher( | ||
| 41 | instance.GetKey(Core::Crypto::S128KeyType::KeyArea, key_generation, key_index), | ||
| 42 | Core::Crypto::Mode::ECB); | ||
| 43 | cipher.Transcode(reinterpret_cast<const u8*>(src_key), src_key_size, | ||
| 44 | reinterpret_cast<u8*>(dst_key), Core::Crypto::Op::Decrypt); | ||
| 45 | } | ||
| 46 | |||
| 47 | } // namespace | ||
| 48 | |||
| 49 | const NcaCryptoConfiguration& GetCryptoConfiguration() { | ||
| 50 | static const NcaCryptoConfiguration configuration = { | ||
| 51 | .header_1_sign_key_moduli{}, | ||
| 52 | .header_1_sign_key_public_exponent{}, | ||
| 53 | .key_area_encryption_key_source{}, | ||
| 54 | .header_encryption_key_source{}, | ||
| 55 | .header_encrypted_encryption_keys{}, | ||
| 56 | .generate_key = GenerateKey, | ||
| 57 | .verify_sign1{}, | ||
| 58 | .is_plaintext_header_available{}, | ||
| 59 | .is_available_sw_key{}, | ||
| 60 | }; | ||
| 61 | |||
| 62 | return configuration; | ||
| 63 | } | ||
| 64 | |||
| 65 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "core/file_sys/fssystem/fssystem_nca_file_system_driver.h" | ||
| 7 | |||
| 8 | namespace FileSys { | ||
| 9 | |||
| 10 | const NcaCryptoConfiguration& GetCryptoConfiguration(); | ||
| 11 | |||
| 12 | } | ||
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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h" | ||
| 5 | #include "core/file_sys/vfs_offset.h" | ||
| 6 | |||
| 7 | namespace FileSys { | ||
| 8 | |||
| 9 | HierarchicalIntegrityVerificationStorage::HierarchicalIntegrityVerificationStorage() | ||
| 10 | : m_data_size(-1) { | ||
| 11 | for (size_t i = 0; i < MaxLayers - 1; i++) { | ||
| 12 | m_verify_storages[i] = std::make_shared<IntegrityVerificationStorage>(); | ||
| 13 | } | ||
| 14 | } | ||
| 15 | |||
| 16 | Result HierarchicalIntegrityVerificationStorage::Initialize( | ||
| 17 | const HierarchicalIntegrityVerificationInformation& info, | ||
| 18 | HierarchicalStorageInformation storage, int max_data_cache_entries, int max_hash_cache_entries, | ||
| 19 | s8 buffer_level) { | ||
| 20 | // Validate preconditions. | ||
| 21 | ASSERT(IntegrityMinLayerCount <= info.max_layers && info.max_layers <= IntegrityMaxLayerCount); | ||
| 22 | |||
| 23 | // Set member variables. | ||
| 24 | m_max_layers = info.max_layers; | ||
| 25 | |||
| 26 | // Initialize the top level verification storage. | ||
| 27 | m_verify_storages[0]->Initialize(storage[HierarchicalStorageInformation::MasterStorage], | ||
| 28 | storage[HierarchicalStorageInformation::Layer1Storage], | ||
| 29 | static_cast<s64>(1) << info.info[0].block_order, HashSize, | ||
| 30 | false); | ||
| 31 | |||
| 32 | // Ensure we don't leak state if further initialization goes wrong. | ||
| 33 | ON_RESULT_FAILURE { | ||
| 34 | m_verify_storages[0]->Finalize(); | ||
| 35 | m_data_size = -1; | ||
| 36 | }; | ||
| 37 | |||
| 38 | // Initialize the top level buffer storage. | ||
| 39 | m_buffer_storages[0] = m_verify_storages[0]; | ||
| 40 | R_UNLESS(m_buffer_storages[0] != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 41 | |||
| 42 | // Prepare to initialize the level storages. | ||
| 43 | s32 level = 0; | ||
| 44 | |||
| 45 | // Ensure we don't leak state if further initialization goes wrong. | ||
| 46 | ON_RESULT_FAILURE_2 { | ||
| 47 | m_verify_storages[level + 1]->Finalize(); | ||
| 48 | for (; level > 0; --level) { | ||
| 49 | m_buffer_storages[level].reset(); | ||
| 50 | m_verify_storages[level]->Finalize(); | ||
| 51 | } | ||
| 52 | }; | ||
| 53 | |||
| 54 | // Initialize the level storages. | ||
| 55 | for (; level < m_max_layers - 3; ++level) { | ||
| 56 | // Initialize the verification storage. | ||
| 57 | auto buffer_storage = | ||
| 58 | std::make_shared<OffsetVfsFile>(m_buffer_storages[level], info.info[level].size, 0); | ||
| 59 | m_verify_storages[level + 1]->Initialize( | ||
| 60 | std::move(buffer_storage), storage[level + 2], | ||
| 61 | static_cast<s64>(1) << info.info[level + 1].block_order, | ||
| 62 | static_cast<s64>(1) << info.info[level].block_order, false); | ||
| 63 | |||
| 64 | // Initialize the buffer storage. | ||
| 65 | m_buffer_storages[level + 1] = m_verify_storages[level + 1]; | ||
| 66 | R_UNLESS(m_buffer_storages[level + 1] != nullptr, | ||
| 67 | ResultAllocationMemoryFailedAllocateShared); | ||
| 68 | } | ||
| 69 | |||
| 70 | // Initialize the final level storage. | ||
| 71 | { | ||
| 72 | // Initialize the verification storage. | ||
| 73 | auto buffer_storage = | ||
| 74 | std::make_shared<OffsetVfsFile>(m_buffer_storages[level], info.info[level].size, 0); | ||
| 75 | m_verify_storages[level + 1]->Initialize( | ||
| 76 | std::move(buffer_storage), storage[level + 2], | ||
| 77 | static_cast<s64>(1) << info.info[level + 1].block_order, | ||
| 78 | static_cast<s64>(1) << info.info[level].block_order, true); | ||
| 79 | |||
| 80 | // Initialize the buffer storage. | ||
| 81 | m_buffer_storages[level + 1] = m_verify_storages[level + 1]; | ||
| 82 | R_UNLESS(m_buffer_storages[level + 1] != nullptr, | ||
| 83 | ResultAllocationMemoryFailedAllocateShared); | ||
| 84 | } | ||
| 85 | |||
| 86 | // Set the data size. | ||
| 87 | m_data_size = info.info[level + 1].size; | ||
| 88 | |||
| 89 | // We succeeded. | ||
| 90 | R_SUCCEED(); | ||
| 91 | } | ||
| 92 | |||
| 93 | void HierarchicalIntegrityVerificationStorage::Finalize() { | ||
| 94 | if (m_data_size >= 0) { | ||
| 95 | m_data_size = 0; | ||
| 96 | |||
| 97 | for (s32 level = m_max_layers - 2; level >= 0; --level) { | ||
| 98 | m_buffer_storages[level].reset(); | ||
| 99 | m_verify_storages[level]->Finalize(); | ||
| 100 | } | ||
| 101 | |||
| 102 | m_data_size = -1; | ||
| 103 | } | ||
| 104 | } | ||
| 105 | |||
| 106 | size_t HierarchicalIntegrityVerificationStorage::Read(u8* buffer, size_t size, | ||
| 107 | size_t offset) const { | ||
| 108 | // Validate preconditions. | ||
| 109 | ASSERT(m_data_size >= 0); | ||
| 110 | |||
| 111 | // Succeed if zero-size. | ||
| 112 | if (size == 0) { | ||
| 113 | return size; | ||
| 114 | } | ||
| 115 | |||
| 116 | // Validate arguments. | ||
| 117 | ASSERT(buffer != nullptr); | ||
| 118 | |||
| 119 | // Read the data. | ||
| 120 | return m_buffer_storages[m_max_layers - 2]->Read(buffer, size, offset); | ||
| 121 | } | ||
| 122 | |||
| 123 | size_t HierarchicalIntegrityVerificationStorage::GetSize() const { | ||
| 124 | return m_data_size; | ||
| 125 | } | ||
| 126 | |||
| 127 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "common/alignment.h" | ||
| 7 | #include "core/file_sys/fssystem/fs_i_storage.h" | ||
| 8 | #include "core/file_sys/fssystem/fs_types.h" | ||
| 9 | #include "core/file_sys/fssystem/fssystem_alignment_matching_storage.h" | ||
| 10 | #include "core/file_sys/fssystem/fssystem_integrity_verification_storage.h" | ||
| 11 | #include "core/file_sys/vfs_offset.h" | ||
| 12 | |||
| 13 | namespace FileSys { | ||
| 14 | |||
| 15 | struct HierarchicalIntegrityVerificationLevelInformation { | ||
| 16 | Int64 offset; | ||
| 17 | Int64 size; | ||
| 18 | s32 block_order; | ||
| 19 | std::array<u8, 4> reserved; | ||
| 20 | }; | ||
| 21 | static_assert(std::is_trivial_v<HierarchicalIntegrityVerificationLevelInformation>); | ||
| 22 | static_assert(sizeof(HierarchicalIntegrityVerificationLevelInformation) == 0x18); | ||
| 23 | static_assert(alignof(HierarchicalIntegrityVerificationLevelInformation) == 0x4); | ||
| 24 | |||
| 25 | struct HierarchicalIntegrityVerificationInformation { | ||
| 26 | u32 max_layers; | ||
| 27 | std::array<HierarchicalIntegrityVerificationLevelInformation, IntegrityMaxLayerCount - 1> info; | ||
| 28 | HashSalt seed; | ||
| 29 | |||
| 30 | s64 GetLayeredHashSize() const { | ||
| 31 | return this->info[this->max_layers - 2].offset; | ||
| 32 | } | ||
| 33 | |||
| 34 | s64 GetDataOffset() const { | ||
| 35 | return this->info[this->max_layers - 2].offset; | ||
| 36 | } | ||
| 37 | |||
| 38 | s64 GetDataSize() const { | ||
| 39 | return this->info[this->max_layers - 2].size; | ||
| 40 | } | ||
| 41 | }; | ||
| 42 | static_assert(std::is_trivial_v<HierarchicalIntegrityVerificationInformation>); | ||
| 43 | |||
| 44 | struct HierarchicalIntegrityVerificationMetaInformation { | ||
| 45 | u32 magic; | ||
| 46 | u32 version; | ||
| 47 | u32 master_hash_size; | ||
| 48 | HierarchicalIntegrityVerificationInformation level_hash_info; | ||
| 49 | }; | ||
| 50 | static_assert(std::is_trivial_v<HierarchicalIntegrityVerificationMetaInformation>); | ||
| 51 | |||
| 52 | struct HierarchicalIntegrityVerificationSizeSet { | ||
| 53 | s64 control_size; | ||
| 54 | s64 master_hash_size; | ||
| 55 | std::array<s64, IntegrityMaxLayerCount - 2> layered_hash_sizes; | ||
| 56 | }; | ||
| 57 | static_assert(std::is_trivial_v<HierarchicalIntegrityVerificationSizeSet>); | ||
| 58 | |||
| 59 | class HierarchicalIntegrityVerificationStorage : public IReadOnlyStorage { | ||
| 60 | YUZU_NON_COPYABLE(HierarchicalIntegrityVerificationStorage); | ||
| 61 | YUZU_NON_MOVEABLE(HierarchicalIntegrityVerificationStorage); | ||
| 62 | |||
| 63 | public: | ||
| 64 | using GenerateRandomFunction = void (*)(void* dst, size_t size); | ||
| 65 | |||
| 66 | class HierarchicalStorageInformation { | ||
| 67 | public: | ||
| 68 | enum { | ||
| 69 | MasterStorage = 0, | ||
| 70 | Layer1Storage = 1, | ||
| 71 | Layer2Storage = 2, | ||
| 72 | Layer3Storage = 3, | ||
| 73 | Layer4Storage = 4, | ||
| 74 | Layer5Storage = 5, | ||
| 75 | DataStorage = 6, | ||
| 76 | }; | ||
| 77 | |||
| 78 | private: | ||
| 79 | std::array<VirtualFile, DataStorage + 1> m_storages; | ||
| 80 | |||
| 81 | public: | ||
| 82 | void SetMasterHashStorage(VirtualFile s) { | ||
| 83 | m_storages[MasterStorage] = s; | ||
| 84 | } | ||
| 85 | void SetLayer1HashStorage(VirtualFile s) { | ||
| 86 | m_storages[Layer1Storage] = s; | ||
| 87 | } | ||
| 88 | void SetLayer2HashStorage(VirtualFile s) { | ||
| 89 | m_storages[Layer2Storage] = s; | ||
| 90 | } | ||
| 91 | void SetLayer3HashStorage(VirtualFile s) { | ||
| 92 | m_storages[Layer3Storage] = s; | ||
| 93 | } | ||
| 94 | void SetLayer4HashStorage(VirtualFile s) { | ||
| 95 | m_storages[Layer4Storage] = s; | ||
| 96 | } | ||
| 97 | void SetLayer5HashStorage(VirtualFile s) { | ||
| 98 | m_storages[Layer5Storage] = s; | ||
| 99 | } | ||
| 100 | void SetDataStorage(VirtualFile s) { | ||
| 101 | m_storages[DataStorage] = s; | ||
| 102 | } | ||
| 103 | |||
| 104 | VirtualFile& operator[](s32 index) { | ||
| 105 | ASSERT(MasterStorage <= index && index <= DataStorage); | ||
| 106 | return m_storages[index]; | ||
| 107 | } | ||
| 108 | }; | ||
| 109 | |||
| 110 | public: | ||
| 111 | HierarchicalIntegrityVerificationStorage(); | ||
| 112 | virtual ~HierarchicalIntegrityVerificationStorage() override { | ||
| 113 | this->Finalize(); | ||
| 114 | } | ||
| 115 | |||
| 116 | Result Initialize(const HierarchicalIntegrityVerificationInformation& info, | ||
| 117 | HierarchicalStorageInformation storage, int max_data_cache_entries, | ||
| 118 | int max_hash_cache_entries, s8 buffer_level); | ||
| 119 | void Finalize(); | ||
| 120 | |||
| 121 | virtual size_t Read(u8* buffer, size_t size, size_t offset) const override; | ||
| 122 | virtual size_t GetSize() const override; | ||
| 123 | |||
| 124 | bool IsInitialized() const { | ||
| 125 | return m_data_size >= 0; | ||
| 126 | } | ||
| 127 | |||
| 128 | s64 GetL1HashVerificationBlockSize() const { | ||
| 129 | return m_verify_storages[m_max_layers - 2]->GetBlockSize(); | ||
| 130 | } | ||
| 131 | |||
| 132 | VirtualFile GetL1HashStorage() { | ||
| 133 | return std::make_shared<OffsetVfsFile>( | ||
| 134 | m_buffer_storages[m_max_layers - 3], | ||
| 135 | Common::DivideUp(m_data_size, this->GetL1HashVerificationBlockSize()), 0); | ||
| 136 | } | ||
| 137 | |||
| 138 | public: | ||
| 139 | static constexpr s8 GetDefaultDataCacheBufferLevel(u32 max_layers) { | ||
| 140 | return static_cast<s8>(16 + max_layers - 2); | ||
| 141 | } | ||
| 142 | |||
| 143 | protected: | ||
| 144 | static constexpr s64 HashSize = 256 / 8; | ||
| 145 | static constexpr size_t MaxLayers = IntegrityMaxLayerCount; | ||
| 146 | |||
| 147 | private: | ||
| 148 | static GenerateRandomFunction s_generate_random; | ||
| 149 | |||
| 150 | static void SetGenerateRandomFunction(GenerateRandomFunction func) { | ||
| 151 | s_generate_random = func; | ||
| 152 | } | ||
| 153 | |||
| 154 | private: | ||
| 155 | friend struct HierarchicalIntegrityVerificationMetaInformation; | ||
| 156 | |||
| 157 | private: | ||
| 158 | std::array<std::shared_ptr<IntegrityVerificationStorage>, MaxLayers - 1> m_verify_storages; | ||
| 159 | std::array<VirtualFile, MaxLayers - 1> m_buffer_storages; | ||
| 160 | s64 m_data_size; | ||
| 161 | s32 m_max_layers; | ||
| 162 | }; | ||
| 163 | |||
| 164 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "common/alignment.h" | ||
| 5 | #include "common/scope_exit.h" | ||
| 6 | #include "core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.h" | ||
| 7 | |||
| 8 | namespace FileSys { | ||
| 9 | |||
| 10 | namespace { | ||
| 11 | |||
| 12 | s32 Log2(s32 value) { | ||
| 13 | ASSERT(value > 0); | ||
| 14 | ASSERT(Common::IsPowerOfTwo(value)); | ||
| 15 | |||
| 16 | s32 log = 0; | ||
| 17 | while ((value >>= 1) > 0) { | ||
| 18 | ++log; | ||
| 19 | } | ||
| 20 | return log; | ||
| 21 | } | ||
| 22 | |||
| 23 | } // namespace | ||
| 24 | |||
| 25 | Result HierarchicalSha256Storage::Initialize(VirtualFile* base_storages, s32 layer_count, | ||
| 26 | size_t htbs, void* hash_buf, size_t hash_buf_size) { | ||
| 27 | // Validate preconditions. | ||
| 28 | ASSERT(layer_count == LayerCount); | ||
| 29 | ASSERT(Common::IsPowerOfTwo(htbs)); | ||
| 30 | ASSERT(hash_buf != nullptr); | ||
| 31 | |||
| 32 | // Set size tracking members. | ||
| 33 | m_hash_target_block_size = static_cast<s32>(htbs); | ||
| 34 | m_log_size_ratio = Log2(m_hash_target_block_size / HashSize); | ||
| 35 | |||
| 36 | // Get the base storage size. | ||
| 37 | m_base_storage_size = base_storages[2]->GetSize(); | ||
| 38 | { | ||
| 39 | auto size_guard = SCOPE_GUARD({ m_base_storage_size = 0; }); | ||
| 40 | R_UNLESS(m_base_storage_size <= static_cast<s64>(HashSize) | ||
| 41 | << m_log_size_ratio << m_log_size_ratio, | ||
| 42 | ResultHierarchicalSha256BaseStorageTooLarge); | ||
| 43 | size_guard.Cancel(); | ||
| 44 | } | ||
| 45 | |||
| 46 | // Set hash buffer tracking members. | ||
| 47 | m_base_storage = base_storages[2]; | ||
| 48 | m_hash_buffer = static_cast<char*>(hash_buf); | ||
| 49 | m_hash_buffer_size = hash_buf_size; | ||
| 50 | |||
| 51 | // Read the master hash. | ||
| 52 | std::array<u8, HashSize> master_hash{}; | ||
| 53 | base_storages[0]->ReadObject(std::addressof(master_hash)); | ||
| 54 | |||
| 55 | // Read and validate the data being hashed. | ||
| 56 | s64 hash_storage_size = base_storages[1]->GetSize(); | ||
| 57 | ASSERT(Common::IsAligned(hash_storage_size, HashSize)); | ||
| 58 | ASSERT(hash_storage_size <= m_hash_target_block_size); | ||
| 59 | ASSERT(hash_storage_size <= static_cast<s64>(m_hash_buffer_size)); | ||
| 60 | |||
| 61 | base_storages[1]->Read(reinterpret_cast<u8*>(m_hash_buffer), | ||
| 62 | static_cast<size_t>(hash_storage_size), 0); | ||
| 63 | |||
| 64 | R_SUCCEED(); | ||
| 65 | } | ||
| 66 | |||
| 67 | size_t HierarchicalSha256Storage::Read(u8* buffer, size_t size, size_t offset) const { | ||
| 68 | // Succeed if zero-size. | ||
| 69 | if (size == 0) { | ||
| 70 | return size; | ||
| 71 | } | ||
| 72 | |||
| 73 | // Validate that we have a buffer to read into. | ||
| 74 | ASSERT(buffer != nullptr); | ||
| 75 | |||
| 76 | // Read the data. | ||
| 77 | return m_base_storage->Read(buffer, size, offset); | ||
| 78 | } | ||
| 79 | |||
| 80 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <mutex> | ||
| 7 | |||
| 8 | #include "core/file_sys/errors.h" | ||
| 9 | #include "core/file_sys/fssystem/fs_i_storage.h" | ||
| 10 | #include "core/file_sys/vfs.h" | ||
| 11 | |||
| 12 | namespace FileSys { | ||
| 13 | |||
| 14 | class HierarchicalSha256Storage : public IReadOnlyStorage { | ||
| 15 | YUZU_NON_COPYABLE(HierarchicalSha256Storage); | ||
| 16 | YUZU_NON_MOVEABLE(HierarchicalSha256Storage); | ||
| 17 | |||
| 18 | public: | ||
| 19 | static constexpr s32 LayerCount = 3; | ||
| 20 | static constexpr size_t HashSize = 256 / 8; | ||
| 21 | |||
| 22 | public: | ||
| 23 | HierarchicalSha256Storage() : m_mutex() {} | ||
| 24 | |||
| 25 | Result Initialize(VirtualFile* base_storages, s32 layer_count, size_t htbs, void* hash_buf, | ||
| 26 | size_t hash_buf_size); | ||
| 27 | |||
| 28 | virtual size_t GetSize() const override { | ||
| 29 | return m_base_storage->GetSize(); | ||
| 30 | } | ||
| 31 | |||
| 32 | virtual size_t Read(u8* buffer, size_t length, size_t offset) const override; | ||
| 33 | |||
| 34 | private: | ||
| 35 | VirtualFile m_base_storage; | ||
| 36 | s64 m_base_storage_size; | ||
| 37 | char* m_hash_buffer; | ||
| 38 | size_t m_hash_buffer_size; | ||
| 39 | s32 m_hash_target_block_size; | ||
| 40 | s32 m_log_size_ratio; | ||
| 41 | std::mutex m_mutex; | ||
| 42 | }; | ||
| 43 | |||
| 44 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "core/file_sys/errors.h" | ||
| 5 | #include "core/file_sys/fssystem/fssystem_indirect_storage.h" | ||
| 6 | |||
| 7 | namespace FileSys { | ||
| 8 | |||
| 9 | Result IndirectStorage::Initialize(VirtualFile table_storage) { | ||
| 10 | // Read and verify the bucket tree header. | ||
| 11 | BucketTree::Header header; | ||
| 12 | table_storage->ReadObject(std::addressof(header)); | ||
| 13 | R_TRY(header.Verify()); | ||
| 14 | |||
| 15 | // Determine extents. | ||
| 16 | const auto node_storage_size = QueryNodeStorageSize(header.entry_count); | ||
| 17 | const auto entry_storage_size = QueryEntryStorageSize(header.entry_count); | ||
| 18 | const auto node_storage_offset = QueryHeaderStorageSize(); | ||
| 19 | const auto entry_storage_offset = node_storage_offset + node_storage_size; | ||
| 20 | |||
| 21 | // Initialize. | ||
| 22 | R_RETURN(this->Initialize( | ||
| 23 | std::make_shared<OffsetVfsFile>(table_storage, node_storage_size, node_storage_offset), | ||
| 24 | std::make_shared<OffsetVfsFile>(table_storage, entry_storage_size, entry_storage_offset), | ||
| 25 | header.entry_count)); | ||
| 26 | } | ||
| 27 | |||
| 28 | void IndirectStorage::Finalize() { | ||
| 29 | if (this->IsInitialized()) { | ||
| 30 | m_table.Finalize(); | ||
| 31 | for (auto i = 0; i < StorageCount; i++) { | ||
| 32 | m_data_storage[i] = VirtualFile(); | ||
| 33 | } | ||
| 34 | } | ||
| 35 | } | ||
| 36 | |||
| 37 | Result IndirectStorage::GetEntryList(Entry* out_entries, s32* out_entry_count, s32 entry_count, | ||
| 38 | s64 offset, s64 size) { | ||
| 39 | // Validate pre-conditions. | ||
| 40 | ASSERT(offset >= 0); | ||
| 41 | ASSERT(size >= 0); | ||
| 42 | ASSERT(this->IsInitialized()); | ||
| 43 | |||
| 44 | // Clear the out count. | ||
| 45 | R_UNLESS(out_entry_count != nullptr, ResultNullptrArgument); | ||
| 46 | *out_entry_count = 0; | ||
| 47 | |||
| 48 | // Succeed if there's no range. | ||
| 49 | R_SUCCEED_IF(size == 0); | ||
| 50 | |||
| 51 | // If we have an output array, we need it to be non-null. | ||
| 52 | R_UNLESS(out_entries != nullptr || entry_count == 0, ResultNullptrArgument); | ||
| 53 | |||
| 54 | // Check that our range is valid. | ||
| 55 | BucketTree::Offsets table_offsets; | ||
| 56 | R_TRY(m_table.GetOffsets(std::addressof(table_offsets))); | ||
| 57 | |||
| 58 | R_UNLESS(table_offsets.IsInclude(offset, size), ResultOutOfRange); | ||
| 59 | |||
| 60 | // Find the offset in our tree. | ||
| 61 | BucketTree::Visitor visitor; | ||
| 62 | R_TRY(m_table.Find(std::addressof(visitor), offset)); | ||
| 63 | { | ||
| 64 | const auto entry_offset = visitor.Get<Entry>()->GetVirtualOffset(); | ||
| 65 | R_UNLESS(0 <= entry_offset && table_offsets.IsInclude(entry_offset), | ||
| 66 | ResultInvalidIndirectEntryOffset); | ||
| 67 | } | ||
| 68 | |||
| 69 | // Prepare to loop over entries. | ||
| 70 | const auto end_offset = offset + static_cast<s64>(size); | ||
| 71 | s32 count = 0; | ||
| 72 | |||
| 73 | auto cur_entry = *visitor.Get<Entry>(); | ||
| 74 | while (cur_entry.GetVirtualOffset() < end_offset) { | ||
| 75 | // Try to write the entry to the out list. | ||
| 76 | if (entry_count != 0) { | ||
| 77 | if (count >= entry_count) { | ||
| 78 | break; | ||
| 79 | } | ||
| 80 | std::memcpy(out_entries + count, std::addressof(cur_entry), sizeof(Entry)); | ||
| 81 | } | ||
| 82 | |||
| 83 | count++; | ||
| 84 | |||
| 85 | // Advance. | ||
| 86 | if (visitor.CanMoveNext()) { | ||
| 87 | R_TRY(visitor.MoveNext()); | ||
| 88 | cur_entry = *visitor.Get<Entry>(); | ||
| 89 | } else { | ||
| 90 | break; | ||
| 91 | } | ||
| 92 | } | ||
| 93 | |||
| 94 | // Write the output count. | ||
| 95 | *out_entry_count = count; | ||
| 96 | R_SUCCEED(); | ||
| 97 | } | ||
| 98 | |||
| 99 | size_t IndirectStorage::Read(u8* buffer, size_t size, size_t offset) const { | ||
| 100 | // Validate pre-conditions. | ||
| 101 | ASSERT(this->IsInitialized()); | ||
| 102 | ASSERT(buffer != nullptr); | ||
| 103 | |||
| 104 | // Succeed if there's nothing to read. | ||
| 105 | if (size == 0) { | ||
| 106 | return 0; | ||
| 107 | } | ||
| 108 | |||
| 109 | const_cast<IndirectStorage*>(this)->OperatePerEntry<true, true>( | ||
| 110 | offset, size, | ||
| 111 | [=](VirtualFile storage, s64 data_offset, s64 cur_offset, s64 cur_size) -> Result { | ||
| 112 | storage->Read(reinterpret_cast<u8*>(buffer) + (cur_offset - offset), | ||
| 113 | static_cast<size_t>(cur_size), data_offset); | ||
| 114 | R_SUCCEED(); | ||
| 115 | }); | ||
| 116 | |||
| 117 | return size; | ||
| 118 | } | ||
| 119 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "core/file_sys/errors.h" | ||
| 7 | #include "core/file_sys/fssystem/fs_i_storage.h" | ||
| 8 | #include "core/file_sys/fssystem/fssystem_bucket_tree.h" | ||
| 9 | #include "core/file_sys/fssystem/fssystem_bucket_tree_template_impl.h" | ||
| 10 | #include "core/file_sys/vfs.h" | ||
| 11 | #include "core/file_sys/vfs_offset.h" | ||
| 12 | |||
| 13 | namespace FileSys { | ||
| 14 | |||
| 15 | class IndirectStorage : public IReadOnlyStorage { | ||
| 16 | YUZU_NON_COPYABLE(IndirectStorage); | ||
| 17 | YUZU_NON_MOVEABLE(IndirectStorage); | ||
| 18 | |||
| 19 | public: | ||
| 20 | static constexpr s32 StorageCount = 2; | ||
| 21 | static constexpr size_t NodeSize = 16_KiB; | ||
| 22 | |||
| 23 | struct Entry { | ||
| 24 | std::array<u8, sizeof(s64)> virt_offset; | ||
| 25 | std::array<u8, sizeof(s64)> phys_offset; | ||
| 26 | s32 storage_index; | ||
| 27 | |||
| 28 | void SetVirtualOffset(const s64& ofs) { | ||
| 29 | std::memcpy(this->virt_offset.data(), std::addressof(ofs), sizeof(s64)); | ||
| 30 | } | ||
| 31 | |||
| 32 | s64 GetVirtualOffset() const { | ||
| 33 | s64 offset; | ||
| 34 | std::memcpy(std::addressof(offset), this->virt_offset.data(), sizeof(s64)); | ||
| 35 | return offset; | ||
| 36 | } | ||
| 37 | |||
| 38 | void SetPhysicalOffset(const s64& ofs) { | ||
| 39 | std::memcpy(this->phys_offset.data(), std::addressof(ofs), sizeof(s64)); | ||
| 40 | } | ||
| 41 | |||
| 42 | s64 GetPhysicalOffset() const { | ||
| 43 | s64 offset; | ||
| 44 | std::memcpy(std::addressof(offset), this->phys_offset.data(), sizeof(s64)); | ||
| 45 | return offset; | ||
| 46 | } | ||
| 47 | }; | ||
| 48 | static_assert(std::is_trivial_v<Entry>); | ||
| 49 | static_assert(sizeof(Entry) == 0x14); | ||
| 50 | |||
| 51 | struct EntryData { | ||
| 52 | s64 virt_offset; | ||
| 53 | s64 phys_offset; | ||
| 54 | s32 storage_index; | ||
| 55 | |||
| 56 | void Set(const Entry& entry) { | ||
| 57 | this->virt_offset = entry.GetVirtualOffset(); | ||
| 58 | this->phys_offset = entry.GetPhysicalOffset(); | ||
| 59 | this->storage_index = entry.storage_index; | ||
| 60 | } | ||
| 61 | }; | ||
| 62 | static_assert(std::is_trivial_v<EntryData>); | ||
| 63 | |||
| 64 | public: | ||
| 65 | IndirectStorage() : m_table(), m_data_storage() {} | ||
| 66 | virtual ~IndirectStorage() { | ||
| 67 | this->Finalize(); | ||
| 68 | } | ||
| 69 | |||
| 70 | Result Initialize(VirtualFile table_storage); | ||
| 71 | void Finalize(); | ||
| 72 | |||
| 73 | bool IsInitialized() const { | ||
| 74 | return m_table.IsInitialized(); | ||
| 75 | } | ||
| 76 | |||
| 77 | Result Initialize(VirtualFile node_storage, VirtualFile entry_storage, s32 entry_count) { | ||
| 78 | R_RETURN( | ||
| 79 | m_table.Initialize(node_storage, entry_storage, NodeSize, sizeof(Entry), entry_count)); | ||
| 80 | } | ||
| 81 | |||
| 82 | void SetStorage(s32 idx, VirtualFile storage) { | ||
| 83 | ASSERT(0 <= idx && idx < StorageCount); | ||
| 84 | m_data_storage[idx] = storage; | ||
| 85 | } | ||
| 86 | |||
| 87 | template <typename T> | ||
| 88 | void SetStorage(s32 idx, T storage, s64 offset, s64 size) { | ||
| 89 | ASSERT(0 <= idx && idx < StorageCount); | ||
| 90 | m_data_storage[idx] = std::make_shared<OffsetVfsFile>(storage, size, offset); | ||
| 91 | } | ||
| 92 | |||
| 93 | Result GetEntryList(Entry* out_entries, s32* out_entry_count, s32 entry_count, s64 offset, | ||
| 94 | s64 size); | ||
| 95 | |||
| 96 | virtual size_t GetSize() const override { | ||
| 97 | BucketTree::Offsets offsets{}; | ||
| 98 | m_table.GetOffsets(std::addressof(offsets)); | ||
| 99 | |||
| 100 | return offsets.end_offset; | ||
| 101 | } | ||
| 102 | |||
| 103 | virtual size_t Read(u8* buffer, size_t size, size_t offset) const override; | ||
| 104 | |||
| 105 | public: | ||
| 106 | static constexpr s64 QueryHeaderStorageSize() { | ||
| 107 | return BucketTree::QueryHeaderStorageSize(); | ||
| 108 | } | ||
| 109 | |||
| 110 | static constexpr s64 QueryNodeStorageSize(s32 entry_count) { | ||
| 111 | return BucketTree::QueryNodeStorageSize(NodeSize, sizeof(Entry), entry_count); | ||
| 112 | } | ||
| 113 | |||
| 114 | static constexpr s64 QueryEntryStorageSize(s32 entry_count) { | ||
| 115 | return BucketTree::QueryEntryStorageSize(NodeSize, sizeof(Entry), entry_count); | ||
| 116 | } | ||
| 117 | |||
| 118 | protected: | ||
| 119 | BucketTree& GetEntryTable() { | ||
| 120 | return m_table; | ||
| 121 | } | ||
| 122 | |||
| 123 | VirtualFile& GetDataStorage(s32 index) { | ||
| 124 | ASSERT(0 <= index && index < StorageCount); | ||
| 125 | return m_data_storage[index]; | ||
| 126 | } | ||
| 127 | |||
| 128 | template <bool ContinuousCheck, bool RangeCheck, typename F> | ||
| 129 | Result OperatePerEntry(s64 offset, s64 size, F func); | ||
| 130 | |||
| 131 | private: | ||
| 132 | struct ContinuousReadingEntry { | ||
| 133 | static constexpr size_t FragmentSizeMax = 4_KiB; | ||
| 134 | |||
| 135 | IndirectStorage::Entry entry; | ||
| 136 | |||
| 137 | s64 GetVirtualOffset() const { | ||
| 138 | return this->entry.GetVirtualOffset(); | ||
| 139 | } | ||
| 140 | |||
| 141 | s64 GetPhysicalOffset() const { | ||
| 142 | return this->entry.GetPhysicalOffset(); | ||
| 143 | } | ||
| 144 | |||
| 145 | bool IsFragment() const { | ||
| 146 | return this->entry.storage_index != 0; | ||
| 147 | } | ||
| 148 | }; | ||
| 149 | static_assert(std::is_trivial_v<ContinuousReadingEntry>); | ||
| 150 | |||
| 151 | private: | ||
| 152 | mutable BucketTree m_table; | ||
| 153 | std::array<VirtualFile, StorageCount> m_data_storage; | ||
| 154 | }; | ||
| 155 | |||
| 156 | template <bool ContinuousCheck, bool RangeCheck, typename F> | ||
| 157 | Result IndirectStorage::OperatePerEntry(s64 offset, s64 size, F func) { | ||
| 158 | // Validate preconditions. | ||
| 159 | ASSERT(offset >= 0); | ||
| 160 | ASSERT(size >= 0); | ||
| 161 | ASSERT(this->IsInitialized()); | ||
| 162 | |||
| 163 | // Succeed if there's nothing to operate on. | ||
| 164 | R_SUCCEED_IF(size == 0); | ||
| 165 | |||
| 166 | // Get the table offsets. | ||
| 167 | BucketTree::Offsets table_offsets; | ||
| 168 | R_TRY(m_table.GetOffsets(std::addressof(table_offsets))); | ||
| 169 | |||
| 170 | // Validate arguments. | ||
| 171 | R_UNLESS(table_offsets.IsInclude(offset, size), ResultOutOfRange); | ||
| 172 | |||
| 173 | // Find the offset in our tree. | ||
| 174 | BucketTree::Visitor visitor; | ||
| 175 | R_TRY(m_table.Find(std::addressof(visitor), offset)); | ||
| 176 | { | ||
| 177 | const auto entry_offset = visitor.Get<Entry>()->GetVirtualOffset(); | ||
| 178 | R_UNLESS(0 <= entry_offset && table_offsets.IsInclude(entry_offset), | ||
| 179 | ResultInvalidIndirectEntryOffset); | ||
| 180 | } | ||
| 181 | |||
| 182 | // Prepare to operate in chunks. | ||
| 183 | auto cur_offset = offset; | ||
| 184 | const auto end_offset = offset + static_cast<s64>(size); | ||
| 185 | BucketTree::ContinuousReadingInfo cr_info; | ||
| 186 | |||
| 187 | while (cur_offset < end_offset) { | ||
| 188 | // Get the current entry. | ||
| 189 | const auto cur_entry = *visitor.Get<Entry>(); | ||
| 190 | |||
| 191 | // Get and validate the entry's offset. | ||
| 192 | const auto cur_entry_offset = cur_entry.GetVirtualOffset(); | ||
| 193 | R_UNLESS(cur_entry_offset <= cur_offset, ResultInvalidIndirectEntryOffset); | ||
| 194 | |||
| 195 | // Validate the storage index. | ||
| 196 | R_UNLESS(0 <= cur_entry.storage_index && cur_entry.storage_index < StorageCount, | ||
| 197 | ResultInvalidIndirectEntryStorageIndex); | ||
| 198 | |||
| 199 | // If we need to check the continuous info, do so. | ||
| 200 | if constexpr (ContinuousCheck) { | ||
| 201 | // Scan, if we need to. | ||
| 202 | if (cr_info.CheckNeedScan()) { | ||
| 203 | R_TRY(visitor.ScanContinuousReading<ContinuousReadingEntry>( | ||
| 204 | std::addressof(cr_info), cur_offset, | ||
| 205 | static_cast<size_t>(end_offset - cur_offset))); | ||
| 206 | } | ||
| 207 | |||
| 208 | // Process a base storage entry. | ||
| 209 | if (cr_info.CanDo()) { | ||
| 210 | // Ensure that we can process. | ||
| 211 | R_UNLESS(cur_entry.storage_index == 0, ResultInvalidIndirectEntryStorageIndex); | ||
| 212 | |||
| 213 | // Ensure that we remain within range. | ||
| 214 | const auto data_offset = cur_offset - cur_entry_offset; | ||
| 215 | const auto cur_entry_phys_offset = cur_entry.GetPhysicalOffset(); | ||
| 216 | const auto cur_size = static_cast<s64>(cr_info.GetReadSize()); | ||
| 217 | |||
| 218 | // If we should, verify the range. | ||
| 219 | if constexpr (RangeCheck) { | ||
| 220 | // Get the current data storage's size. | ||
| 221 | s64 cur_data_storage_size = m_data_storage[0]->GetSize(); | ||
| 222 | |||
| 223 | R_UNLESS(0 <= cur_entry_phys_offset && | ||
| 224 | cur_entry_phys_offset <= cur_data_storage_size, | ||
| 225 | ResultInvalidIndirectEntryOffset); | ||
| 226 | R_UNLESS(cur_entry_phys_offset + data_offset + cur_size <= | ||
| 227 | cur_data_storage_size, | ||
| 228 | ResultInvalidIndirectStorageSize); | ||
| 229 | } | ||
| 230 | |||
| 231 | // Operate. | ||
| 232 | R_TRY(func(m_data_storage[0], cur_entry_phys_offset + data_offset, cur_offset, | ||
| 233 | cur_size)); | ||
| 234 | |||
| 235 | // Mark as done. | ||
| 236 | cr_info.Done(); | ||
| 237 | } | ||
| 238 | } | ||
| 239 | |||
| 240 | // Get and validate the next entry offset. | ||
| 241 | s64 next_entry_offset; | ||
| 242 | if (visitor.CanMoveNext()) { | ||
| 243 | R_TRY(visitor.MoveNext()); | ||
| 244 | next_entry_offset = visitor.Get<Entry>()->GetVirtualOffset(); | ||
| 245 | R_UNLESS(table_offsets.IsInclude(next_entry_offset), ResultInvalidIndirectEntryOffset); | ||
| 246 | } else { | ||
| 247 | next_entry_offset = table_offsets.end_offset; | ||
| 248 | } | ||
| 249 | R_UNLESS(cur_offset < next_entry_offset, ResultInvalidIndirectEntryOffset); | ||
| 250 | |||
| 251 | // Get the offset of the entry in the data we read. | ||
| 252 | const auto data_offset = cur_offset - cur_entry_offset; | ||
| 253 | const auto data_size = (next_entry_offset - cur_entry_offset); | ||
| 254 | ASSERT(data_size > 0); | ||
| 255 | |||
| 256 | // Determine how much is left. | ||
| 257 | const auto remaining_size = end_offset - cur_offset; | ||
| 258 | const auto cur_size = std::min<s64>(remaining_size, data_size - data_offset); | ||
| 259 | ASSERT(cur_size <= size); | ||
| 260 | |||
| 261 | // Operate, if we need to. | ||
| 262 | bool needs_operate; | ||
| 263 | if constexpr (!ContinuousCheck) { | ||
| 264 | needs_operate = true; | ||
| 265 | } else { | ||
| 266 | needs_operate = !cr_info.IsDone() || cur_entry.storage_index != 0; | ||
| 267 | } | ||
| 268 | |||
| 269 | if (needs_operate) { | ||
| 270 | const auto cur_entry_phys_offset = cur_entry.GetPhysicalOffset(); | ||
| 271 | |||
| 272 | if constexpr (RangeCheck) { | ||
| 273 | // Get the current data storage's size. | ||
| 274 | s64 cur_data_storage_size = m_data_storage[cur_entry.storage_index]->GetSize(); | ||
| 275 | |||
| 276 | // Ensure that we remain within range. | ||
| 277 | R_UNLESS(0 <= cur_entry_phys_offset && | ||
| 278 | cur_entry_phys_offset <= cur_data_storage_size, | ||
| 279 | ResultIndirectStorageCorrupted); | ||
| 280 | R_UNLESS(cur_entry_phys_offset + data_offset + cur_size <= cur_data_storage_size, | ||
| 281 | ResultIndirectStorageCorrupted); | ||
| 282 | } | ||
| 283 | |||
| 284 | R_TRY(func(m_data_storage[cur_entry.storage_index], cur_entry_phys_offset + data_offset, | ||
| 285 | cur_offset, cur_size)); | ||
| 286 | } | ||
| 287 | |||
| 288 | cur_offset += cur_size; | ||
| 289 | } | ||
| 290 | |||
| 291 | R_SUCCEED(); | ||
| 292 | } | ||
| 293 | |||
| 294 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "core/file_sys/fssystem/fssystem_integrity_romfs_storage.h" | ||
| 5 | |||
| 6 | namespace FileSys { | ||
| 7 | |||
| 8 | Result IntegrityRomFsStorage::Initialize( | ||
| 9 | HierarchicalIntegrityVerificationInformation level_hash_info, Hash master_hash, | ||
| 10 | HierarchicalIntegrityVerificationStorage::HierarchicalStorageInformation storage_info, | ||
| 11 | int max_data_cache_entries, int max_hash_cache_entries, s8 buffer_level) { | ||
| 12 | // Set master hash. | ||
| 13 | m_master_hash = master_hash; | ||
| 14 | m_master_hash_storage = std::make_shared<ArrayVfsFile<sizeof(Hash)>>(m_master_hash.value); | ||
| 15 | R_UNLESS(m_master_hash_storage != nullptr, | ||
| 16 | ResultAllocationMemoryFailedInIntegrityRomFsStorageA); | ||
| 17 | |||
| 18 | // Set the master hash storage. | ||
| 19 | storage_info[0] = m_master_hash_storage; | ||
| 20 | |||
| 21 | // Initialize our integrity storage. | ||
| 22 | R_RETURN(m_integrity_storage.Initialize(level_hash_info, storage_info, max_data_cache_entries, | ||
| 23 | max_hash_cache_entries, buffer_level)); | ||
| 24 | } | ||
| 25 | |||
| 26 | void IntegrityRomFsStorage::Finalize() { | ||
| 27 | m_integrity_storage.Finalize(); | ||
| 28 | } | ||
| 29 | |||
| 30 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h" | ||
| 7 | #include "core/file_sys/fssystem/fssystem_nca_header.h" | ||
| 8 | #include "core/file_sys/vfs_vector.h" | ||
| 9 | |||
| 10 | namespace FileSys { | ||
| 11 | |||
| 12 | constexpr inline size_t IntegrityLayerCountRomFs = 7; | ||
| 13 | constexpr inline size_t IntegrityHashLayerBlockSize = 16_KiB; | ||
| 14 | |||
| 15 | class IntegrityRomFsStorage : public IReadOnlyStorage { | ||
| 16 | public: | ||
| 17 | IntegrityRomFsStorage() {} | ||
| 18 | virtual ~IntegrityRomFsStorage() override { | ||
| 19 | this->Finalize(); | ||
| 20 | } | ||
| 21 | |||
| 22 | Result Initialize( | ||
| 23 | HierarchicalIntegrityVerificationInformation level_hash_info, Hash master_hash, | ||
| 24 | HierarchicalIntegrityVerificationStorage::HierarchicalStorageInformation storage_info, | ||
| 25 | int max_data_cache_entries, int max_hash_cache_entries, s8 buffer_level); | ||
| 26 | void Finalize(); | ||
| 27 | |||
| 28 | virtual size_t Read(u8* buffer, size_t size, size_t offset) const override { | ||
| 29 | return m_integrity_storage.Read(buffer, size, offset); | ||
| 30 | } | ||
| 31 | |||
| 32 | virtual size_t GetSize() const override { | ||
| 33 | return m_integrity_storage.GetSize(); | ||
| 34 | } | ||
| 35 | |||
| 36 | private: | ||
| 37 | HierarchicalIntegrityVerificationStorage m_integrity_storage; | ||
| 38 | Hash m_master_hash; | ||
| 39 | std::shared_ptr<ArrayVfsFile<sizeof(Hash)>> m_master_hash_storage; | ||
| 40 | }; | ||
| 41 | |||
| 42 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "common/alignment.h" | ||
| 5 | #include "core/file_sys/fssystem/fssystem_integrity_verification_storage.h" | ||
| 6 | |||
| 7 | namespace FileSys { | ||
| 8 | |||
| 9 | constexpr inline u32 ILog2(u32 val) { | ||
| 10 | ASSERT(val > 0); | ||
| 11 | return static_cast<u32>((sizeof(u32) * 8) - 1 - std::countl_zero<u32>(val)); | ||
| 12 | } | ||
| 13 | |||
| 14 | void IntegrityVerificationStorage::Initialize(VirtualFile hs, VirtualFile ds, s64 verif_block_size, | ||
| 15 | s64 upper_layer_verif_block_size, bool is_real_data) { | ||
| 16 | // Validate preconditions. | ||
| 17 | ASSERT(verif_block_size >= HashSize); | ||
| 18 | |||
| 19 | // Set storages. | ||
| 20 | m_hash_storage = hs; | ||
| 21 | m_data_storage = ds; | ||
| 22 | |||
| 23 | // Set verification block sizes. | ||
| 24 | m_verification_block_size = verif_block_size; | ||
| 25 | m_verification_block_order = ILog2(static_cast<u32>(verif_block_size)); | ||
| 26 | ASSERT(m_verification_block_size == 1ll << m_verification_block_order); | ||
| 27 | |||
| 28 | // Set upper layer block sizes. | ||
| 29 | upper_layer_verif_block_size = std::max(upper_layer_verif_block_size, HashSize); | ||
| 30 | m_upper_layer_verification_block_size = upper_layer_verif_block_size; | ||
| 31 | m_upper_layer_verification_block_order = ILog2(static_cast<u32>(upper_layer_verif_block_size)); | ||
| 32 | ASSERT(m_upper_layer_verification_block_size == 1ll << m_upper_layer_verification_block_order); | ||
| 33 | |||
| 34 | // Validate sizes. | ||
| 35 | { | ||
| 36 | s64 hash_size = m_hash_storage->GetSize(); | ||
| 37 | s64 data_size = m_data_storage->GetSize(); | ||
| 38 | ASSERT(((hash_size / HashSize) * m_verification_block_size) >= data_size); | ||
| 39 | } | ||
| 40 | |||
| 41 | // Set data. | ||
| 42 | m_is_real_data = is_real_data; | ||
| 43 | } | ||
| 44 | |||
| 45 | void IntegrityVerificationStorage::Finalize() { | ||
| 46 | m_hash_storage = VirtualFile(); | ||
| 47 | m_data_storage = VirtualFile(); | ||
| 48 | } | ||
| 49 | |||
| 50 | size_t IntegrityVerificationStorage::Read(u8* buffer, size_t size, size_t offset) const { | ||
| 51 | // Succeed if zero size. | ||
| 52 | if (size == 0) { | ||
| 53 | return size; | ||
| 54 | } | ||
| 55 | |||
| 56 | // Validate arguments. | ||
| 57 | ASSERT(buffer != nullptr); | ||
| 58 | |||
| 59 | // Validate the offset. | ||
| 60 | s64 data_size = m_data_storage->GetSize(); | ||
| 61 | ASSERT(offset <= static_cast<size_t>(data_size)); | ||
| 62 | |||
| 63 | // Validate the access range. | ||
| 64 | ASSERT(R_SUCCEEDED(IStorage::CheckAccessRange( | ||
| 65 | offset, size, Common::AlignUp(data_size, static_cast<size_t>(m_verification_block_size))))); | ||
| 66 | |||
| 67 | // Determine the read extents. | ||
| 68 | size_t read_size = size; | ||
| 69 | if (static_cast<s64>(offset + read_size) > data_size) { | ||
| 70 | // Determine the padding sizes. | ||
| 71 | s64 padding_offset = data_size - offset; | ||
| 72 | size_t padding_size = static_cast<size_t>( | ||
| 73 | m_verification_block_size - (padding_offset & (m_verification_block_size - 1))); | ||
| 74 | ASSERT(static_cast<s64>(padding_size) < m_verification_block_size); | ||
| 75 | |||
| 76 | // Clear the padding. | ||
| 77 | std::memset(static_cast<u8*>(buffer) + padding_offset, 0, padding_size); | ||
| 78 | |||
| 79 | // Set the new in-bounds size. | ||
| 80 | read_size = static_cast<size_t>(data_size - offset); | ||
| 81 | } | ||
| 82 | |||
| 83 | // Perform the read. | ||
| 84 | return m_data_storage->Read(buffer, read_size, offset); | ||
| 85 | } | ||
| 86 | |||
| 87 | size_t IntegrityVerificationStorage::GetSize() const { | ||
| 88 | return m_data_storage->GetSize(); | ||
| 89 | } | ||
| 90 | |||
| 91 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <optional> | ||
| 7 | |||
| 8 | #include "core/file_sys/fssystem/fs_i_storage.h" | ||
| 9 | #include "core/file_sys/fssystem/fs_types.h" | ||
| 10 | |||
| 11 | namespace FileSys { | ||
| 12 | |||
| 13 | class IntegrityVerificationStorage : public IReadOnlyStorage { | ||
| 14 | YUZU_NON_COPYABLE(IntegrityVerificationStorage); | ||
| 15 | YUZU_NON_MOVEABLE(IntegrityVerificationStorage); | ||
| 16 | |||
| 17 | public: | ||
| 18 | static constexpr s64 HashSize = 256 / 8; | ||
| 19 | |||
| 20 | struct BlockHash { | ||
| 21 | std::array<u8, HashSize> hash; | ||
| 22 | }; | ||
| 23 | static_assert(std::is_trivial_v<BlockHash>); | ||
| 24 | |||
| 25 | public: | ||
| 26 | IntegrityVerificationStorage() | ||
| 27 | : m_verification_block_size(0), m_verification_block_order(0), | ||
| 28 | m_upper_layer_verification_block_size(0), m_upper_layer_verification_block_order(0) {} | ||
| 29 | virtual ~IntegrityVerificationStorage() override { | ||
| 30 | this->Finalize(); | ||
| 31 | } | ||
| 32 | |||
| 33 | void Initialize(VirtualFile hs, VirtualFile ds, s64 verif_block_size, | ||
| 34 | s64 upper_layer_verif_block_size, bool is_real_data); | ||
| 35 | void Finalize(); | ||
| 36 | |||
| 37 | virtual size_t Read(u8* buffer, size_t size, size_t offset) const override; | ||
| 38 | virtual size_t GetSize() const override; | ||
| 39 | |||
| 40 | s64 GetBlockSize() const { | ||
| 41 | return m_verification_block_size; | ||
| 42 | } | ||
| 43 | |||
| 44 | private: | ||
| 45 | static void SetValidationBit(BlockHash* hash) { | ||
| 46 | ASSERT(hash != nullptr); | ||
| 47 | hash->hash[HashSize - 1] |= 0x80; | ||
| 48 | } | ||
| 49 | |||
| 50 | static bool IsValidationBit(const BlockHash* hash) { | ||
| 51 | ASSERT(hash != nullptr); | ||
| 52 | return (hash->hash[HashSize - 1] & 0x80) != 0; | ||
| 53 | } | ||
| 54 | |||
| 55 | private: | ||
| 56 | VirtualFile m_hash_storage; | ||
| 57 | VirtualFile m_data_storage; | ||
| 58 | s64 m_verification_block_size; | ||
| 59 | s64 m_verification_block_order; | ||
| 60 | s64 m_upper_layer_verification_block_size; | ||
| 61 | s64 m_upper_layer_verification_block_order; | ||
| 62 | bool m_is_real_data; | ||
| 63 | }; | ||
| 64 | |||
| 65 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "core/file_sys/fssystem/fs_i_storage.h" | ||
| 7 | |||
| 8 | namespace FileSys { | ||
| 9 | |||
| 10 | class MemoryResourceBufferHoldStorage : public IStorage { | ||
| 11 | YUZU_NON_COPYABLE(MemoryResourceBufferHoldStorage); | ||
| 12 | YUZU_NON_MOVEABLE(MemoryResourceBufferHoldStorage); | ||
| 13 | |||
| 14 | public: | ||
| 15 | MemoryResourceBufferHoldStorage(VirtualFile storage, size_t buffer_size) | ||
| 16 | : m_storage(std::move(storage)), m_buffer(::operator new(buffer_size)), | ||
| 17 | m_buffer_size(buffer_size) {} | ||
| 18 | |||
| 19 | virtual ~MemoryResourceBufferHoldStorage() { | ||
| 20 | // If we have a buffer, deallocate it. | ||
| 21 | if (m_buffer != nullptr) { | ||
| 22 | ::operator delete(m_buffer); | ||
| 23 | } | ||
| 24 | } | ||
| 25 | |||
| 26 | bool IsValid() const { | ||
| 27 | return m_buffer != nullptr; | ||
| 28 | } | ||
| 29 | void* GetBuffer() const { | ||
| 30 | return m_buffer; | ||
| 31 | } | ||
| 32 | |||
| 33 | public: | ||
| 34 | virtual size_t Read(u8* buffer, size_t size, size_t offset) const override { | ||
| 35 | // Check pre-conditions. | ||
| 36 | ASSERT(m_storage != nullptr); | ||
| 37 | |||
| 38 | return m_storage->Read(buffer, size, offset); | ||
| 39 | } | ||
| 40 | |||
| 41 | virtual size_t GetSize() const override { | ||
| 42 | // Check pre-conditions. | ||
| 43 | ASSERT(m_storage != nullptr); | ||
| 44 | |||
| 45 | return m_storage->GetSize(); | ||
| 46 | } | ||
| 47 | |||
| 48 | virtual size_t Write(const u8* buffer, size_t size, size_t offset) override { | ||
| 49 | // Check pre-conditions. | ||
| 50 | ASSERT(m_storage != nullptr); | ||
| 51 | |||
| 52 | return m_storage->Write(buffer, size, offset); | ||
| 53 | } | ||
| 54 | |||
| 55 | private: | ||
| 56 | VirtualFile m_storage; | ||
| 57 | void* m_buffer; | ||
| 58 | size_t m_buffer_size; | ||
| 59 | }; | ||
| 60 | |||
| 61 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.h" | ||
| 5 | #include "core/file_sys/fssystem/fssystem_aes_ctr_storage.h" | ||
| 6 | #include "core/file_sys/fssystem/fssystem_aes_xts_storage.h" | ||
| 7 | #include "core/file_sys/fssystem/fssystem_alignment_matching_storage.h" | ||
| 8 | #include "core/file_sys/fssystem/fssystem_compressed_storage.h" | ||
| 9 | #include "core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h" | ||
| 10 | #include "core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.h" | ||
| 11 | #include "core/file_sys/fssystem/fssystem_indirect_storage.h" | ||
| 12 | #include "core/file_sys/fssystem/fssystem_integrity_romfs_storage.h" | ||
| 13 | #include "core/file_sys/fssystem/fssystem_memory_resource_buffer_hold_storage.h" | ||
| 14 | #include "core/file_sys/fssystem/fssystem_nca_file_system_driver.h" | ||
| 15 | #include "core/file_sys/fssystem/fssystem_sparse_storage.h" | ||
| 16 | #include "core/file_sys/fssystem/fssystem_switch_storage.h" | ||
| 17 | #include "core/file_sys/vfs_offset.h" | ||
| 18 | #include "core/file_sys/vfs_vector.h" | ||
| 19 | |||
| 20 | namespace FileSys { | ||
| 21 | |||
| 22 | namespace { | ||
| 23 | |||
| 24 | constexpr inline s32 IntegrityDataCacheCount = 24; | ||
| 25 | constexpr inline s32 IntegrityHashCacheCount = 8; | ||
| 26 | |||
| 27 | constexpr inline s32 IntegrityDataCacheCountForMeta = 16; | ||
| 28 | constexpr inline s32 IntegrityHashCacheCountForMeta = 2; | ||
| 29 | |||
| 30 | class SharedNcaBodyStorage : public IReadOnlyStorage { | ||
| 31 | YUZU_NON_COPYABLE(SharedNcaBodyStorage); | ||
| 32 | YUZU_NON_MOVEABLE(SharedNcaBodyStorage); | ||
| 33 | |||
| 34 | private: | ||
| 35 | VirtualFile m_storage; | ||
| 36 | std::shared_ptr<NcaReader> m_nca_reader; | ||
| 37 | |||
| 38 | public: | ||
| 39 | SharedNcaBodyStorage(VirtualFile s, std::shared_ptr<NcaReader> r) | ||
| 40 | : m_storage(std::move(s)), m_nca_reader(std::move(r)) {} | ||
| 41 | |||
| 42 | virtual size_t Read(u8* buffer, size_t size, size_t offset) const override { | ||
| 43 | // Validate pre-conditions. | ||
| 44 | ASSERT(m_storage != nullptr); | ||
| 45 | |||
| 46 | // Read from the base storage. | ||
| 47 | return m_storage->Read(buffer, size, offset); | ||
| 48 | } | ||
| 49 | |||
| 50 | virtual size_t GetSize() const override { | ||
| 51 | // Validate pre-conditions. | ||
| 52 | ASSERT(m_storage != nullptr); | ||
| 53 | |||
| 54 | return m_storage->GetSize(); | ||
| 55 | } | ||
| 56 | }; | ||
| 57 | |||
| 58 | inline s64 GetFsOffset(const NcaReader& reader, s32 fs_index) { | ||
| 59 | return static_cast<s64>(reader.GetFsOffset(fs_index)); | ||
| 60 | } | ||
| 61 | |||
| 62 | inline s64 GetFsEndOffset(const NcaReader& reader, s32 fs_index) { | ||
| 63 | return static_cast<s64>(reader.GetFsEndOffset(fs_index)); | ||
| 64 | } | ||
| 65 | |||
| 66 | using Sha256DataRegion = NcaFsHeader::Region; | ||
| 67 | using IntegrityLevelInfo = NcaFsHeader::HashData::IntegrityMetaInfo::LevelHashInfo; | ||
| 68 | using IntegrityDataInfo = IntegrityLevelInfo::HierarchicalIntegrityVerificationLevelInformation; | ||
| 69 | |||
| 70 | } // namespace | ||
| 71 | |||
| 72 | Result NcaFileSystemDriver::OpenStorageWithContext(VirtualFile* out, | ||
| 73 | NcaFsHeaderReader* out_header_reader, | ||
| 74 | s32 fs_index, StorageContext* ctx) { | ||
| 75 | // Open storage. | ||
| 76 | R_RETURN(this->OpenStorageImpl(out, out_header_reader, fs_index, ctx)); | ||
| 77 | } | ||
| 78 | |||
| 79 | Result NcaFileSystemDriver::OpenStorageImpl(VirtualFile* out, NcaFsHeaderReader* out_header_reader, | ||
| 80 | s32 fs_index, StorageContext* ctx) { | ||
| 81 | // Validate preconditions. | ||
| 82 | ASSERT(out != nullptr); | ||
| 83 | ASSERT(out_header_reader != nullptr); | ||
| 84 | ASSERT(0 <= fs_index && fs_index < NcaHeader::FsCountMax); | ||
| 85 | |||
| 86 | // Validate the fs index. | ||
| 87 | R_UNLESS(m_reader->HasFsInfo(fs_index), ResultPartitionNotFound); | ||
| 88 | |||
| 89 | // Initialize our header reader for the fs index. | ||
| 90 | R_TRY(out_header_reader->Initialize(*m_reader, fs_index)); | ||
| 91 | |||
| 92 | // Declare the storage we're opening. | ||
| 93 | VirtualFile storage; | ||
| 94 | |||
| 95 | // Process sparse layer. | ||
| 96 | s64 fs_data_offset = 0; | ||
| 97 | if (out_header_reader->ExistsSparseLayer()) { | ||
| 98 | // Get the sparse info. | ||
| 99 | const auto& sparse_info = out_header_reader->GetSparseInfo(); | ||
| 100 | |||
| 101 | // Create based on whether we have a meta hash layer. | ||
| 102 | if (out_header_reader->ExistsSparseMetaHashLayer()) { | ||
| 103 | // Create the sparse storage with verification. | ||
| 104 | R_TRY(this->CreateSparseStorageWithVerification( | ||
| 105 | std::addressof(storage), std::addressof(fs_data_offset), | ||
| 106 | ctx != nullptr ? std::addressof(ctx->current_sparse_storage) : nullptr, | ||
| 107 | ctx != nullptr ? std::addressof(ctx->sparse_storage_meta_storage) : nullptr, | ||
| 108 | ctx != nullptr ? std::addressof(ctx->sparse_layer_info_storage) : nullptr, fs_index, | ||
| 109 | out_header_reader->GetAesCtrUpperIv(), sparse_info, | ||
| 110 | out_header_reader->GetSparseMetaDataHashDataInfo(), | ||
| 111 | out_header_reader->GetSparseMetaHashType())); | ||
| 112 | } else { | ||
| 113 | // Create the sparse storage. | ||
| 114 | R_TRY(this->CreateSparseStorage( | ||
| 115 | std::addressof(storage), std::addressof(fs_data_offset), | ||
| 116 | ctx != nullptr ? std::addressof(ctx->current_sparse_storage) : nullptr, | ||
| 117 | ctx != nullptr ? std::addressof(ctx->sparse_storage_meta_storage) : nullptr, | ||
| 118 | fs_index, out_header_reader->GetAesCtrUpperIv(), sparse_info)); | ||
| 119 | } | ||
| 120 | } else { | ||
| 121 | // Get the data offsets. | ||
| 122 | fs_data_offset = GetFsOffset(*m_reader, fs_index); | ||
| 123 | const auto fs_end_offset = GetFsEndOffset(*m_reader, fs_index); | ||
| 124 | |||
| 125 | // Validate that we're within range. | ||
| 126 | const auto data_size = fs_end_offset - fs_data_offset; | ||
| 127 | R_UNLESS(data_size > 0, ResultInvalidNcaHeader); | ||
| 128 | |||
| 129 | // Create the body substorage. | ||
| 130 | R_TRY(this->CreateBodySubStorage(std::addressof(storage), fs_data_offset, data_size)); | ||
| 131 | |||
| 132 | // Potentially save the body substorage to our context. | ||
| 133 | if (ctx != nullptr) { | ||
| 134 | ctx->body_substorage = storage; | ||
| 135 | } | ||
| 136 | } | ||
| 137 | |||
| 138 | // Process patch layer. | ||
| 139 | const auto& patch_info = out_header_reader->GetPatchInfo(); | ||
| 140 | VirtualFile patch_meta_aes_ctr_ex_meta_storage; | ||
| 141 | VirtualFile patch_meta_indirect_meta_storage; | ||
| 142 | if (out_header_reader->ExistsPatchMetaHashLayer()) { | ||
| 143 | // Check the meta hash type. | ||
| 144 | R_UNLESS(out_header_reader->GetPatchMetaHashType() == | ||
| 145 | NcaFsHeader::MetaDataHashType::HierarchicalIntegrity, | ||
| 146 | ResultRomNcaInvalidPatchMetaDataHashType); | ||
| 147 | |||
| 148 | // Create the patch meta storage. | ||
| 149 | R_TRY(this->CreatePatchMetaStorage( | ||
| 150 | std::addressof(patch_meta_aes_ctr_ex_meta_storage), | ||
| 151 | std::addressof(patch_meta_indirect_meta_storage), | ||
| 152 | ctx != nullptr ? std::addressof(ctx->patch_layer_info_storage) : nullptr, storage, | ||
| 153 | fs_data_offset, out_header_reader->GetAesCtrUpperIv(), patch_info, | ||
| 154 | out_header_reader->GetPatchMetaDataHashDataInfo())); | ||
| 155 | } | ||
| 156 | |||
| 157 | if (patch_info.HasAesCtrExTable()) { | ||
| 158 | // Check the encryption type. | ||
| 159 | ASSERT(out_header_reader->GetEncryptionType() == NcaFsHeader::EncryptionType::None || | ||
| 160 | out_header_reader->GetEncryptionType() == NcaFsHeader::EncryptionType::AesCtrEx || | ||
| 161 | out_header_reader->GetEncryptionType() == | ||
| 162 | NcaFsHeader::EncryptionType::AesCtrExSkipLayerHash); | ||
| 163 | |||
| 164 | // Create the ex meta storage. | ||
| 165 | VirtualFile aes_ctr_ex_storage_meta_storage = patch_meta_aes_ctr_ex_meta_storage; | ||
| 166 | if (aes_ctr_ex_storage_meta_storage == nullptr) { | ||
| 167 | // If we don't have a meta storage, we must not have a patch meta hash layer. | ||
| 168 | ASSERT(!out_header_reader->ExistsPatchMetaHashLayer()); | ||
| 169 | |||
| 170 | R_TRY(this->CreateAesCtrExStorageMetaStorage( | ||
| 171 | std::addressof(aes_ctr_ex_storage_meta_storage), storage, fs_data_offset, | ||
| 172 | out_header_reader->GetEncryptionType(), out_header_reader->GetAesCtrUpperIv(), | ||
| 173 | patch_info)); | ||
| 174 | } | ||
| 175 | |||
| 176 | // Create the ex storage. | ||
| 177 | VirtualFile aes_ctr_ex_storage; | ||
| 178 | R_TRY(this->CreateAesCtrExStorage( | ||
| 179 | std::addressof(aes_ctr_ex_storage), | ||
| 180 | ctx != nullptr ? std::addressof(ctx->aes_ctr_ex_storage) : nullptr, std::move(storage), | ||
| 181 | aes_ctr_ex_storage_meta_storage, fs_data_offset, out_header_reader->GetAesCtrUpperIv(), | ||
| 182 | patch_info)); | ||
| 183 | |||
| 184 | // Set the base storage as the ex storage. | ||
| 185 | storage = std::move(aes_ctr_ex_storage); | ||
| 186 | |||
| 187 | // Potentially save storages to our context. | ||
| 188 | if (ctx != nullptr) { | ||
| 189 | ctx->aes_ctr_ex_storage_meta_storage = aes_ctr_ex_storage_meta_storage; | ||
| 190 | ctx->aes_ctr_ex_storage_data_storage = storage; | ||
| 191 | ctx->fs_data_storage = storage; | ||
| 192 | } | ||
| 193 | } else { | ||
| 194 | // Create the appropriate storage for the encryption type. | ||
| 195 | switch (out_header_reader->GetEncryptionType()) { | ||
| 196 | case NcaFsHeader::EncryptionType::None: | ||
| 197 | // If there's no encryption, use the base storage we made previously. | ||
| 198 | break; | ||
| 199 | case NcaFsHeader::EncryptionType::AesXts: | ||
| 200 | R_TRY(this->CreateAesXtsStorage(std::addressof(storage), std::move(storage), | ||
| 201 | fs_data_offset)); | ||
| 202 | break; | ||
| 203 | case NcaFsHeader::EncryptionType::AesCtr: | ||
| 204 | R_TRY(this->CreateAesCtrStorage(std::addressof(storage), std::move(storage), | ||
| 205 | fs_data_offset, out_header_reader->GetAesCtrUpperIv(), | ||
| 206 | AlignmentStorageRequirement::None)); | ||
| 207 | break; | ||
| 208 | case NcaFsHeader::EncryptionType::AesCtrSkipLayerHash: { | ||
| 209 | // Create the aes ctr storage. | ||
| 210 | VirtualFile aes_ctr_storage; | ||
| 211 | R_TRY(this->CreateAesCtrStorage(std::addressof(aes_ctr_storage), storage, | ||
| 212 | fs_data_offset, out_header_reader->GetAesCtrUpperIv(), | ||
| 213 | AlignmentStorageRequirement::None)); | ||
| 214 | |||
| 215 | // Create region switch storage. | ||
| 216 | R_TRY(this->CreateRegionSwitchStorage(std::addressof(storage), out_header_reader, | ||
| 217 | std::move(storage), std::move(aes_ctr_storage))); | ||
| 218 | } break; | ||
| 219 | default: | ||
| 220 | R_THROW(ResultInvalidNcaFsHeaderEncryptionType); | ||
| 221 | } | ||
| 222 | |||
| 223 | // Potentially save storages to our context. | ||
| 224 | if (ctx != nullptr) { | ||
| 225 | ctx->fs_data_storage = storage; | ||
| 226 | } | ||
| 227 | } | ||
| 228 | |||
| 229 | // Process indirect layer. | ||
| 230 | if (patch_info.HasIndirectTable()) { | ||
| 231 | // Create the indirect meta storage. | ||
| 232 | VirtualFile indirect_storage_meta_storage = patch_meta_indirect_meta_storage; | ||
| 233 | if (indirect_storage_meta_storage == nullptr) { | ||
| 234 | // If we don't have a meta storage, we must not have a patch meta hash layer. | ||
| 235 | ASSERT(!out_header_reader->ExistsPatchMetaHashLayer()); | ||
| 236 | |||
| 237 | R_TRY(this->CreateIndirectStorageMetaStorage( | ||
| 238 | std::addressof(indirect_storage_meta_storage), storage, patch_info)); | ||
| 239 | } | ||
| 240 | |||
| 241 | // Potentially save the indirect meta storage to our context. | ||
| 242 | if (ctx != nullptr) { | ||
| 243 | ctx->indirect_storage_meta_storage = indirect_storage_meta_storage; | ||
| 244 | } | ||
| 245 | |||
| 246 | // Get the original indirectable storage. | ||
| 247 | VirtualFile original_indirectable_storage; | ||
| 248 | if (m_original_reader != nullptr && m_original_reader->HasFsInfo(fs_index)) { | ||
| 249 | // Create a driver for the original. | ||
| 250 | NcaFileSystemDriver original_driver(m_original_reader); | ||
| 251 | |||
| 252 | // Create a header reader for the original. | ||
| 253 | NcaFsHeaderReader original_header_reader; | ||
| 254 | R_TRY(original_header_reader.Initialize(*m_original_reader, fs_index)); | ||
| 255 | |||
| 256 | // Open original indirectable storage. | ||
| 257 | R_TRY(original_driver.OpenIndirectableStorageAsOriginal( | ||
| 258 | std::addressof(original_indirectable_storage), | ||
| 259 | std::addressof(original_header_reader), ctx)); | ||
| 260 | } else if (ctx != nullptr && ctx->external_original_storage != nullptr) { | ||
| 261 | // Use the external original storage. | ||
| 262 | original_indirectable_storage = ctx->external_original_storage; | ||
| 263 | } else { | ||
| 264 | // Allocate a dummy memory storage as original storage. | ||
| 265 | original_indirectable_storage = std::make_shared<VectorVfsFile>(); | ||
| 266 | R_UNLESS(original_indirectable_storage != nullptr, | ||
| 267 | ResultAllocationMemoryFailedAllocateShared); | ||
| 268 | } | ||
| 269 | |||
| 270 | // Create the indirect storage. | ||
| 271 | VirtualFile indirect_storage; | ||
| 272 | R_TRY(this->CreateIndirectStorage( | ||
| 273 | std::addressof(indirect_storage), | ||
| 274 | ctx != nullptr ? std::addressof(ctx->indirect_storage) : nullptr, std::move(storage), | ||
| 275 | std::move(original_indirectable_storage), std::move(indirect_storage_meta_storage), | ||
| 276 | patch_info)); | ||
| 277 | |||
| 278 | // Set storage as the indirect storage. | ||
| 279 | storage = std::move(indirect_storage); | ||
| 280 | } | ||
| 281 | |||
| 282 | // Check if we're sparse or requested to skip the integrity layer. | ||
| 283 | if (out_header_reader->ExistsSparseLayer() || (ctx != nullptr && ctx->open_raw_storage)) { | ||
| 284 | *out = std::move(storage); | ||
| 285 | R_SUCCEED(); | ||
| 286 | } | ||
| 287 | |||
| 288 | // Create the non-raw storage. | ||
| 289 | R_RETURN(this->CreateStorageByRawStorage(out, out_header_reader, std::move(storage), ctx)); | ||
| 290 | } | ||
| 291 | |||
| 292 | Result NcaFileSystemDriver::CreateStorageByRawStorage(VirtualFile* out, | ||
| 293 | const NcaFsHeaderReader* header_reader, | ||
| 294 | VirtualFile raw_storage, | ||
| 295 | StorageContext* ctx) { | ||
| 296 | // Initialize storage as raw storage. | ||
| 297 | VirtualFile storage = std::move(raw_storage); | ||
| 298 | |||
| 299 | // Process hash/integrity layer. | ||
| 300 | switch (header_reader->GetHashType()) { | ||
| 301 | case NcaFsHeader::HashType::HierarchicalSha256Hash: | ||
| 302 | R_TRY(this->CreateSha256Storage(std::addressof(storage), std::move(storage), | ||
| 303 | header_reader->GetHashData().hierarchical_sha256_data)); | ||
| 304 | break; | ||
| 305 | case NcaFsHeader::HashType::HierarchicalIntegrityHash: | ||
| 306 | R_TRY(this->CreateIntegrityVerificationStorage( | ||
| 307 | std::addressof(storage), std::move(storage), | ||
| 308 | header_reader->GetHashData().integrity_meta_info)); | ||
| 309 | break; | ||
| 310 | default: | ||
| 311 | R_THROW(ResultInvalidNcaFsHeaderHashType); | ||
| 312 | } | ||
| 313 | |||
| 314 | // Process compression layer. | ||
| 315 | if (header_reader->ExistsCompressionLayer()) { | ||
| 316 | R_TRY(this->CreateCompressedStorage( | ||
| 317 | std::addressof(storage), | ||
| 318 | ctx != nullptr ? std::addressof(ctx->compressed_storage) : nullptr, | ||
| 319 | ctx != nullptr ? std::addressof(ctx->compressed_storage_meta_storage) : nullptr, | ||
| 320 | std::move(storage), header_reader->GetCompressionInfo())); | ||
| 321 | } | ||
| 322 | |||
| 323 | // Set output storage. | ||
| 324 | *out = std::move(storage); | ||
| 325 | R_SUCCEED(); | ||
| 326 | } | ||
| 327 | |||
| 328 | Result NcaFileSystemDriver::OpenIndirectableStorageAsOriginal( | ||
| 329 | VirtualFile* out, const NcaFsHeaderReader* header_reader, StorageContext* ctx) { | ||
| 330 | // Get the fs index. | ||
| 331 | const auto fs_index = header_reader->GetFsIndex(); | ||
| 332 | |||
| 333 | // Declare the storage we're opening. | ||
| 334 | VirtualFile storage; | ||
| 335 | |||
| 336 | // Process sparse layer. | ||
| 337 | s64 fs_data_offset = 0; | ||
| 338 | if (header_reader->ExistsSparseLayer()) { | ||
| 339 | // Get the sparse info. | ||
| 340 | const auto& sparse_info = header_reader->GetSparseInfo(); | ||
| 341 | |||
| 342 | // Create based on whether we have a meta hash layer. | ||
| 343 | if (header_reader->ExistsSparseMetaHashLayer()) { | ||
| 344 | // Create the sparse storage with verification. | ||
| 345 | R_TRY(this->CreateSparseStorageWithVerification( | ||
| 346 | std::addressof(storage), std::addressof(fs_data_offset), | ||
| 347 | ctx != nullptr ? std::addressof(ctx->original_sparse_storage) : nullptr, | ||
| 348 | ctx != nullptr ? std::addressof(ctx->sparse_storage_meta_storage) : nullptr, | ||
| 349 | ctx != nullptr ? std::addressof(ctx->sparse_layer_info_storage) : nullptr, fs_index, | ||
| 350 | header_reader->GetAesCtrUpperIv(), sparse_info, | ||
| 351 | header_reader->GetSparseMetaDataHashDataInfo(), | ||
| 352 | header_reader->GetSparseMetaHashType())); | ||
| 353 | } else { | ||
| 354 | // Create the sparse storage. | ||
| 355 | R_TRY(this->CreateSparseStorage( | ||
| 356 | std::addressof(storage), std::addressof(fs_data_offset), | ||
| 357 | ctx != nullptr ? std::addressof(ctx->original_sparse_storage) : nullptr, | ||
| 358 | ctx != nullptr ? std::addressof(ctx->sparse_storage_meta_storage) : nullptr, | ||
| 359 | fs_index, header_reader->GetAesCtrUpperIv(), sparse_info)); | ||
| 360 | } | ||
| 361 | } else { | ||
| 362 | // Get the data offsets. | ||
| 363 | fs_data_offset = GetFsOffset(*m_reader, fs_index); | ||
| 364 | const auto fs_end_offset = GetFsEndOffset(*m_reader, fs_index); | ||
| 365 | |||
| 366 | // Validate that we're within range. | ||
| 367 | const auto data_size = fs_end_offset - fs_data_offset; | ||
| 368 | R_UNLESS(data_size > 0, ResultInvalidNcaHeader); | ||
| 369 | |||
| 370 | // Create the body substorage. | ||
| 371 | R_TRY(this->CreateBodySubStorage(std::addressof(storage), fs_data_offset, data_size)); | ||
| 372 | } | ||
| 373 | |||
| 374 | // Create the appropriate storage for the encryption type. | ||
| 375 | switch (header_reader->GetEncryptionType()) { | ||
| 376 | case NcaFsHeader::EncryptionType::None: | ||
| 377 | // If there's no encryption, use the base storage we made previously. | ||
| 378 | break; | ||
| 379 | case NcaFsHeader::EncryptionType::AesXts: | ||
| 380 | R_TRY( | ||
| 381 | this->CreateAesXtsStorage(std::addressof(storage), std::move(storage), fs_data_offset)); | ||
| 382 | break; | ||
| 383 | case NcaFsHeader::EncryptionType::AesCtr: | ||
| 384 | R_TRY(this->CreateAesCtrStorage(std::addressof(storage), std::move(storage), fs_data_offset, | ||
| 385 | header_reader->GetAesCtrUpperIv(), | ||
| 386 | AlignmentStorageRequirement::CacheBlockSize)); | ||
| 387 | break; | ||
| 388 | default: | ||
| 389 | R_THROW(ResultInvalidNcaFsHeaderEncryptionType); | ||
| 390 | } | ||
| 391 | |||
| 392 | // Set output storage. | ||
| 393 | *out = std::move(storage); | ||
| 394 | R_SUCCEED(); | ||
| 395 | } | ||
| 396 | |||
| 397 | Result NcaFileSystemDriver::CreateBodySubStorage(VirtualFile* out, s64 offset, s64 size) { | ||
| 398 | // Create the body storage. | ||
| 399 | auto body_storage = | ||
| 400 | std::make_shared<SharedNcaBodyStorage>(m_reader->GetSharedBodyStorage(), m_reader); | ||
| 401 | R_UNLESS(body_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 402 | |||
| 403 | // Get the body storage size. | ||
| 404 | s64 body_size = body_storage->GetSize(); | ||
| 405 | |||
| 406 | // Check that we're within range. | ||
| 407 | R_UNLESS(offset + size <= body_size, ResultNcaBaseStorageOutOfRangeB); | ||
| 408 | |||
| 409 | // Create substorage. | ||
| 410 | auto body_substorage = std::make_shared<OffsetVfsFile>(std::move(body_storage), size, offset); | ||
| 411 | R_UNLESS(body_substorage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 412 | |||
| 413 | // Set the output storage. | ||
| 414 | *out = std::move(body_substorage); | ||
| 415 | R_SUCCEED(); | ||
| 416 | } | ||
| 417 | |||
| 418 | Result NcaFileSystemDriver::CreateAesCtrStorage( | ||
| 419 | VirtualFile* out, VirtualFile base_storage, s64 offset, const NcaAesCtrUpperIv& upper_iv, | ||
| 420 | AlignmentStorageRequirement alignment_storage_requirement) { | ||
| 421 | // Check pre-conditions. | ||
| 422 | ASSERT(out != nullptr); | ||
| 423 | ASSERT(base_storage != nullptr); | ||
| 424 | |||
| 425 | // Create the iv. | ||
| 426 | std::array<u8, AesCtrStorage::IvSize> iv{}; | ||
| 427 | AesCtrStorage::MakeIv(iv.data(), sizeof(iv), upper_iv.value, offset); | ||
| 428 | |||
| 429 | // Create the ctr storage. | ||
| 430 | VirtualFile aes_ctr_storage; | ||
| 431 | if (m_reader->HasExternalDecryptionKey()) { | ||
| 432 | aes_ctr_storage = std::make_shared<AesCtrStorage>( | ||
| 433 | std::move(base_storage), m_reader->GetExternalDecryptionKey(), AesCtrStorage::KeySize, | ||
| 434 | iv.data(), AesCtrStorage::IvSize); | ||
| 435 | R_UNLESS(aes_ctr_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 436 | } else { | ||
| 437 | // Create software decryption storage. | ||
| 438 | auto sw_storage = std::make_shared<AesCtrStorage>( | ||
| 439 | base_storage, m_reader->GetDecryptionKey(NcaHeader::DecryptionKey_AesCtr), | ||
| 440 | AesCtrStorage::KeySize, iv.data(), AesCtrStorage::IvSize); | ||
| 441 | R_UNLESS(sw_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 442 | |||
| 443 | aes_ctr_storage = std::move(sw_storage); | ||
| 444 | } | ||
| 445 | |||
| 446 | // Create alignment matching storage. | ||
| 447 | auto aligned_storage = std::make_shared<AlignmentMatchingStorage<NcaHeader::CtrBlockSize, 1>>( | ||
| 448 | std::move(aes_ctr_storage)); | ||
| 449 | R_UNLESS(aligned_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 450 | |||
| 451 | // Set the out storage. | ||
| 452 | *out = std::move(aligned_storage); | ||
| 453 | R_SUCCEED(); | ||
| 454 | } | ||
| 455 | |||
| 456 | Result NcaFileSystemDriver::CreateAesXtsStorage(VirtualFile* out, VirtualFile base_storage, | ||
| 457 | s64 offset) { | ||
| 458 | // Check pre-conditions. | ||
| 459 | ASSERT(out != nullptr); | ||
| 460 | ASSERT(base_storage != nullptr); | ||
| 461 | |||
| 462 | // Create the iv. | ||
| 463 | std::array<u8, AesXtsStorage::IvSize> iv{}; | ||
| 464 | AesXtsStorage::MakeAesXtsIv(iv.data(), sizeof(iv), offset, NcaHeader::XtsBlockSize); | ||
| 465 | |||
| 466 | // Make the aes xts storage. | ||
| 467 | const auto* const key1 = m_reader->GetDecryptionKey(NcaHeader::DecryptionKey_AesXts1); | ||
| 468 | const auto* const key2 = m_reader->GetDecryptionKey(NcaHeader::DecryptionKey_AesXts2); | ||
| 469 | auto xts_storage = | ||
| 470 | std::make_shared<AesXtsStorage>(std::move(base_storage), key1, key2, AesXtsStorage::KeySize, | ||
| 471 | iv.data(), AesXtsStorage::IvSize, NcaHeader::XtsBlockSize); | ||
| 472 | R_UNLESS(xts_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 473 | |||
| 474 | // Create alignment matching storage. | ||
| 475 | auto aligned_storage = std::make_shared<AlignmentMatchingStorage<NcaHeader::XtsBlockSize, 1>>( | ||
| 476 | std::move(xts_storage)); | ||
| 477 | R_UNLESS(aligned_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 478 | |||
| 479 | // Set the out storage. | ||
| 480 | *out = std::move(xts_storage); | ||
| 481 | R_SUCCEED(); | ||
| 482 | } | ||
| 483 | |||
| 484 | Result NcaFileSystemDriver::CreateSparseStorageMetaStorage(VirtualFile* out, | ||
| 485 | VirtualFile base_storage, s64 offset, | ||
| 486 | const NcaAesCtrUpperIv& upper_iv, | ||
| 487 | const NcaSparseInfo& sparse_info) { | ||
| 488 | // Validate preconditions. | ||
| 489 | ASSERT(out != nullptr); | ||
| 490 | ASSERT(base_storage != nullptr); | ||
| 491 | |||
| 492 | // Get the base storage size. | ||
| 493 | s64 base_size = base_storage->GetSize(); | ||
| 494 | |||
| 495 | // Get the meta extents. | ||
| 496 | const auto meta_offset = sparse_info.bucket.offset; | ||
| 497 | const auto meta_size = sparse_info.bucket.size; | ||
| 498 | R_UNLESS(meta_offset + meta_size - offset <= base_size, ResultNcaBaseStorageOutOfRangeB); | ||
| 499 | |||
| 500 | // Create the encrypted storage. | ||
| 501 | auto enc_storage = | ||
| 502 | std::make_shared<OffsetVfsFile>(std::move(base_storage), meta_size, meta_offset); | ||
| 503 | R_UNLESS(enc_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 504 | |||
| 505 | // Create the decrypted storage. | ||
| 506 | VirtualFile decrypted_storage; | ||
| 507 | R_TRY(this->CreateAesCtrStorage(std::addressof(decrypted_storage), std::move(enc_storage), | ||
| 508 | offset + meta_offset, sparse_info.MakeAesCtrUpperIv(upper_iv), | ||
| 509 | AlignmentStorageRequirement::None)); | ||
| 510 | |||
| 511 | // Create buffered storage. | ||
| 512 | std::vector<u8> meta_data(meta_size); | ||
| 513 | decrypted_storage->Read(meta_data.data(), meta_size, 0); | ||
| 514 | |||
| 515 | auto buffered_storage = std::make_shared<VectorVfsFile>(std::move(meta_data)); | ||
| 516 | R_UNLESS(buffered_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 517 | |||
| 518 | // Set the output. | ||
| 519 | *out = std::move(buffered_storage); | ||
| 520 | R_SUCCEED(); | ||
| 521 | } | ||
| 522 | |||
| 523 | Result NcaFileSystemDriver::CreateSparseStorageCore(std::shared_ptr<SparseStorage>* out, | ||
| 524 | VirtualFile base_storage, s64 base_size, | ||
| 525 | VirtualFile meta_storage, | ||
| 526 | const NcaSparseInfo& sparse_info, | ||
| 527 | bool external_info) { | ||
| 528 | // Validate preconditions. | ||
| 529 | ASSERT(out != nullptr); | ||
| 530 | ASSERT(base_storage != nullptr); | ||
| 531 | ASSERT(meta_storage != nullptr); | ||
| 532 | |||
| 533 | // Read and verify the bucket tree header. | ||
| 534 | BucketTree::Header header; | ||
| 535 | std::memcpy(std::addressof(header), sparse_info.bucket.header.data(), sizeof(header)); | ||
| 536 | R_TRY(header.Verify()); | ||
| 537 | |||
| 538 | // Determine storage extents. | ||
| 539 | const auto node_offset = 0; | ||
| 540 | const auto node_size = SparseStorage::QueryNodeStorageSize(header.entry_count); | ||
| 541 | const auto entry_offset = node_offset + node_size; | ||
| 542 | const auto entry_size = SparseStorage::QueryEntryStorageSize(header.entry_count); | ||
| 543 | |||
| 544 | // Create the sparse storage. | ||
| 545 | auto sparse_storage = std::make_shared<SparseStorage>(); | ||
| 546 | R_UNLESS(sparse_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 547 | |||
| 548 | // Sanity check that we can be doing this. | ||
| 549 | ASSERT(header.entry_count != 0); | ||
| 550 | |||
| 551 | // Initialize the sparse storage. | ||
| 552 | R_TRY(sparse_storage->Initialize( | ||
| 553 | std::make_shared<OffsetVfsFile>(meta_storage, node_size, node_offset), | ||
| 554 | std::make_shared<OffsetVfsFile>(meta_storage, entry_size, entry_offset), | ||
| 555 | header.entry_count)); | ||
| 556 | |||
| 557 | // If not external, set the data storage. | ||
| 558 | if (!external_info) { | ||
| 559 | sparse_storage->SetDataStorage( | ||
| 560 | std::make_shared<OffsetVfsFile>(std::move(base_storage), base_size, 0)); | ||
| 561 | } | ||
| 562 | |||
| 563 | // Set the output. | ||
| 564 | *out = std::move(sparse_storage); | ||
| 565 | R_SUCCEED(); | ||
| 566 | } | ||
| 567 | |||
| 568 | Result NcaFileSystemDriver::CreateSparseStorage(VirtualFile* out, s64* out_fs_data_offset, | ||
| 569 | std::shared_ptr<SparseStorage>* out_sparse_storage, | ||
| 570 | VirtualFile* out_meta_storage, s32 index, | ||
| 571 | const NcaAesCtrUpperIv& upper_iv, | ||
| 572 | const NcaSparseInfo& sparse_info) { | ||
| 573 | // Validate preconditions. | ||
| 574 | ASSERT(out != nullptr); | ||
| 575 | ASSERT(out_fs_data_offset != nullptr); | ||
| 576 | |||
| 577 | // Check the sparse info generation. | ||
| 578 | R_UNLESS(sparse_info.generation != 0, ResultInvalidNcaHeader); | ||
| 579 | |||
| 580 | // Read and verify the bucket tree header. | ||
| 581 | BucketTree::Header header; | ||
| 582 | std::memcpy(std::addressof(header), sparse_info.bucket.header.data(), sizeof(header)); | ||
| 583 | R_TRY(header.Verify()); | ||
| 584 | |||
| 585 | // Determine the storage extents. | ||
| 586 | const auto fs_offset = GetFsOffset(*m_reader, index); | ||
| 587 | const auto fs_end_offset = GetFsEndOffset(*m_reader, index); | ||
| 588 | const auto fs_size = fs_end_offset - fs_offset; | ||
| 589 | |||
| 590 | // Create the sparse storage. | ||
| 591 | std::shared_ptr<SparseStorage> sparse_storage; | ||
| 592 | if (header.entry_count != 0) { | ||
| 593 | // Create the body substorage. | ||
| 594 | VirtualFile body_substorage; | ||
| 595 | R_TRY(this->CreateBodySubStorage(std::addressof(body_substorage), | ||
| 596 | sparse_info.physical_offset, | ||
| 597 | sparse_info.GetPhysicalSize())); | ||
| 598 | |||
| 599 | // Create the meta storage. | ||
| 600 | VirtualFile meta_storage; | ||
| 601 | R_TRY(this->CreateSparseStorageMetaStorage(std::addressof(meta_storage), body_substorage, | ||
| 602 | sparse_info.physical_offset, upper_iv, | ||
| 603 | sparse_info)); | ||
| 604 | |||
| 605 | // Potentially set the output meta storage. | ||
| 606 | if (out_meta_storage != nullptr) { | ||
| 607 | *out_meta_storage = meta_storage; | ||
| 608 | } | ||
| 609 | |||
| 610 | // Create the sparse storage. | ||
| 611 | R_TRY(this->CreateSparseStorageCore(std::addressof(sparse_storage), body_substorage, | ||
| 612 | sparse_info.GetPhysicalSize(), std::move(meta_storage), | ||
| 613 | sparse_info, false)); | ||
| 614 | } else { | ||
| 615 | // If there are no entries, there's nothing to actually do. | ||
| 616 | sparse_storage = std::make_shared<SparseStorage>(); | ||
| 617 | R_UNLESS(sparse_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 618 | |||
| 619 | sparse_storage->Initialize(fs_size); | ||
| 620 | } | ||
| 621 | |||
| 622 | // Potentially set the output sparse storage. | ||
| 623 | if (out_sparse_storage != nullptr) { | ||
| 624 | *out_sparse_storage = sparse_storage; | ||
| 625 | } | ||
| 626 | |||
| 627 | // Set the output fs data offset. | ||
| 628 | *out_fs_data_offset = fs_offset; | ||
| 629 | |||
| 630 | // Set the output storage. | ||
| 631 | *out = std::move(sparse_storage); | ||
| 632 | R_SUCCEED(); | ||
| 633 | } | ||
| 634 | |||
| 635 | Result NcaFileSystemDriver::CreateSparseStorageMetaStorageWithVerification( | ||
| 636 | VirtualFile* out, VirtualFile* out_layer_info_storage, VirtualFile base_storage, s64 offset, | ||
| 637 | const NcaAesCtrUpperIv& upper_iv, const NcaSparseInfo& sparse_info, | ||
| 638 | const NcaMetaDataHashDataInfo& meta_data_hash_data_info) { | ||
| 639 | // Validate preconditions. | ||
| 640 | ASSERT(out != nullptr); | ||
| 641 | ASSERT(base_storage != nullptr); | ||
| 642 | |||
| 643 | // Get the base storage size. | ||
| 644 | s64 base_size = base_storage->GetSize(); | ||
| 645 | |||
| 646 | // Get the meta extents. | ||
| 647 | const auto meta_offset = sparse_info.bucket.offset; | ||
| 648 | const auto meta_size = sparse_info.bucket.size; | ||
| 649 | R_UNLESS(meta_offset + meta_size - offset <= base_size, ResultNcaBaseStorageOutOfRangeB); | ||
| 650 | |||
| 651 | // Get the meta data hash data extents. | ||
| 652 | const s64 meta_data_hash_data_offset = meta_data_hash_data_info.offset; | ||
| 653 | const s64 meta_data_hash_data_size = | ||
| 654 | Common::AlignUp<s64>(meta_data_hash_data_info.size, NcaHeader::CtrBlockSize); | ||
| 655 | R_UNLESS(meta_data_hash_data_offset + meta_data_hash_data_size <= base_size, | ||
| 656 | ResultNcaBaseStorageOutOfRangeB); | ||
| 657 | |||
| 658 | // Check that the meta is before the hash data. | ||
| 659 | R_UNLESS(meta_offset + meta_size <= meta_data_hash_data_offset, | ||
| 660 | ResultRomNcaInvalidSparseMetaDataHashDataOffset); | ||
| 661 | |||
| 662 | // Check that offsets are appropriately aligned. | ||
| 663 | R_UNLESS(Common::IsAligned<s64>(meta_data_hash_data_offset, NcaHeader::CtrBlockSize), | ||
| 664 | ResultRomNcaInvalidSparseMetaDataHashDataOffset); | ||
| 665 | R_UNLESS(Common::IsAligned<s64>(meta_offset, NcaHeader::CtrBlockSize), | ||
| 666 | ResultInvalidNcaFsHeader); | ||
| 667 | |||
| 668 | // Create the meta storage. | ||
| 669 | auto enc_storage = std::make_shared<OffsetVfsFile>( | ||
| 670 | std::move(base_storage), | ||
| 671 | meta_data_hash_data_offset + meta_data_hash_data_size - meta_offset, meta_offset); | ||
| 672 | R_UNLESS(enc_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 673 | |||
| 674 | // Create the decrypted storage. | ||
| 675 | VirtualFile decrypted_storage; | ||
| 676 | R_TRY(this->CreateAesCtrStorage(std::addressof(decrypted_storage), std::move(enc_storage), | ||
| 677 | offset + meta_offset, sparse_info.MakeAesCtrUpperIv(upper_iv), | ||
| 678 | AlignmentStorageRequirement::None)); | ||
| 679 | |||
| 680 | // Create the verification storage. | ||
| 681 | VirtualFile integrity_storage; | ||
| 682 | Result rc = this->CreateIntegrityVerificationStorageForMeta( | ||
| 683 | std::addressof(integrity_storage), out_layer_info_storage, std::move(decrypted_storage), | ||
| 684 | meta_offset, meta_data_hash_data_info); | ||
| 685 | if (rc == ResultInvalidNcaMetaDataHashDataSize) { | ||
| 686 | R_THROW(ResultRomNcaInvalidSparseMetaDataHashDataSize); | ||
| 687 | } | ||
| 688 | if (rc == ResultInvalidNcaMetaDataHashDataHash) { | ||
| 689 | R_THROW(ResultRomNcaInvalidSparseMetaDataHashDataHash); | ||
| 690 | } | ||
| 691 | R_TRY(rc); | ||
| 692 | |||
| 693 | // Create the meta storage. | ||
| 694 | auto meta_storage = std::make_shared<OffsetVfsFile>(std::move(integrity_storage), meta_size, 0); | ||
| 695 | R_UNLESS(meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 696 | |||
| 697 | // Set the output. | ||
| 698 | *out = std::move(meta_storage); | ||
| 699 | R_SUCCEED(); | ||
| 700 | } | ||
| 701 | |||
| 702 | Result NcaFileSystemDriver::CreateSparseStorageWithVerification( | ||
| 703 | VirtualFile* out, s64* out_fs_data_offset, std::shared_ptr<SparseStorage>* out_sparse_storage, | ||
| 704 | VirtualFile* out_meta_storage, VirtualFile* out_layer_info_storage, s32 index, | ||
| 705 | const NcaAesCtrUpperIv& upper_iv, const NcaSparseInfo& sparse_info, | ||
| 706 | const NcaMetaDataHashDataInfo& meta_data_hash_data_info, | ||
| 707 | NcaFsHeader::MetaDataHashType meta_data_hash_type) { | ||
| 708 | // Validate preconditions. | ||
| 709 | ASSERT(out != nullptr); | ||
| 710 | ASSERT(out_fs_data_offset != nullptr); | ||
| 711 | |||
| 712 | // Check the sparse info generation. | ||
| 713 | R_UNLESS(sparse_info.generation != 0, ResultInvalidNcaHeader); | ||
| 714 | |||
| 715 | // Read and verify the bucket tree header. | ||
| 716 | BucketTree::Header header; | ||
| 717 | std::memcpy(std::addressof(header), sparse_info.bucket.header.data(), sizeof(header)); | ||
| 718 | R_TRY(header.Verify()); | ||
| 719 | |||
| 720 | // Determine the storage extents. | ||
| 721 | const auto fs_offset = GetFsOffset(*m_reader, index); | ||
| 722 | const auto fs_end_offset = GetFsEndOffset(*m_reader, index); | ||
| 723 | const auto fs_size = fs_end_offset - fs_offset; | ||
| 724 | |||
| 725 | // Create the sparse storage. | ||
| 726 | std::shared_ptr<SparseStorage> sparse_storage; | ||
| 727 | if (header.entry_count != 0) { | ||
| 728 | // Create the body substorage. | ||
| 729 | VirtualFile body_substorage; | ||
| 730 | R_TRY(this->CreateBodySubStorage( | ||
| 731 | std::addressof(body_substorage), sparse_info.physical_offset, | ||
| 732 | Common::AlignUp<s64>(static_cast<s64>(meta_data_hash_data_info.offset) + | ||
| 733 | static_cast<s64>(meta_data_hash_data_info.size), | ||
| 734 | NcaHeader::CtrBlockSize))); | ||
| 735 | |||
| 736 | // Check the meta data hash type. | ||
| 737 | R_UNLESS(meta_data_hash_type == NcaFsHeader::MetaDataHashType::HierarchicalIntegrity, | ||
| 738 | ResultRomNcaInvalidSparseMetaDataHashType); | ||
| 739 | |||
| 740 | // Create the meta storage. | ||
| 741 | VirtualFile meta_storage; | ||
| 742 | R_TRY(this->CreateSparseStorageMetaStorageWithVerification( | ||
| 743 | std::addressof(meta_storage), out_layer_info_storage, body_substorage, | ||
| 744 | sparse_info.physical_offset, upper_iv, sparse_info, meta_data_hash_data_info)); | ||
| 745 | |||
| 746 | // Potentially set the output meta storage. | ||
| 747 | if (out_meta_storage != nullptr) { | ||
| 748 | *out_meta_storage = meta_storage; | ||
| 749 | } | ||
| 750 | |||
| 751 | // Create the sparse storage. | ||
| 752 | R_TRY(this->CreateSparseStorageCore(std::addressof(sparse_storage), body_substorage, | ||
| 753 | sparse_info.GetPhysicalSize(), std::move(meta_storage), | ||
| 754 | sparse_info, false)); | ||
| 755 | } else { | ||
| 756 | // If there are no entries, there's nothing to actually do. | ||
| 757 | sparse_storage = std::make_shared<SparseStorage>(); | ||
| 758 | R_UNLESS(sparse_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 759 | |||
| 760 | sparse_storage->Initialize(fs_size); | ||
| 761 | } | ||
| 762 | |||
| 763 | // Potentially set the output sparse storage. | ||
| 764 | if (out_sparse_storage != nullptr) { | ||
| 765 | *out_sparse_storage = sparse_storage; | ||
| 766 | } | ||
| 767 | |||
| 768 | // Set the output fs data offset. | ||
| 769 | *out_fs_data_offset = fs_offset; | ||
| 770 | |||
| 771 | // Set the output storage. | ||
| 772 | *out = std::move(sparse_storage); | ||
| 773 | R_SUCCEED(); | ||
| 774 | } | ||
| 775 | |||
| 776 | Result NcaFileSystemDriver::CreateAesCtrExStorageMetaStorage( | ||
| 777 | VirtualFile* out, VirtualFile base_storage, s64 offset, | ||
| 778 | NcaFsHeader::EncryptionType encryption_type, const NcaAesCtrUpperIv& upper_iv, | ||
| 779 | const NcaPatchInfo& patch_info) { | ||
| 780 | // Validate preconditions. | ||
| 781 | ASSERT(out != nullptr); | ||
| 782 | ASSERT(base_storage != nullptr); | ||
| 783 | ASSERT(encryption_type == NcaFsHeader::EncryptionType::None || | ||
| 784 | encryption_type == NcaFsHeader::EncryptionType::AesCtrEx || | ||
| 785 | encryption_type == NcaFsHeader::EncryptionType::AesCtrExSkipLayerHash); | ||
| 786 | ASSERT(patch_info.HasAesCtrExTable()); | ||
| 787 | |||
| 788 | // Validate patch info extents. | ||
| 789 | R_UNLESS(patch_info.indirect_size > 0, ResultInvalidNcaPatchInfoIndirectSize); | ||
| 790 | R_UNLESS(patch_info.aes_ctr_ex_size > 0, ResultInvalidNcaPatchInfoAesCtrExSize); | ||
| 791 | R_UNLESS(patch_info.indirect_size + patch_info.indirect_offset <= patch_info.aes_ctr_ex_offset, | ||
| 792 | ResultInvalidNcaPatchInfoAesCtrExOffset); | ||
| 793 | |||
| 794 | // Get the base storage size. | ||
| 795 | s64 base_size = base_storage->GetSize(); | ||
| 796 | |||
| 797 | // Get and validate the meta extents. | ||
| 798 | const s64 meta_offset = patch_info.aes_ctr_ex_offset; | ||
| 799 | const s64 meta_size = | ||
| 800 | Common::AlignUp(static_cast<s64>(patch_info.aes_ctr_ex_size), NcaHeader::XtsBlockSize); | ||
| 801 | R_UNLESS(meta_offset + meta_size <= base_size, ResultNcaBaseStorageOutOfRangeB); | ||
| 802 | |||
| 803 | // Create the encrypted storage. | ||
| 804 | auto enc_storage = | ||
| 805 | std::make_shared<OffsetVfsFile>(std::move(base_storage), meta_size, meta_offset); | ||
| 806 | R_UNLESS(enc_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 807 | |||
| 808 | // Create the decrypted storage. | ||
| 809 | VirtualFile decrypted_storage; | ||
| 810 | if (encryption_type != NcaFsHeader::EncryptionType::None) { | ||
| 811 | R_TRY(this->CreateAesCtrStorage(std::addressof(decrypted_storage), std::move(enc_storage), | ||
| 812 | offset + meta_offset, upper_iv, | ||
| 813 | AlignmentStorageRequirement::None)); | ||
| 814 | } else { | ||
| 815 | // If encryption type is none, don't do any decryption. | ||
| 816 | decrypted_storage = std::move(enc_storage); | ||
| 817 | } | ||
| 818 | |||
| 819 | // Create meta storage. | ||
| 820 | auto meta_storage = std::make_shared<OffsetVfsFile>(decrypted_storage, meta_size, 0); | ||
| 821 | R_UNLESS(meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 822 | |||
| 823 | // Create buffered storage. | ||
| 824 | std::vector<u8> meta_data(meta_size); | ||
| 825 | meta_storage->Read(meta_data.data(), meta_size, 0); | ||
| 826 | |||
| 827 | auto buffered_storage = std::make_shared<VectorVfsFile>(std::move(meta_data)); | ||
| 828 | R_UNLESS(buffered_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 829 | |||
| 830 | // Set the output. | ||
| 831 | *out = std::move(buffered_storage); | ||
| 832 | R_SUCCEED(); | ||
| 833 | } | ||
| 834 | |||
| 835 | Result NcaFileSystemDriver::CreateAesCtrExStorage( | ||
| 836 | VirtualFile* out, std::shared_ptr<AesCtrCounterExtendedStorage>* out_ext, | ||
| 837 | VirtualFile base_storage, VirtualFile meta_storage, s64 counter_offset, | ||
| 838 | const NcaAesCtrUpperIv& upper_iv, const NcaPatchInfo& patch_info) { | ||
| 839 | // Validate pre-conditions. | ||
| 840 | ASSERT(out != nullptr); | ||
| 841 | ASSERT(base_storage != nullptr); | ||
| 842 | ASSERT(meta_storage != nullptr); | ||
| 843 | ASSERT(patch_info.HasAesCtrExTable()); | ||
| 844 | |||
| 845 | // Read the bucket tree header. | ||
| 846 | BucketTree::Header header; | ||
| 847 | std::memcpy(std::addressof(header), patch_info.aes_ctr_ex_header.data(), sizeof(header)); | ||
| 848 | R_TRY(header.Verify()); | ||
| 849 | |||
| 850 | // Determine the bucket extents. | ||
| 851 | const auto entry_count = header.entry_count; | ||
| 852 | const s64 data_offset = 0; | ||
| 853 | const s64 data_size = patch_info.aes_ctr_ex_offset; | ||
| 854 | const s64 node_offset = 0; | ||
| 855 | const s64 node_size = AesCtrCounterExtendedStorage::QueryNodeStorageSize(entry_count); | ||
| 856 | const s64 entry_offset = node_offset + node_size; | ||
| 857 | const s64 entry_size = AesCtrCounterExtendedStorage::QueryEntryStorageSize(entry_count); | ||
| 858 | |||
| 859 | // Create bucket storages. | ||
| 860 | auto data_storage = | ||
| 861 | std::make_shared<OffsetVfsFile>(std::move(base_storage), data_size, data_offset); | ||
| 862 | auto node_storage = std::make_shared<OffsetVfsFile>(meta_storage, node_size, node_offset); | ||
| 863 | auto entry_storage = std::make_shared<OffsetVfsFile>(meta_storage, entry_size, entry_offset); | ||
| 864 | |||
| 865 | // Get the secure value. | ||
| 866 | const auto secure_value = upper_iv.part.secure_value; | ||
| 867 | |||
| 868 | // Create the aes ctr ex storage. | ||
| 869 | VirtualFile aes_ctr_ex_storage; | ||
| 870 | if (m_reader->HasExternalDecryptionKey()) { | ||
| 871 | // Create the decryptor. | ||
| 872 | std::unique_ptr<AesCtrCounterExtendedStorage::IDecryptor> decryptor; | ||
| 873 | R_TRY(AesCtrCounterExtendedStorage::CreateSoftwareDecryptor(std::addressof(decryptor))); | ||
| 874 | |||
| 875 | // Create the aes ctr ex storage. | ||
| 876 | auto impl_storage = std::make_shared<AesCtrCounterExtendedStorage>(); | ||
| 877 | R_UNLESS(impl_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 878 | |||
| 879 | // Initialize the aes ctr ex storage. | ||
| 880 | R_TRY(impl_storage->Initialize(m_reader->GetExternalDecryptionKey(), AesCtrStorage::KeySize, | ||
| 881 | secure_value, counter_offset, data_storage, node_storage, | ||
| 882 | entry_storage, entry_count, std::move(decryptor))); | ||
| 883 | |||
| 884 | // Potentially set the output implementation storage. | ||
| 885 | if (out_ext != nullptr) { | ||
| 886 | *out_ext = impl_storage; | ||
| 887 | } | ||
| 888 | |||
| 889 | // Set the implementation storage. | ||
| 890 | aes_ctr_ex_storage = std::move(impl_storage); | ||
| 891 | } else { | ||
| 892 | // Create the software decryptor. | ||
| 893 | std::unique_ptr<AesCtrCounterExtendedStorage::IDecryptor> sw_decryptor; | ||
| 894 | R_TRY(AesCtrCounterExtendedStorage::CreateSoftwareDecryptor(std::addressof(sw_decryptor))); | ||
| 895 | |||
| 896 | // Make the software storage. | ||
| 897 | auto sw_storage = std::make_shared<AesCtrCounterExtendedStorage>(); | ||
| 898 | R_UNLESS(sw_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 899 | |||
| 900 | // Initialize the software storage. | ||
| 901 | R_TRY(sw_storage->Initialize(m_reader->GetDecryptionKey(NcaHeader::DecryptionKey_AesCtr), | ||
| 902 | AesCtrStorage::KeySize, secure_value, counter_offset, | ||
| 903 | data_storage, node_storage, entry_storage, entry_count, | ||
| 904 | std::move(sw_decryptor))); | ||
| 905 | |||
| 906 | // Potentially set the output implementation storage. | ||
| 907 | if (out_ext != nullptr) { | ||
| 908 | *out_ext = sw_storage; | ||
| 909 | } | ||
| 910 | |||
| 911 | // Set the implementation storage. | ||
| 912 | aes_ctr_ex_storage = std::move(sw_storage); | ||
| 913 | } | ||
| 914 | |||
| 915 | // Create an alignment-matching storage. | ||
| 916 | using AlignedStorage = AlignmentMatchingStorage<NcaHeader::CtrBlockSize, 1>; | ||
| 917 | auto aligned_storage = std::make_shared<AlignedStorage>(std::move(aes_ctr_ex_storage)); | ||
| 918 | R_UNLESS(aligned_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 919 | |||
| 920 | // Set the output. | ||
| 921 | *out = std::move(aligned_storage); | ||
| 922 | R_SUCCEED(); | ||
| 923 | } | ||
| 924 | |||
| 925 | Result NcaFileSystemDriver::CreateIndirectStorageMetaStorage(VirtualFile* out, | ||
| 926 | VirtualFile base_storage, | ||
| 927 | const NcaPatchInfo& patch_info) { | ||
| 928 | // Validate preconditions. | ||
| 929 | ASSERT(out != nullptr); | ||
| 930 | ASSERT(base_storage != nullptr); | ||
| 931 | ASSERT(patch_info.HasIndirectTable()); | ||
| 932 | |||
| 933 | // Get the base storage size. | ||
| 934 | s64 base_size = base_storage->GetSize(); | ||
| 935 | |||
| 936 | // Check that we're within range. | ||
| 937 | R_UNLESS(patch_info.indirect_offset + patch_info.indirect_size <= base_size, | ||
| 938 | ResultNcaBaseStorageOutOfRangeE); | ||
| 939 | |||
| 940 | // Create the meta storage. | ||
| 941 | auto meta_storage = std::make_shared<OffsetVfsFile>(base_storage, patch_info.indirect_size, | ||
| 942 | patch_info.indirect_offset); | ||
| 943 | R_UNLESS(meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 944 | |||
| 945 | // Create buffered storage. | ||
| 946 | std::vector<u8> meta_data(patch_info.indirect_size); | ||
| 947 | meta_storage->Read(meta_data.data(), patch_info.indirect_size, 0); | ||
| 948 | |||
| 949 | auto buffered_storage = std::make_shared<VectorVfsFile>(std::move(meta_data)); | ||
| 950 | R_UNLESS(buffered_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 951 | |||
| 952 | // Set the output. | ||
| 953 | *out = std::move(buffered_storage); | ||
| 954 | R_SUCCEED(); | ||
| 955 | } | ||
| 956 | |||
| 957 | Result NcaFileSystemDriver::CreateIndirectStorage( | ||
| 958 | VirtualFile* out, std::shared_ptr<IndirectStorage>* out_ind, VirtualFile base_storage, | ||
| 959 | VirtualFile original_data_storage, VirtualFile meta_storage, const NcaPatchInfo& patch_info) { | ||
| 960 | // Validate preconditions. | ||
| 961 | ASSERT(out != nullptr); | ||
| 962 | ASSERT(base_storage != nullptr); | ||
| 963 | ASSERT(meta_storage != nullptr); | ||
| 964 | ASSERT(patch_info.HasIndirectTable()); | ||
| 965 | |||
| 966 | // Read the bucket tree header. | ||
| 967 | BucketTree::Header header; | ||
| 968 | std::memcpy(std::addressof(header), patch_info.indirect_header.data(), sizeof(header)); | ||
| 969 | R_TRY(header.Verify()); | ||
| 970 | |||
| 971 | // Determine the storage sizes. | ||
| 972 | const auto node_size = IndirectStorage::QueryNodeStorageSize(header.entry_count); | ||
| 973 | const auto entry_size = IndirectStorage::QueryEntryStorageSize(header.entry_count); | ||
| 974 | R_UNLESS(node_size + entry_size <= patch_info.indirect_size, | ||
| 975 | ResultInvalidNcaIndirectStorageOutOfRange); | ||
| 976 | |||
| 977 | // Get the indirect data size. | ||
| 978 | const s64 indirect_data_size = patch_info.indirect_offset; | ||
| 979 | ASSERT(Common::IsAligned(indirect_data_size, NcaHeader::XtsBlockSize)); | ||
| 980 | |||
| 981 | // Create the indirect data storage. | ||
| 982 | auto indirect_data_storage = | ||
| 983 | std::make_shared<OffsetVfsFile>(base_storage, indirect_data_size, 0); | ||
| 984 | R_UNLESS(indirect_data_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 985 | |||
| 986 | // Create the indirect storage. | ||
| 987 | auto indirect_storage = std::make_shared<IndirectStorage>(); | ||
| 988 | R_UNLESS(indirect_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 989 | |||
| 990 | // Initialize the indirect storage. | ||
| 991 | R_TRY(indirect_storage->Initialize( | ||
| 992 | std::make_shared<OffsetVfsFile>(meta_storage, node_size, 0), | ||
| 993 | std::make_shared<OffsetVfsFile>(meta_storage, entry_size, node_size), header.entry_count)); | ||
| 994 | |||
| 995 | // Get the original data size. | ||
| 996 | s64 original_data_size = original_data_storage->GetSize(); | ||
| 997 | |||
| 998 | // Set the indirect storages. | ||
| 999 | indirect_storage->SetStorage( | ||
| 1000 | 0, std::make_shared<OffsetVfsFile>(original_data_storage, original_data_size, 0)); | ||
| 1001 | indirect_storage->SetStorage( | ||
| 1002 | 1, std::make_shared<OffsetVfsFile>(indirect_data_storage, indirect_data_size, 0)); | ||
| 1003 | |||
| 1004 | // If necessary, set the output indirect storage. | ||
| 1005 | if (out_ind != nullptr) { | ||
| 1006 | *out_ind = indirect_storage; | ||
| 1007 | } | ||
| 1008 | |||
| 1009 | // Set the output. | ||
| 1010 | *out = std::move(indirect_storage); | ||
| 1011 | R_SUCCEED(); | ||
| 1012 | } | ||
| 1013 | |||
| 1014 | Result NcaFileSystemDriver::CreatePatchMetaStorage( | ||
| 1015 | VirtualFile* out_aes_ctr_ex_meta, VirtualFile* out_indirect_meta, | ||
| 1016 | VirtualFile* out_layer_info_storage, VirtualFile base_storage, s64 offset, | ||
| 1017 | const NcaAesCtrUpperIv& upper_iv, const NcaPatchInfo& patch_info, | ||
| 1018 | const NcaMetaDataHashDataInfo& meta_data_hash_data_info) { | ||
| 1019 | // Validate preconditions. | ||
| 1020 | ASSERT(out_aes_ctr_ex_meta != nullptr); | ||
| 1021 | ASSERT(out_indirect_meta != nullptr); | ||
| 1022 | ASSERT(base_storage != nullptr); | ||
| 1023 | ASSERT(patch_info.HasAesCtrExTable()); | ||
| 1024 | ASSERT(patch_info.HasIndirectTable()); | ||
| 1025 | ASSERT(Common::IsAligned<s64>(patch_info.aes_ctr_ex_size, NcaHeader::XtsBlockSize)); | ||
| 1026 | |||
| 1027 | // Validate patch info extents. | ||
| 1028 | R_UNLESS(patch_info.indirect_size > 0, ResultInvalidNcaPatchInfoIndirectSize); | ||
| 1029 | R_UNLESS(patch_info.aes_ctr_ex_size >= 0, ResultInvalidNcaPatchInfoAesCtrExSize); | ||
| 1030 | R_UNLESS(patch_info.indirect_size + patch_info.indirect_offset <= patch_info.aes_ctr_ex_offset, | ||
| 1031 | ResultInvalidNcaPatchInfoAesCtrExOffset); | ||
| 1032 | R_UNLESS(patch_info.aes_ctr_ex_offset + patch_info.aes_ctr_ex_size <= | ||
| 1033 | meta_data_hash_data_info.offset, | ||
| 1034 | ResultRomNcaInvalidPatchMetaDataHashDataOffset); | ||
| 1035 | |||
| 1036 | // Get the base storage size. | ||
| 1037 | s64 base_size = base_storage->GetSize(); | ||
| 1038 | |||
| 1039 | // Check that extents remain within range. | ||
| 1040 | R_UNLESS(patch_info.indirect_offset + patch_info.indirect_size <= base_size, | ||
| 1041 | ResultNcaBaseStorageOutOfRangeE); | ||
| 1042 | R_UNLESS(patch_info.aes_ctr_ex_offset + patch_info.aes_ctr_ex_size <= base_size, | ||
| 1043 | ResultNcaBaseStorageOutOfRangeB); | ||
| 1044 | |||
| 1045 | // Check that metadata hash data extents remain within range. | ||
| 1046 | const s64 meta_data_hash_data_offset = meta_data_hash_data_info.offset; | ||
| 1047 | const s64 meta_data_hash_data_size = | ||
| 1048 | Common::AlignUp<s64>(meta_data_hash_data_info.size, NcaHeader::CtrBlockSize); | ||
| 1049 | R_UNLESS(meta_data_hash_data_offset + meta_data_hash_data_size <= base_size, | ||
| 1050 | ResultNcaBaseStorageOutOfRangeB); | ||
| 1051 | |||
| 1052 | // Create the encrypted storage. | ||
| 1053 | auto enc_storage = std::make_shared<OffsetVfsFile>( | ||
| 1054 | std::move(base_storage), | ||
| 1055 | meta_data_hash_data_offset + meta_data_hash_data_size - patch_info.indirect_offset, | ||
| 1056 | patch_info.indirect_offset); | ||
| 1057 | R_UNLESS(enc_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 1058 | |||
| 1059 | // Create the decrypted storage. | ||
| 1060 | VirtualFile decrypted_storage; | ||
| 1061 | R_TRY(this->CreateAesCtrStorage(std::addressof(decrypted_storage), std::move(enc_storage), | ||
| 1062 | offset + patch_info.indirect_offset, upper_iv, | ||
| 1063 | AlignmentStorageRequirement::None)); | ||
| 1064 | |||
| 1065 | // Create the verification storage. | ||
| 1066 | VirtualFile integrity_storage; | ||
| 1067 | Result rc = this->CreateIntegrityVerificationStorageForMeta( | ||
| 1068 | std::addressof(integrity_storage), out_layer_info_storage, std::move(decrypted_storage), | ||
| 1069 | patch_info.indirect_offset, meta_data_hash_data_info); | ||
| 1070 | if (rc == ResultInvalidNcaMetaDataHashDataSize) { | ||
| 1071 | R_THROW(ResultRomNcaInvalidPatchMetaDataHashDataSize); | ||
| 1072 | } | ||
| 1073 | if (rc == ResultInvalidNcaMetaDataHashDataHash) { | ||
| 1074 | R_THROW(ResultRomNcaInvalidPatchMetaDataHashDataHash); | ||
| 1075 | } | ||
| 1076 | R_TRY(rc); | ||
| 1077 | |||
| 1078 | // Create the indirect meta storage. | ||
| 1079 | auto indirect_meta_storage = | ||
| 1080 | std::make_shared<OffsetVfsFile>(integrity_storage, patch_info.indirect_size, | ||
| 1081 | patch_info.indirect_offset - patch_info.indirect_offset); | ||
| 1082 | R_UNLESS(indirect_meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 1083 | |||
| 1084 | // Create the aes ctr ex meta storage. | ||
| 1085 | auto aes_ctr_ex_meta_storage = | ||
| 1086 | std::make_shared<OffsetVfsFile>(integrity_storage, patch_info.aes_ctr_ex_size, | ||
| 1087 | patch_info.aes_ctr_ex_offset - patch_info.indirect_offset); | ||
| 1088 | R_UNLESS(aes_ctr_ex_meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 1089 | |||
| 1090 | // Set the output. | ||
| 1091 | *out_aes_ctr_ex_meta = std::move(aes_ctr_ex_meta_storage); | ||
| 1092 | *out_indirect_meta = std::move(indirect_meta_storage); | ||
| 1093 | R_SUCCEED(); | ||
| 1094 | } | ||
| 1095 | |||
| 1096 | Result NcaFileSystemDriver::CreateSha256Storage( | ||
| 1097 | VirtualFile* out, VirtualFile base_storage, | ||
| 1098 | const NcaFsHeader::HashData::HierarchicalSha256Data& hash_data) { | ||
| 1099 | // Validate preconditions. | ||
| 1100 | ASSERT(out != nullptr); | ||
| 1101 | ASSERT(base_storage != nullptr); | ||
| 1102 | |||
| 1103 | // Define storage types. | ||
| 1104 | using VerificationStorage = HierarchicalSha256Storage; | ||
| 1105 | |||
| 1106 | // Validate the hash data. | ||
| 1107 | R_UNLESS(Common::IsPowerOfTwo(hash_data.hash_block_size), | ||
| 1108 | ResultInvalidHierarchicalSha256BlockSize); | ||
| 1109 | R_UNLESS(hash_data.hash_layer_count == VerificationStorage::LayerCount - 1, | ||
| 1110 | ResultInvalidHierarchicalSha256LayerCount); | ||
| 1111 | |||
| 1112 | // Get the regions. | ||
| 1113 | const auto& hash_region = hash_data.hash_layer_region[0]; | ||
| 1114 | const auto& data_region = hash_data.hash_layer_region[1]; | ||
| 1115 | |||
| 1116 | // Determine buffer sizes. | ||
| 1117 | constexpr s32 CacheBlockCount = 2; | ||
| 1118 | const auto hash_buffer_size = static_cast<size_t>(hash_region.size); | ||
| 1119 | const auto cache_buffer_size = CacheBlockCount * hash_data.hash_block_size; | ||
| 1120 | const auto total_buffer_size = hash_buffer_size + cache_buffer_size; | ||
| 1121 | |||
| 1122 | // Make a buffer holder storage. | ||
| 1123 | auto buffer_hold_storage = std::make_shared<MemoryResourceBufferHoldStorage>( | ||
| 1124 | std::move(base_storage), total_buffer_size); | ||
| 1125 | R_UNLESS(buffer_hold_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 1126 | R_UNLESS(buffer_hold_storage->IsValid(), ResultAllocationMemoryFailedInNcaFileSystemDriverI); | ||
| 1127 | |||
| 1128 | // Get storage size. | ||
| 1129 | s64 base_size = buffer_hold_storage->GetSize(); | ||
| 1130 | |||
| 1131 | // Check that we're within range. | ||
| 1132 | R_UNLESS(hash_region.offset + hash_region.size <= base_size, ResultNcaBaseStorageOutOfRangeC); | ||
| 1133 | R_UNLESS(data_region.offset + data_region.size <= base_size, ResultNcaBaseStorageOutOfRangeC); | ||
| 1134 | |||
| 1135 | // Create the master hash storage. | ||
| 1136 | auto master_hash_storage = | ||
| 1137 | std::make_shared<ArrayVfsFile<sizeof(Hash)>>(hash_data.fs_data_master_hash.value); | ||
| 1138 | |||
| 1139 | // Make the verification storage. | ||
| 1140 | auto verification_storage = std::make_shared<VerificationStorage>(); | ||
| 1141 | R_UNLESS(verification_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 1142 | |||
| 1143 | // Make layer storages. | ||
| 1144 | std::array<VirtualFile, VerificationStorage::LayerCount> layer_storages{ | ||
| 1145 | std::make_shared<OffsetVfsFile>(master_hash_storage, sizeof(Hash), 0), | ||
| 1146 | std::make_shared<OffsetVfsFile>(buffer_hold_storage, hash_region.size, hash_region.offset), | ||
| 1147 | std::make_shared<OffsetVfsFile>(buffer_hold_storage, data_region.size, data_region.offset), | ||
| 1148 | }; | ||
| 1149 | |||
| 1150 | // Initialize the verification storage. | ||
| 1151 | R_TRY(verification_storage->Initialize(layer_storages.data(), VerificationStorage::LayerCount, | ||
| 1152 | hash_data.hash_block_size, | ||
| 1153 | buffer_hold_storage->GetBuffer(), hash_buffer_size)); | ||
| 1154 | |||
| 1155 | // Set the output. | ||
| 1156 | *out = std::move(verification_storage); | ||
| 1157 | R_SUCCEED(); | ||
| 1158 | } | ||
| 1159 | |||
| 1160 | Result NcaFileSystemDriver::CreateIntegrityVerificationStorage( | ||
| 1161 | VirtualFile* out, VirtualFile base_storage, | ||
| 1162 | const NcaFsHeader::HashData::IntegrityMetaInfo& meta_info) { | ||
| 1163 | R_RETURN(this->CreateIntegrityVerificationStorageImpl( | ||
| 1164 | out, base_storage, meta_info, 0, IntegrityDataCacheCount, IntegrityHashCacheCount, | ||
| 1165 | HierarchicalIntegrityVerificationStorage::GetDefaultDataCacheBufferLevel( | ||
| 1166 | meta_info.level_hash_info.max_layers))); | ||
| 1167 | } | ||
| 1168 | |||
| 1169 | Result NcaFileSystemDriver::CreateIntegrityVerificationStorageForMeta( | ||
| 1170 | VirtualFile* out, VirtualFile* out_layer_info_storage, VirtualFile base_storage, s64 offset, | ||
| 1171 | const NcaMetaDataHashDataInfo& meta_data_hash_data_info) { | ||
| 1172 | // Validate preconditions. | ||
| 1173 | ASSERT(out != nullptr); | ||
| 1174 | |||
| 1175 | // Check the meta data hash data size. | ||
| 1176 | R_UNLESS(meta_data_hash_data_info.size == sizeof(NcaMetaDataHashData), | ||
| 1177 | ResultInvalidNcaMetaDataHashDataSize); | ||
| 1178 | |||
| 1179 | // Read the meta data hash data. | ||
| 1180 | NcaMetaDataHashData meta_data_hash_data; | ||
| 1181 | base_storage->ReadObject(std::addressof(meta_data_hash_data), | ||
| 1182 | meta_data_hash_data_info.offset - offset); | ||
| 1183 | |||
| 1184 | // Set the out layer info storage, if necessary. | ||
| 1185 | if (out_layer_info_storage != nullptr) { | ||
| 1186 | auto layer_info_storage = std::make_shared<OffsetVfsFile>( | ||
| 1187 | base_storage, | ||
| 1188 | meta_data_hash_data_info.offset + meta_data_hash_data_info.size - | ||
| 1189 | meta_data_hash_data.layer_info_offset, | ||
| 1190 | meta_data_hash_data.layer_info_offset - offset); | ||
| 1191 | R_UNLESS(layer_info_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 1192 | |||
| 1193 | *out_layer_info_storage = std::move(layer_info_storage); | ||
| 1194 | } | ||
| 1195 | |||
| 1196 | // Create the meta storage. | ||
| 1197 | auto meta_storage = std::make_shared<OffsetVfsFile>( | ||
| 1198 | std::move(base_storage), meta_data_hash_data_info.offset - offset, 0); | ||
| 1199 | R_UNLESS(meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 1200 | |||
| 1201 | // Create the integrity verification storage. | ||
| 1202 | R_RETURN(this->CreateIntegrityVerificationStorageImpl( | ||
| 1203 | out, std::move(meta_storage), meta_data_hash_data.integrity_meta_info, | ||
| 1204 | meta_data_hash_data.layer_info_offset - offset, IntegrityDataCacheCountForMeta, | ||
| 1205 | IntegrityHashCacheCountForMeta, 0)); | ||
| 1206 | } | ||
| 1207 | |||
| 1208 | Result NcaFileSystemDriver::CreateIntegrityVerificationStorageImpl( | ||
| 1209 | VirtualFile* out, VirtualFile base_storage, | ||
| 1210 | const NcaFsHeader::HashData::IntegrityMetaInfo& meta_info, s64 layer_info_offset, | ||
| 1211 | int max_data_cache_entries, int max_hash_cache_entries, s8 buffer_level) { | ||
| 1212 | // Validate preconditions. | ||
| 1213 | ASSERT(out != nullptr); | ||
| 1214 | ASSERT(base_storage != nullptr); | ||
| 1215 | ASSERT(layer_info_offset >= 0); | ||
| 1216 | |||
| 1217 | // Define storage types. | ||
| 1218 | using VerificationStorage = HierarchicalIntegrityVerificationStorage; | ||
| 1219 | using StorageInfo = VerificationStorage::HierarchicalStorageInformation; | ||
| 1220 | |||
| 1221 | // Validate the meta info. | ||
| 1222 | HierarchicalIntegrityVerificationInformation level_hash_info; | ||
| 1223 | std::memcpy(std::addressof(level_hash_info), std::addressof(meta_info.level_hash_info), | ||
| 1224 | sizeof(level_hash_info)); | ||
| 1225 | |||
| 1226 | R_UNLESS(IntegrityMinLayerCount <= level_hash_info.max_layers, | ||
| 1227 | ResultInvalidNcaHierarchicalIntegrityVerificationLayerCount); | ||
| 1228 | R_UNLESS(level_hash_info.max_layers <= IntegrityMaxLayerCount, | ||
| 1229 | ResultInvalidNcaHierarchicalIntegrityVerificationLayerCount); | ||
| 1230 | |||
| 1231 | // Get the base storage size. | ||
| 1232 | s64 base_storage_size = base_storage->GetSize(); | ||
| 1233 | |||
| 1234 | // Create storage info. | ||
| 1235 | StorageInfo storage_info; | ||
| 1236 | for (s32 i = 0; i < static_cast<s32>(level_hash_info.max_layers - 2); ++i) { | ||
| 1237 | const auto& layer_info = level_hash_info.info[i]; | ||
| 1238 | R_UNLESS(layer_info_offset + layer_info.offset + layer_info.size <= base_storage_size, | ||
| 1239 | ResultNcaBaseStorageOutOfRangeD); | ||
| 1240 | |||
| 1241 | storage_info[i + 1] = std::make_shared<OffsetVfsFile>( | ||
| 1242 | base_storage, layer_info.size, layer_info_offset + layer_info.offset); | ||
| 1243 | } | ||
| 1244 | |||
| 1245 | // Set the last layer info. | ||
| 1246 | const auto& layer_info = level_hash_info.info[level_hash_info.max_layers - 2]; | ||
| 1247 | const s64 last_layer_info_offset = layer_info_offset > 0 ? 0LL : layer_info.offset.Get(); | ||
| 1248 | R_UNLESS(last_layer_info_offset + layer_info.size <= base_storage_size, | ||
| 1249 | ResultNcaBaseStorageOutOfRangeD); | ||
| 1250 | if (layer_info_offset > 0) { | ||
| 1251 | R_UNLESS(last_layer_info_offset + layer_info.size <= layer_info_offset, | ||
| 1252 | ResultRomNcaInvalidIntegrityLayerInfoOffset); | ||
| 1253 | } | ||
| 1254 | storage_info.SetDataStorage(std::make_shared<OffsetVfsFile>( | ||
| 1255 | std::move(base_storage), layer_info.size, last_layer_info_offset)); | ||
| 1256 | |||
| 1257 | // Make the integrity romfs storage. | ||
| 1258 | auto integrity_storage = std::make_shared<IntegrityRomFsStorage>(); | ||
| 1259 | R_UNLESS(integrity_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 1260 | |||
| 1261 | // Initialize the integrity storage. | ||
| 1262 | R_TRY(integrity_storage->Initialize(level_hash_info, meta_info.master_hash, storage_info, | ||
| 1263 | max_data_cache_entries, max_hash_cache_entries, | ||
| 1264 | buffer_level)); | ||
| 1265 | |||
| 1266 | // Set the output. | ||
| 1267 | *out = std::move(integrity_storage); | ||
| 1268 | R_SUCCEED(); | ||
| 1269 | } | ||
| 1270 | |||
| 1271 | Result NcaFileSystemDriver::CreateRegionSwitchStorage(VirtualFile* out, | ||
| 1272 | const NcaFsHeaderReader* header_reader, | ||
| 1273 | VirtualFile inside_storage, | ||
| 1274 | VirtualFile outside_storage) { | ||
| 1275 | // Check pre-conditions. | ||
| 1276 | ASSERT(header_reader->GetHashType() == NcaFsHeader::HashType::HierarchicalIntegrityHash); | ||
| 1277 | |||
| 1278 | // Create the region. | ||
| 1279 | RegionSwitchStorage::Region region = {}; | ||
| 1280 | R_TRY(header_reader->GetHashTargetOffset(std::addressof(region.size))); | ||
| 1281 | |||
| 1282 | // Create the region switch storage. | ||
| 1283 | auto region_switch_storage = std::make_shared<RegionSwitchStorage>( | ||
| 1284 | std::move(inside_storage), std::move(outside_storage), region); | ||
| 1285 | R_UNLESS(region_switch_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 1286 | |||
| 1287 | // Set the output. | ||
| 1288 | *out = std::move(region_switch_storage); | ||
| 1289 | R_SUCCEED(); | ||
| 1290 | } | ||
| 1291 | |||
| 1292 | Result NcaFileSystemDriver::CreateCompressedStorage(VirtualFile* out, | ||
| 1293 | std::shared_ptr<CompressedStorage>* out_cmp, | ||
| 1294 | VirtualFile* out_meta, VirtualFile base_storage, | ||
| 1295 | const NcaCompressionInfo& compression_info) { | ||
| 1296 | R_RETURN(this->CreateCompressedStorage(out, out_cmp, out_meta, std::move(base_storage), | ||
| 1297 | compression_info, m_reader->GetDecompressor())); | ||
| 1298 | } | ||
| 1299 | |||
| 1300 | Result NcaFileSystemDriver::CreateCompressedStorage(VirtualFile* out, | ||
| 1301 | std::shared_ptr<CompressedStorage>* out_cmp, | ||
| 1302 | VirtualFile* out_meta, VirtualFile base_storage, | ||
| 1303 | const NcaCompressionInfo& compression_info, | ||
| 1304 | GetDecompressorFunction get_decompressor) { | ||
| 1305 | // Check pre-conditions. | ||
| 1306 | ASSERT(out != nullptr); | ||
| 1307 | ASSERT(base_storage != nullptr); | ||
| 1308 | ASSERT(get_decompressor != nullptr); | ||
| 1309 | |||
| 1310 | // Read and verify the bucket tree header. | ||
| 1311 | BucketTree::Header header; | ||
| 1312 | std::memcpy(std::addressof(header), compression_info.bucket.header.data(), sizeof(header)); | ||
| 1313 | R_TRY(header.Verify()); | ||
| 1314 | |||
| 1315 | // Determine the storage extents. | ||
| 1316 | const auto table_offset = compression_info.bucket.offset; | ||
| 1317 | const auto table_size = compression_info.bucket.size; | ||
| 1318 | const auto node_size = CompressedStorage::QueryNodeStorageSize(header.entry_count); | ||
| 1319 | const auto entry_size = CompressedStorage::QueryEntryStorageSize(header.entry_count); | ||
| 1320 | R_UNLESS(node_size + entry_size <= table_size, ResultInvalidCompressedStorageSize); | ||
| 1321 | |||
| 1322 | // If we should, set the output meta storage. | ||
| 1323 | if (out_meta != nullptr) { | ||
| 1324 | auto meta_storage = std::make_shared<OffsetVfsFile>(base_storage, table_size, table_offset); | ||
| 1325 | R_UNLESS(meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 1326 | |||
| 1327 | *out_meta = std::move(meta_storage); | ||
| 1328 | } | ||
| 1329 | |||
| 1330 | // Allocate the compressed storage. | ||
| 1331 | auto compressed_storage = std::make_shared<CompressedStorage>(); | ||
| 1332 | R_UNLESS(compressed_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 1333 | |||
| 1334 | // Initialize the compressed storage. | ||
| 1335 | R_TRY(compressed_storage->Initialize( | ||
| 1336 | std::make_shared<OffsetVfsFile>(base_storage, table_offset, 0), | ||
| 1337 | std::make_shared<OffsetVfsFile>(base_storage, node_size, table_offset), | ||
| 1338 | std::make_shared<OffsetVfsFile>(base_storage, entry_size, table_offset + node_size), | ||
| 1339 | header.entry_count, 64_KiB, 640_KiB, get_decompressor, 16_KiB, 16_KiB, 32)); | ||
| 1340 | |||
| 1341 | // Potentially set the output compressed storage. | ||
| 1342 | if (out_cmp) { | ||
| 1343 | *out_cmp = compressed_storage; | ||
| 1344 | } | ||
| 1345 | |||
| 1346 | // Set the output. | ||
| 1347 | *out = std::move(compressed_storage); | ||
| 1348 | R_SUCCEED(); | ||
| 1349 | } | ||
| 1350 | |||
| 1351 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "core/file_sys/fssystem/fssystem_compression_common.h" | ||
| 7 | #include "core/file_sys/fssystem/fssystem_nca_header.h" | ||
| 8 | #include "core/file_sys/vfs.h" | ||
| 9 | |||
| 10 | namespace FileSys { | ||
| 11 | |||
| 12 | class CompressedStorage; | ||
| 13 | class AesCtrCounterExtendedStorage; | ||
| 14 | class IndirectStorage; | ||
| 15 | class SparseStorage; | ||
| 16 | |||
| 17 | struct NcaCryptoConfiguration; | ||
| 18 | |||
| 19 | using KeyGenerationFunction = void (*)(void* dst_key, size_t dst_key_size, const void* src_key, | ||
| 20 | size_t src_key_size, s32 key_type); | ||
| 21 | using VerifySign1Function = bool (*)(const void* sig, size_t sig_size, const void* data, | ||
| 22 | size_t data_size, u8 generation); | ||
| 23 | |||
| 24 | struct NcaCryptoConfiguration { | ||
| 25 | static constexpr size_t Rsa2048KeyModulusSize = 2048 / 8; | ||
| 26 | static constexpr size_t Rsa2048KeyPublicExponentSize = 3; | ||
| 27 | static constexpr size_t Rsa2048KeyPrivateExponentSize = Rsa2048KeyModulusSize; | ||
| 28 | |||
| 29 | static constexpr size_t Aes128KeySize = 128 / 8; | ||
| 30 | |||
| 31 | static constexpr size_t Header1SignatureKeyGenerationMax = 1; | ||
| 32 | |||
| 33 | static constexpr s32 KeyAreaEncryptionKeyIndexCount = 3; | ||
| 34 | static constexpr s32 HeaderEncryptionKeyCount = 2; | ||
| 35 | |||
| 36 | static constexpr u8 KeyAreaEncryptionKeyIndexZeroKey = 0xFF; | ||
| 37 | |||
| 38 | static constexpr size_t KeyGenerationMax = 32; | ||
| 39 | |||
| 40 | std::array<const u8*, Header1SignatureKeyGenerationMax + 1> header_1_sign_key_moduli; | ||
| 41 | std::array<u8, Rsa2048KeyPublicExponentSize> header_1_sign_key_public_exponent; | ||
| 42 | std::array<std::array<u8, Aes128KeySize>, KeyAreaEncryptionKeyIndexCount> | ||
| 43 | key_area_encryption_key_source; | ||
| 44 | std::array<u8, Aes128KeySize> header_encryption_key_source; | ||
| 45 | std::array<std::array<u8, Aes128KeySize>, HeaderEncryptionKeyCount> | ||
| 46 | header_encrypted_encryption_keys; | ||
| 47 | KeyGenerationFunction generate_key; | ||
| 48 | VerifySign1Function verify_sign1; | ||
| 49 | bool is_plaintext_header_available; | ||
| 50 | bool is_available_sw_key; | ||
| 51 | }; | ||
| 52 | static_assert(std::is_trivial_v<NcaCryptoConfiguration>); | ||
| 53 | |||
| 54 | struct NcaCompressionConfiguration { | ||
| 55 | GetDecompressorFunction get_decompressor; | ||
| 56 | }; | ||
| 57 | static_assert(std::is_trivial_v<NcaCompressionConfiguration>); | ||
| 58 | |||
| 59 | constexpr inline s32 KeyAreaEncryptionKeyCount = | ||
| 60 | NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount * | ||
| 61 | NcaCryptoConfiguration::KeyGenerationMax; | ||
| 62 | |||
| 63 | enum class KeyType : s32 { | ||
| 64 | ZeroKey = -2, | ||
| 65 | InvalidKey = -1, | ||
| 66 | NcaHeaderKey1 = KeyAreaEncryptionKeyCount + 0, | ||
| 67 | NcaHeaderKey2 = KeyAreaEncryptionKeyCount + 1, | ||
| 68 | NcaExternalKey = KeyAreaEncryptionKeyCount + 2, | ||
| 69 | SaveDataDeviceUniqueMac = KeyAreaEncryptionKeyCount + 3, | ||
| 70 | SaveDataSeedUniqueMac = KeyAreaEncryptionKeyCount + 4, | ||
| 71 | SaveDataTransferMac = KeyAreaEncryptionKeyCount + 5, | ||
| 72 | }; | ||
| 73 | |||
| 74 | constexpr inline bool IsInvalidKeyTypeValue(s32 key_type) { | ||
| 75 | return key_type < 0; | ||
| 76 | } | ||
| 77 | |||
| 78 | constexpr inline s32 GetKeyTypeValue(u8 key_index, u8 key_generation) { | ||
| 79 | if (key_index == NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexZeroKey) { | ||
| 80 | return static_cast<s32>(KeyType::ZeroKey); | ||
| 81 | } | ||
| 82 | |||
| 83 | if (key_index >= NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount) { | ||
| 84 | return static_cast<s32>(KeyType::InvalidKey); | ||
| 85 | } | ||
| 86 | |||
| 87 | return NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount * key_generation + key_index; | ||
| 88 | } | ||
| 89 | |||
| 90 | class NcaReader { | ||
| 91 | YUZU_NON_COPYABLE(NcaReader); | ||
| 92 | YUZU_NON_MOVEABLE(NcaReader); | ||
| 93 | |||
| 94 | public: | ||
| 95 | NcaReader(); | ||
| 96 | ~NcaReader(); | ||
| 97 | |||
| 98 | Result Initialize(VirtualFile base_storage, const NcaCryptoConfiguration& crypto_cfg, | ||
| 99 | const NcaCompressionConfiguration& compression_cfg); | ||
| 100 | |||
| 101 | VirtualFile GetSharedBodyStorage(); | ||
| 102 | u32 GetMagic() const; | ||
| 103 | NcaHeader::DistributionType GetDistributionType() const; | ||
| 104 | NcaHeader::ContentType GetContentType() const; | ||
| 105 | u8 GetHeaderSign1KeyGeneration() const; | ||
| 106 | u8 GetKeyGeneration() const; | ||
| 107 | u8 GetKeyIndex() const; | ||
| 108 | u64 GetContentSize() const; | ||
| 109 | u64 GetProgramId() const; | ||
| 110 | u32 GetContentIndex() const; | ||
| 111 | u32 GetSdkAddonVersion() const; | ||
| 112 | void GetRightsId(u8* dst, size_t dst_size) const; | ||
| 113 | bool HasFsInfo(s32 index) const; | ||
| 114 | s32 GetFsCount() const; | ||
| 115 | const Hash& GetFsHeaderHash(s32 index) const; | ||
| 116 | void GetFsHeaderHash(Hash* dst, s32 index) const; | ||
| 117 | void GetFsInfo(NcaHeader::FsInfo* dst, s32 index) const; | ||
| 118 | u64 GetFsOffset(s32 index) const; | ||
| 119 | u64 GetFsEndOffset(s32 index) const; | ||
| 120 | u64 GetFsSize(s32 index) const; | ||
| 121 | void GetEncryptedKey(void* dst, size_t size) const; | ||
| 122 | const void* GetDecryptionKey(s32 index) const; | ||
| 123 | bool HasValidInternalKey() const; | ||
| 124 | bool HasInternalDecryptionKeyForAesHw() const; | ||
| 125 | bool IsSoftwareAesPrioritized() const; | ||
| 126 | void PrioritizeSoftwareAes(); | ||
| 127 | bool IsAvailableSwKey() const; | ||
| 128 | bool HasExternalDecryptionKey() const; | ||
| 129 | const void* GetExternalDecryptionKey() const; | ||
| 130 | void SetExternalDecryptionKey(const void* src, size_t size); | ||
| 131 | void GetRawData(void* dst, size_t dst_size) const; | ||
| 132 | NcaHeader::EncryptionType GetEncryptionType() const; | ||
| 133 | Result ReadHeader(NcaFsHeader* dst, s32 index) const; | ||
| 134 | |||
| 135 | GetDecompressorFunction GetDecompressor() const; | ||
| 136 | |||
| 137 | bool GetHeaderSign1Valid() const; | ||
| 138 | |||
| 139 | void GetHeaderSign2(void* dst, size_t size) const; | ||
| 140 | |||
| 141 | private: | ||
| 142 | NcaHeader m_header; | ||
| 143 | std::array<std::array<u8, NcaCryptoConfiguration::Aes128KeySize>, | ||
| 144 | NcaHeader::DecryptionKey_Count> | ||
| 145 | m_decryption_keys; | ||
| 146 | VirtualFile m_body_storage; | ||
| 147 | VirtualFile m_header_storage; | ||
| 148 | std::array<u8, NcaCryptoConfiguration::Aes128KeySize> m_external_decryption_key; | ||
| 149 | bool m_is_software_aes_prioritized; | ||
| 150 | bool m_is_available_sw_key; | ||
| 151 | NcaHeader::EncryptionType m_header_encryption_type; | ||
| 152 | bool m_is_header_sign1_signature_valid; | ||
| 153 | GetDecompressorFunction m_get_decompressor; | ||
| 154 | }; | ||
| 155 | |||
| 156 | class NcaFsHeaderReader { | ||
| 157 | YUZU_NON_COPYABLE(NcaFsHeaderReader); | ||
| 158 | YUZU_NON_MOVEABLE(NcaFsHeaderReader); | ||
| 159 | |||
| 160 | public: | ||
| 161 | NcaFsHeaderReader() : m_fs_index(-1) { | ||
| 162 | std::memset(std::addressof(m_data), 0, sizeof(m_data)); | ||
| 163 | } | ||
| 164 | |||
| 165 | Result Initialize(const NcaReader& reader, s32 index); | ||
| 166 | bool IsInitialized() const { | ||
| 167 | return m_fs_index >= 0; | ||
| 168 | } | ||
| 169 | |||
| 170 | void GetRawData(void* dst, size_t dst_size) const; | ||
| 171 | |||
| 172 | NcaFsHeader::HashData& GetHashData(); | ||
| 173 | const NcaFsHeader::HashData& GetHashData() const; | ||
| 174 | u16 GetVersion() const; | ||
| 175 | s32 GetFsIndex() const; | ||
| 176 | NcaFsHeader::FsType GetFsType() const; | ||
| 177 | NcaFsHeader::HashType GetHashType() const; | ||
| 178 | NcaFsHeader::EncryptionType GetEncryptionType() const; | ||
| 179 | NcaPatchInfo& GetPatchInfo(); | ||
| 180 | const NcaPatchInfo& GetPatchInfo() const; | ||
| 181 | const NcaAesCtrUpperIv GetAesCtrUpperIv() const; | ||
| 182 | |||
| 183 | bool IsSkipLayerHashEncryption() const; | ||
| 184 | Result GetHashTargetOffset(s64* out) const; | ||
| 185 | |||
| 186 | bool ExistsSparseLayer() const; | ||
| 187 | NcaSparseInfo& GetSparseInfo(); | ||
| 188 | const NcaSparseInfo& GetSparseInfo() const; | ||
| 189 | |||
| 190 | bool ExistsCompressionLayer() const; | ||
| 191 | NcaCompressionInfo& GetCompressionInfo(); | ||
| 192 | const NcaCompressionInfo& GetCompressionInfo() const; | ||
| 193 | |||
| 194 | bool ExistsPatchMetaHashLayer() const; | ||
| 195 | NcaMetaDataHashDataInfo& GetPatchMetaDataHashDataInfo(); | ||
| 196 | const NcaMetaDataHashDataInfo& GetPatchMetaDataHashDataInfo() const; | ||
| 197 | NcaFsHeader::MetaDataHashType GetPatchMetaHashType() const; | ||
| 198 | |||
| 199 | bool ExistsSparseMetaHashLayer() const; | ||
| 200 | NcaMetaDataHashDataInfo& GetSparseMetaDataHashDataInfo(); | ||
| 201 | const NcaMetaDataHashDataInfo& GetSparseMetaDataHashDataInfo() const; | ||
| 202 | NcaFsHeader::MetaDataHashType GetSparseMetaHashType() const; | ||
| 203 | |||
| 204 | private: | ||
| 205 | NcaFsHeader m_data; | ||
| 206 | s32 m_fs_index; | ||
| 207 | }; | ||
| 208 | |||
| 209 | class NcaFileSystemDriver { | ||
| 210 | YUZU_NON_COPYABLE(NcaFileSystemDriver); | ||
| 211 | YUZU_NON_MOVEABLE(NcaFileSystemDriver); | ||
| 212 | |||
| 213 | public: | ||
| 214 | struct StorageContext { | ||
| 215 | bool open_raw_storage; | ||
| 216 | VirtualFile body_substorage; | ||
| 217 | std::shared_ptr<SparseStorage> current_sparse_storage; | ||
| 218 | VirtualFile sparse_storage_meta_storage; | ||
| 219 | std::shared_ptr<SparseStorage> original_sparse_storage; | ||
| 220 | void* external_current_sparse_storage; | ||
| 221 | void* external_original_sparse_storage; | ||
| 222 | VirtualFile aes_ctr_ex_storage_meta_storage; | ||
| 223 | VirtualFile aes_ctr_ex_storage_data_storage; | ||
| 224 | std::shared_ptr<AesCtrCounterExtendedStorage> aes_ctr_ex_storage; | ||
| 225 | VirtualFile indirect_storage_meta_storage; | ||
| 226 | std::shared_ptr<IndirectStorage> indirect_storage; | ||
| 227 | VirtualFile fs_data_storage; | ||
| 228 | VirtualFile compressed_storage_meta_storage; | ||
| 229 | std::shared_ptr<CompressedStorage> compressed_storage; | ||
| 230 | |||
| 231 | VirtualFile patch_layer_info_storage; | ||
| 232 | VirtualFile sparse_layer_info_storage; | ||
| 233 | |||
| 234 | VirtualFile external_original_storage; | ||
| 235 | }; | ||
| 236 | |||
| 237 | private: | ||
| 238 | enum class AlignmentStorageRequirement { | ||
| 239 | CacheBlockSize = 0, | ||
| 240 | None = 1, | ||
| 241 | }; | ||
| 242 | |||
| 243 | public: | ||
| 244 | static Result SetupFsHeaderReader(NcaFsHeaderReader* out, const NcaReader& reader, | ||
| 245 | s32 fs_index); | ||
| 246 | |||
| 247 | public: | ||
| 248 | NcaFileSystemDriver(std::shared_ptr<NcaReader> reader) : m_original_reader(), m_reader(reader) { | ||
| 249 | ASSERT(m_reader != nullptr); | ||
| 250 | } | ||
| 251 | |||
| 252 | NcaFileSystemDriver(std::shared_ptr<NcaReader> original_reader, | ||
| 253 | std::shared_ptr<NcaReader> reader) | ||
| 254 | : m_original_reader(original_reader), m_reader(reader) { | ||
| 255 | ASSERT(m_reader != nullptr); | ||
| 256 | } | ||
| 257 | |||
| 258 | Result OpenStorageWithContext(VirtualFile* out, NcaFsHeaderReader* out_header_reader, | ||
| 259 | s32 fs_index, StorageContext* ctx); | ||
| 260 | |||
| 261 | Result OpenStorage(VirtualFile* out, NcaFsHeaderReader* out_header_reader, s32 fs_index) { | ||
| 262 | // Create a storage context. | ||
| 263 | StorageContext ctx{}; | ||
| 264 | |||
| 265 | // Open the storage. | ||
| 266 | R_RETURN(OpenStorageWithContext(out, out_header_reader, fs_index, std::addressof(ctx))); | ||
| 267 | } | ||
| 268 | |||
| 269 | public: | ||
| 270 | Result CreateStorageByRawStorage(VirtualFile* out, const NcaFsHeaderReader* header_reader, | ||
| 271 | VirtualFile raw_storage, StorageContext* ctx); | ||
| 272 | |||
| 273 | private: | ||
| 274 | Result OpenStorageImpl(VirtualFile* out, NcaFsHeaderReader* out_header_reader, s32 fs_index, | ||
| 275 | StorageContext* ctx); | ||
| 276 | |||
| 277 | Result OpenIndirectableStorageAsOriginal(VirtualFile* out, | ||
| 278 | const NcaFsHeaderReader* header_reader, | ||
| 279 | StorageContext* ctx); | ||
| 280 | |||
| 281 | Result CreateBodySubStorage(VirtualFile* out, s64 offset, s64 size); | ||
| 282 | |||
| 283 | Result CreateAesCtrStorage(VirtualFile* out, VirtualFile base_storage, s64 offset, | ||
| 284 | const NcaAesCtrUpperIv& upper_iv, | ||
| 285 | AlignmentStorageRequirement alignment_storage_requirement); | ||
| 286 | Result CreateAesXtsStorage(VirtualFile* out, VirtualFile base_storage, s64 offset); | ||
| 287 | |||
| 288 | Result CreateSparseStorageMetaStorage(VirtualFile* out, VirtualFile base_storage, s64 offset, | ||
| 289 | const NcaAesCtrUpperIv& upper_iv, | ||
| 290 | const NcaSparseInfo& sparse_info); | ||
| 291 | Result CreateSparseStorageCore(std::shared_ptr<SparseStorage>* out, VirtualFile base_storage, | ||
| 292 | s64 base_size, VirtualFile meta_storage, | ||
| 293 | const NcaSparseInfo& sparse_info, bool external_info); | ||
| 294 | Result CreateSparseStorage(VirtualFile* out, s64* out_fs_data_offset, | ||
| 295 | std::shared_ptr<SparseStorage>* out_sparse_storage, | ||
| 296 | VirtualFile* out_meta_storage, s32 index, | ||
| 297 | const NcaAesCtrUpperIv& upper_iv, const NcaSparseInfo& sparse_info); | ||
| 298 | |||
| 299 | Result CreateSparseStorageMetaStorageWithVerification( | ||
| 300 | VirtualFile* out, VirtualFile* out_verification, VirtualFile base_storage, s64 offset, | ||
| 301 | const NcaAesCtrUpperIv& upper_iv, const NcaSparseInfo& sparse_info, | ||
| 302 | const NcaMetaDataHashDataInfo& meta_data_hash_data_info); | ||
| 303 | Result CreateSparseStorageWithVerification( | ||
| 304 | VirtualFile* out, s64* out_fs_data_offset, | ||
| 305 | std::shared_ptr<SparseStorage>* out_sparse_storage, VirtualFile* out_meta_storage, | ||
| 306 | VirtualFile* out_verification, s32 index, const NcaAesCtrUpperIv& upper_iv, | ||
| 307 | const NcaSparseInfo& sparse_info, const NcaMetaDataHashDataInfo& meta_data_hash_data_info, | ||
| 308 | NcaFsHeader::MetaDataHashType meta_data_hash_type); | ||
| 309 | |||
| 310 | Result CreateAesCtrExStorageMetaStorage(VirtualFile* out, VirtualFile base_storage, s64 offset, | ||
| 311 | NcaFsHeader::EncryptionType encryption_type, | ||
| 312 | const NcaAesCtrUpperIv& upper_iv, | ||
| 313 | const NcaPatchInfo& patch_info); | ||
| 314 | Result CreateAesCtrExStorage(VirtualFile* out, | ||
| 315 | std::shared_ptr<AesCtrCounterExtendedStorage>* out_ext, | ||
| 316 | VirtualFile base_storage, VirtualFile meta_storage, | ||
| 317 | s64 counter_offset, const NcaAesCtrUpperIv& upper_iv, | ||
| 318 | const NcaPatchInfo& patch_info); | ||
| 319 | |||
| 320 | Result CreateIndirectStorageMetaStorage(VirtualFile* out, VirtualFile base_storage, | ||
| 321 | const NcaPatchInfo& patch_info); | ||
| 322 | Result CreateIndirectStorage(VirtualFile* out, std::shared_ptr<IndirectStorage>* out_ind, | ||
| 323 | VirtualFile base_storage, VirtualFile original_data_storage, | ||
| 324 | VirtualFile meta_storage, const NcaPatchInfo& patch_info); | ||
| 325 | |||
| 326 | Result CreatePatchMetaStorage(VirtualFile* out_aes_ctr_ex_meta, VirtualFile* out_indirect_meta, | ||
| 327 | VirtualFile* out_verification, VirtualFile base_storage, | ||
| 328 | s64 offset, const NcaAesCtrUpperIv& upper_iv, | ||
| 329 | const NcaPatchInfo& patch_info, | ||
| 330 | const NcaMetaDataHashDataInfo& meta_data_hash_data_info); | ||
| 331 | |||
| 332 | Result CreateSha256Storage(VirtualFile* out, VirtualFile base_storage, | ||
| 333 | const NcaFsHeader::HashData::HierarchicalSha256Data& sha256_data); | ||
| 334 | |||
| 335 | Result CreateIntegrityVerificationStorage( | ||
| 336 | VirtualFile* out, VirtualFile base_storage, | ||
| 337 | const NcaFsHeader::HashData::IntegrityMetaInfo& meta_info); | ||
| 338 | Result CreateIntegrityVerificationStorageForMeta( | ||
| 339 | VirtualFile* out, VirtualFile* out_verification, VirtualFile base_storage, s64 offset, | ||
| 340 | const NcaMetaDataHashDataInfo& meta_data_hash_data_info); | ||
| 341 | Result CreateIntegrityVerificationStorageImpl( | ||
| 342 | VirtualFile* out, VirtualFile base_storage, | ||
| 343 | const NcaFsHeader::HashData::IntegrityMetaInfo& meta_info, s64 layer_info_offset, | ||
| 344 | int max_data_cache_entries, int max_hash_cache_entries, s8 buffer_level); | ||
| 345 | |||
| 346 | Result CreateRegionSwitchStorage(VirtualFile* out, const NcaFsHeaderReader* header_reader, | ||
| 347 | VirtualFile inside_storage, VirtualFile outside_storage); | ||
| 348 | |||
| 349 | Result CreateCompressedStorage(VirtualFile* out, std::shared_ptr<CompressedStorage>* out_cmp, | ||
| 350 | VirtualFile* out_meta, VirtualFile base_storage, | ||
| 351 | const NcaCompressionInfo& compression_info); | ||
| 352 | |||
| 353 | public: | ||
| 354 | Result CreateCompressedStorage(VirtualFile* out, std::shared_ptr<CompressedStorage>* out_cmp, | ||
| 355 | VirtualFile* out_meta, VirtualFile base_storage, | ||
| 356 | const NcaCompressionInfo& compression_info, | ||
| 357 | GetDecompressorFunction get_decompressor); | ||
| 358 | |||
| 359 | private: | ||
| 360 | std::shared_ptr<NcaReader> m_original_reader; | ||
| 361 | std::shared_ptr<NcaReader> m_reader; | ||
| 362 | }; | ||
| 363 | |||
| 364 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "core/file_sys/fssystem/fssystem_nca_header.h" | ||
| 5 | |||
| 6 | namespace FileSys { | ||
| 7 | |||
| 8 | u8 NcaHeader::GetProperKeyGeneration() const { | ||
| 9 | return std::max(this->key_generation, this->key_generation_2); | ||
| 10 | } | ||
| 11 | |||
| 12 | bool NcaPatchInfo::HasIndirectTable() const { | ||
| 13 | return this->indirect_size != 0; | ||
| 14 | } | ||
| 15 | |||
| 16 | bool NcaPatchInfo::HasAesCtrExTable() const { | ||
| 17 | return this->aes_ctr_ex_size != 0; | ||
| 18 | } | ||
| 19 | |||
| 20 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "common/common_funcs.h" | ||
| 7 | #include "common/common_types.h" | ||
| 8 | #include "common/literals.h" | ||
| 9 | |||
| 10 | #include "core/file_sys/errors.h" | ||
| 11 | #include "core/file_sys/fssystem/fs_types.h" | ||
| 12 | |||
| 13 | namespace FileSys { | ||
| 14 | |||
| 15 | using namespace Common::Literals; | ||
| 16 | |||
| 17 | struct Hash { | ||
| 18 | static constexpr std::size_t Size = 256 / 8; | ||
| 19 | std::array<u8, Size> value; | ||
| 20 | }; | ||
| 21 | static_assert(sizeof(Hash) == Hash::Size); | ||
| 22 | static_assert(std::is_trivial_v<Hash>); | ||
| 23 | |||
| 24 | using NcaDigest = Hash; | ||
| 25 | |||
| 26 | struct NcaHeader { | ||
| 27 | enum class ContentType : u8 { | ||
| 28 | Program = 0, | ||
| 29 | Meta = 1, | ||
| 30 | Control = 2, | ||
| 31 | Manual = 3, | ||
| 32 | Data = 4, | ||
| 33 | PublicData = 5, | ||
| 34 | |||
| 35 | Start = Program, | ||
| 36 | End = PublicData, | ||
| 37 | }; | ||
| 38 | |||
| 39 | enum class DistributionType : u8 { | ||
| 40 | Download = 0, | ||
| 41 | GameCard = 1, | ||
| 42 | |||
| 43 | Start = Download, | ||
| 44 | End = GameCard, | ||
| 45 | }; | ||
| 46 | |||
| 47 | enum class EncryptionType : u8 { | ||
| 48 | Auto = 0, | ||
| 49 | None = 1, | ||
| 50 | }; | ||
| 51 | |||
| 52 | enum DecryptionKey { | ||
| 53 | DecryptionKey_AesXts = 0, | ||
| 54 | DecryptionKey_AesXts1 = DecryptionKey_AesXts, | ||
| 55 | DecryptionKey_AesXts2 = 1, | ||
| 56 | DecryptionKey_AesCtr = 2, | ||
| 57 | DecryptionKey_AesCtrEx = 3, | ||
| 58 | DecryptionKey_AesCtrHw = 4, | ||
| 59 | DecryptionKey_Count, | ||
| 60 | }; | ||
| 61 | |||
| 62 | struct FsInfo { | ||
| 63 | u32 start_sector; | ||
| 64 | u32 end_sector; | ||
| 65 | u32 hash_sectors; | ||
| 66 | u32 reserved; | ||
| 67 | }; | ||
| 68 | static_assert(sizeof(FsInfo) == 0x10); | ||
| 69 | static_assert(std::is_trivial_v<FsInfo>); | ||
| 70 | |||
| 71 | static constexpr u32 Magic0 = Common::MakeMagic('N', 'C', 'A', '0'); | ||
| 72 | static constexpr u32 Magic1 = Common::MakeMagic('N', 'C', 'A', '1'); | ||
| 73 | static constexpr u32 Magic2 = Common::MakeMagic('N', 'C', 'A', '2'); | ||
| 74 | static constexpr u32 Magic3 = Common::MakeMagic('N', 'C', 'A', '3'); | ||
| 75 | |||
| 76 | static constexpr u32 Magic = Magic3; | ||
| 77 | |||
| 78 | static constexpr std::size_t Size = 1_KiB; | ||
| 79 | static constexpr s32 FsCountMax = 4; | ||
| 80 | static constexpr std::size_t HeaderSignCount = 2; | ||
| 81 | static constexpr std::size_t HeaderSignSize = 0x100; | ||
| 82 | static constexpr std::size_t EncryptedKeyAreaSize = 0x100; | ||
| 83 | static constexpr std::size_t SectorSize = 0x200; | ||
| 84 | static constexpr std::size_t SectorShift = 9; | ||
| 85 | static constexpr std::size_t RightsIdSize = 0x10; | ||
| 86 | static constexpr std::size_t XtsBlockSize = 0x200; | ||
| 87 | static constexpr std::size_t CtrBlockSize = 0x10; | ||
| 88 | |||
| 89 | static_assert(SectorSize == (1 << SectorShift)); | ||
| 90 | |||
| 91 | // Data members. | ||
| 92 | std::array<u8, HeaderSignSize> header_sign_1; | ||
| 93 | std::array<u8, HeaderSignSize> header_sign_2; | ||
| 94 | u32 magic; | ||
| 95 | DistributionType distribution_type; | ||
| 96 | ContentType content_type; | ||
| 97 | u8 key_generation; | ||
| 98 | u8 key_index; | ||
| 99 | u64 content_size; | ||
| 100 | u64 program_id; | ||
| 101 | u32 content_index; | ||
| 102 | u32 sdk_addon_version; | ||
| 103 | u8 key_generation_2; | ||
| 104 | u8 header1_signature_key_generation; | ||
| 105 | std::array<u8, 2> reserved_222; | ||
| 106 | std::array<u32, 3> reserved_224; | ||
| 107 | std::array<u8, RightsIdSize> rights_id; | ||
| 108 | std::array<FsInfo, FsCountMax> fs_info; | ||
| 109 | std::array<Hash, FsCountMax> fs_header_hash; | ||
| 110 | std::array<u8, EncryptedKeyAreaSize> encrypted_key_area; | ||
| 111 | |||
| 112 | static constexpr u64 SectorToByte(u32 sector) { | ||
| 113 | return static_cast<u64>(sector) << SectorShift; | ||
| 114 | } | ||
| 115 | |||
| 116 | static constexpr u32 ByteToSector(u64 byte) { | ||
| 117 | return static_cast<u32>(byte >> SectorShift); | ||
| 118 | } | ||
| 119 | |||
| 120 | u8 GetProperKeyGeneration() const; | ||
| 121 | }; | ||
| 122 | static_assert(sizeof(NcaHeader) == NcaHeader::Size); | ||
| 123 | static_assert(std::is_trivial_v<NcaHeader>); | ||
| 124 | |||
| 125 | struct NcaBucketInfo { | ||
| 126 | static constexpr size_t HeaderSize = 0x10; | ||
| 127 | Int64 offset; | ||
| 128 | Int64 size; | ||
| 129 | std::array<u8, HeaderSize> header; | ||
| 130 | }; | ||
| 131 | static_assert(std::is_trivial_v<NcaBucketInfo>); | ||
| 132 | |||
| 133 | struct NcaPatchInfo { | ||
| 134 | static constexpr size_t Size = 0x40; | ||
| 135 | static constexpr size_t Offset = 0x100; | ||
| 136 | |||
| 137 | Int64 indirect_offset; | ||
| 138 | Int64 indirect_size; | ||
| 139 | std::array<u8, NcaBucketInfo::HeaderSize> indirect_header; | ||
| 140 | Int64 aes_ctr_ex_offset; | ||
| 141 | Int64 aes_ctr_ex_size; | ||
| 142 | std::array<u8, NcaBucketInfo::HeaderSize> aes_ctr_ex_header; | ||
| 143 | |||
| 144 | bool HasIndirectTable() const; | ||
| 145 | bool HasAesCtrExTable() const; | ||
| 146 | }; | ||
| 147 | static_assert(std::is_trivial_v<NcaPatchInfo>); | ||
| 148 | |||
| 149 | union NcaAesCtrUpperIv { | ||
| 150 | u64 value; | ||
| 151 | struct { | ||
| 152 | u32 generation; | ||
| 153 | u32 secure_value; | ||
| 154 | } part; | ||
| 155 | }; | ||
| 156 | static_assert(std::is_trivial_v<NcaAesCtrUpperIv>); | ||
| 157 | |||
| 158 | struct NcaSparseInfo { | ||
| 159 | NcaBucketInfo bucket; | ||
| 160 | Int64 physical_offset; | ||
| 161 | u16 generation; | ||
| 162 | std::array<u8, 6> reserved; | ||
| 163 | |||
| 164 | s64 GetPhysicalSize() const { | ||
| 165 | return this->bucket.offset + this->bucket.size; | ||
| 166 | } | ||
| 167 | |||
| 168 | u32 GetGeneration() const { | ||
| 169 | return static_cast<u32>(this->generation) << 16; | ||
| 170 | } | ||
| 171 | |||
| 172 | const NcaAesCtrUpperIv MakeAesCtrUpperIv(NcaAesCtrUpperIv upper_iv) const { | ||
| 173 | NcaAesCtrUpperIv sparse_upper_iv = upper_iv; | ||
| 174 | sparse_upper_iv.part.generation = this->GetGeneration(); | ||
| 175 | return sparse_upper_iv; | ||
| 176 | } | ||
| 177 | }; | ||
| 178 | static_assert(std::is_trivial_v<NcaSparseInfo>); | ||
| 179 | |||
| 180 | struct NcaCompressionInfo { | ||
| 181 | NcaBucketInfo bucket; | ||
| 182 | std::array<u8, 8> resreved; | ||
| 183 | }; | ||
| 184 | static_assert(std::is_trivial_v<NcaCompressionInfo>); | ||
| 185 | |||
| 186 | struct NcaMetaDataHashDataInfo { | ||
| 187 | Int64 offset; | ||
| 188 | Int64 size; | ||
| 189 | Hash hash; | ||
| 190 | }; | ||
| 191 | static_assert(std::is_trivial_v<NcaMetaDataHashDataInfo>); | ||
| 192 | |||
| 193 | struct NcaFsHeader { | ||
| 194 | static constexpr size_t Size = 0x200; | ||
| 195 | static constexpr size_t HashDataOffset = 0x8; | ||
| 196 | |||
| 197 | struct Region { | ||
| 198 | Int64 offset; | ||
| 199 | Int64 size; | ||
| 200 | }; | ||
| 201 | static_assert(std::is_trivial_v<Region>); | ||
| 202 | |||
| 203 | enum class FsType : u8 { | ||
| 204 | RomFs = 0, | ||
| 205 | PartitionFs = 1, | ||
| 206 | }; | ||
| 207 | |||
| 208 | enum class EncryptionType : u8 { | ||
| 209 | Auto = 0, | ||
| 210 | None = 1, | ||
| 211 | AesXts = 2, | ||
| 212 | AesCtr = 3, | ||
| 213 | AesCtrEx = 4, | ||
| 214 | AesCtrSkipLayerHash = 5, | ||
| 215 | AesCtrExSkipLayerHash = 6, | ||
| 216 | }; | ||
| 217 | |||
| 218 | enum class HashType : u8 { | ||
| 219 | Auto = 0, | ||
| 220 | None = 1, | ||
| 221 | HierarchicalSha256Hash = 2, | ||
| 222 | HierarchicalIntegrityHash = 3, | ||
| 223 | AutoSha3 = 4, | ||
| 224 | HierarchicalSha3256Hash = 5, | ||
| 225 | HierarchicalIntegritySha3Hash = 6, | ||
| 226 | }; | ||
| 227 | |||
| 228 | enum class MetaDataHashType : u8 { | ||
| 229 | None = 0, | ||
| 230 | HierarchicalIntegrity = 1, | ||
| 231 | }; | ||
| 232 | |||
| 233 | union HashData { | ||
| 234 | struct HierarchicalSha256Data { | ||
| 235 | static constexpr size_t HashLayerCountMax = 5; | ||
| 236 | static const size_t MasterHashOffset; | ||
| 237 | |||
| 238 | Hash fs_data_master_hash; | ||
| 239 | s32 hash_block_size; | ||
| 240 | s32 hash_layer_count; | ||
| 241 | std::array<Region, HashLayerCountMax> hash_layer_region; | ||
| 242 | } hierarchical_sha256_data; | ||
| 243 | static_assert(std::is_trivial_v<HierarchicalSha256Data>); | ||
| 244 | |||
| 245 | struct IntegrityMetaInfo { | ||
| 246 | static const size_t MasterHashOffset; | ||
| 247 | |||
| 248 | u32 magic; | ||
| 249 | u32 version; | ||
| 250 | u32 master_hash_size; | ||
| 251 | |||
| 252 | struct LevelHashInfo { | ||
| 253 | u32 max_layers; | ||
| 254 | |||
| 255 | struct HierarchicalIntegrityVerificationLevelInformation { | ||
| 256 | static constexpr size_t IntegrityMaxLayerCount = 7; | ||
| 257 | Int64 offset; | ||
| 258 | Int64 size; | ||
| 259 | s32 block_order; | ||
| 260 | std::array<u8, 4> reserved; | ||
| 261 | }; | ||
| 262 | std::array< | ||
| 263 | HierarchicalIntegrityVerificationLevelInformation, | ||
| 264 | HierarchicalIntegrityVerificationLevelInformation::IntegrityMaxLayerCount - 1> | ||
| 265 | info; | ||
| 266 | |||
| 267 | struct SignatureSalt { | ||
| 268 | static constexpr size_t Size = 0x20; | ||
| 269 | std::array<u8, Size> value; | ||
| 270 | }; | ||
| 271 | SignatureSalt seed; | ||
| 272 | } level_hash_info; | ||
| 273 | |||
| 274 | Hash master_hash; | ||
| 275 | } integrity_meta_info; | ||
| 276 | static_assert(std::is_trivial_v<IntegrityMetaInfo>); | ||
| 277 | |||
| 278 | std::array<u8, NcaPatchInfo::Offset - HashDataOffset> padding; | ||
| 279 | }; | ||
| 280 | |||
| 281 | u16 version; | ||
| 282 | FsType fs_type; | ||
| 283 | HashType hash_type; | ||
| 284 | EncryptionType encryption_type; | ||
| 285 | MetaDataHashType meta_data_hash_type; | ||
| 286 | std::array<u8, 2> reserved; | ||
| 287 | HashData hash_data; | ||
| 288 | NcaPatchInfo patch_info; | ||
| 289 | NcaAesCtrUpperIv aes_ctr_upper_iv; | ||
| 290 | NcaSparseInfo sparse_info; | ||
| 291 | NcaCompressionInfo compression_info; | ||
| 292 | NcaMetaDataHashDataInfo meta_data_hash_data_info; | ||
| 293 | std::array<u8, 0x30> pad; | ||
| 294 | |||
| 295 | bool IsSkipLayerHashEncryption() const { | ||
| 296 | return this->encryption_type == EncryptionType::AesCtrSkipLayerHash || | ||
| 297 | this->encryption_type == EncryptionType::AesCtrExSkipLayerHash; | ||
| 298 | } | ||
| 299 | |||
| 300 | Result GetHashTargetOffset(s64* out) const { | ||
| 301 | switch (this->hash_type) { | ||
| 302 | case HashType::HierarchicalIntegrityHash: | ||
| 303 | case HashType::HierarchicalIntegritySha3Hash: | ||
| 304 | *out = this->hash_data.integrity_meta_info.level_hash_info | ||
| 305 | .info[this->hash_data.integrity_meta_info.level_hash_info.max_layers - 2] | ||
| 306 | .offset; | ||
| 307 | R_SUCCEED(); | ||
| 308 | case HashType::HierarchicalSha256Hash: | ||
| 309 | case HashType::HierarchicalSha3256Hash: | ||
| 310 | *out = | ||
| 311 | this->hash_data.hierarchical_sha256_data | ||
| 312 | .hash_layer_region[this->hash_data.hierarchical_sha256_data.hash_layer_count - | ||
| 313 | 1] | ||
| 314 | .offset; | ||
| 315 | R_SUCCEED(); | ||
| 316 | default: | ||
| 317 | R_THROW(ResultInvalidNcaFsHeader); | ||
| 318 | } | ||
| 319 | } | ||
| 320 | }; | ||
| 321 | static_assert(sizeof(NcaFsHeader) == NcaFsHeader::Size); | ||
| 322 | static_assert(std::is_trivial_v<NcaFsHeader>); | ||
| 323 | static_assert(offsetof(NcaFsHeader, patch_info) == NcaPatchInfo::Offset); | ||
| 324 | |||
| 325 | inline constexpr const size_t NcaFsHeader::HashData::HierarchicalSha256Data::MasterHashOffset = | ||
| 326 | offsetof(NcaFsHeader, hash_data.hierarchical_sha256_data.fs_data_master_hash); | ||
| 327 | inline constexpr const size_t NcaFsHeader::HashData::IntegrityMetaInfo::MasterHashOffset = | ||
| 328 | offsetof(NcaFsHeader, hash_data.integrity_meta_info.master_hash); | ||
| 329 | |||
| 330 | struct NcaMetaDataHashData { | ||
| 331 | s64 layer_info_offset; | ||
| 332 | NcaFsHeader::HashData::IntegrityMetaInfo integrity_meta_info; | ||
| 333 | }; | ||
| 334 | static_assert(sizeof(NcaMetaDataHashData) == | ||
| 335 | sizeof(NcaFsHeader::HashData::IntegrityMetaInfo) + sizeof(s64)); | ||
| 336 | static_assert(std::is_trivial_v<NcaMetaDataHashData>); | ||
| 337 | |||
| 338 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "core/file_sys/fssystem/fssystem_aes_xts_storage.h" | ||
| 5 | #include "core/file_sys/fssystem/fssystem_nca_file_system_driver.h" | ||
| 6 | #include "core/file_sys/vfs_offset.h" | ||
| 7 | |||
| 8 | namespace FileSys { | ||
| 9 | |||
| 10 | namespace { | ||
| 11 | |||
| 12 | constexpr inline u32 SdkAddonVersionMin = 0x000B0000; | ||
| 13 | constexpr inline size_t Aes128KeySize = 0x10; | ||
| 14 | constexpr const std::array<u8, Aes128KeySize> ZeroKey{}; | ||
| 15 | |||
| 16 | constexpr Result CheckNcaMagic(u32 magic) { | ||
| 17 | // Verify the magic is not a deprecated one. | ||
| 18 | R_UNLESS(magic != NcaHeader::Magic0, ResultUnsupportedSdkVersion); | ||
| 19 | R_UNLESS(magic != NcaHeader::Magic1, ResultUnsupportedSdkVersion); | ||
| 20 | R_UNLESS(magic != NcaHeader::Magic2, ResultUnsupportedSdkVersion); | ||
| 21 | |||
| 22 | // Verify the magic is the current one. | ||
| 23 | R_UNLESS(magic == NcaHeader::Magic3, ResultInvalidNcaSignature); | ||
| 24 | |||
| 25 | R_SUCCEED(); | ||
| 26 | } | ||
| 27 | |||
| 28 | } // namespace | ||
| 29 | |||
| 30 | NcaReader::NcaReader() | ||
| 31 | : m_body_storage(), m_header_storage(), m_is_software_aes_prioritized(false), | ||
| 32 | m_is_available_sw_key(false), m_header_encryption_type(NcaHeader::EncryptionType::Auto), | ||
| 33 | m_get_decompressor() { | ||
| 34 | std::memset(std::addressof(m_header), 0, sizeof(m_header)); | ||
| 35 | std::memset(std::addressof(m_decryption_keys), 0, sizeof(m_decryption_keys)); | ||
| 36 | std::memset(std::addressof(m_external_decryption_key), 0, sizeof(m_external_decryption_key)); | ||
| 37 | } | ||
| 38 | |||
| 39 | NcaReader::~NcaReader() {} | ||
| 40 | |||
| 41 | Result NcaReader::Initialize(VirtualFile base_storage, const NcaCryptoConfiguration& crypto_cfg, | ||
| 42 | const NcaCompressionConfiguration& compression_cfg) { | ||
| 43 | // Validate preconditions. | ||
| 44 | ASSERT(base_storage != nullptr); | ||
| 45 | ASSERT(m_body_storage == nullptr); | ||
| 46 | |||
| 47 | // Create the work header storage storage. | ||
| 48 | VirtualFile work_header_storage; | ||
| 49 | |||
| 50 | // We need to be able to generate keys. | ||
| 51 | R_UNLESS(crypto_cfg.generate_key != nullptr, ResultInvalidArgument); | ||
| 52 | |||
| 53 | // Generate keys for header. | ||
| 54 | using AesXtsStorageForNcaHeader = AesXtsStorage; | ||
| 55 | |||
| 56 | constexpr std::array<s32, NcaCryptoConfiguration::HeaderEncryptionKeyCount> | ||
| 57 | HeaderKeyTypeValues = { | ||
| 58 | static_cast<s32>(KeyType::NcaHeaderKey1), | ||
| 59 | static_cast<s32>(KeyType::NcaHeaderKey2), | ||
| 60 | }; | ||
| 61 | |||
| 62 | std::array<std::array<u8, NcaCryptoConfiguration::Aes128KeySize>, | ||
| 63 | NcaCryptoConfiguration::HeaderEncryptionKeyCount> | ||
| 64 | header_decryption_keys; | ||
| 65 | for (size_t i = 0; i < NcaCryptoConfiguration::HeaderEncryptionKeyCount; i++) { | ||
| 66 | crypto_cfg.generate_key(header_decryption_keys[i].data(), | ||
| 67 | AesXtsStorageForNcaHeader::KeySize, | ||
| 68 | crypto_cfg.header_encrypted_encryption_keys[i].data(), | ||
| 69 | AesXtsStorageForNcaHeader::KeySize, HeaderKeyTypeValues[i]); | ||
| 70 | } | ||
| 71 | |||
| 72 | // Create the header storage. | ||
| 73 | std::array<u8, AesXtsStorageForNcaHeader::IvSize> header_iv = {}; | ||
| 74 | work_header_storage = std::make_unique<AesXtsStorageForNcaHeader>( | ||
| 75 | base_storage, header_decryption_keys[0].data(), header_decryption_keys[1].data(), | ||
| 76 | AesXtsStorageForNcaHeader::KeySize, header_iv.data(), AesXtsStorageForNcaHeader::IvSize, | ||
| 77 | NcaHeader::XtsBlockSize); | ||
| 78 | |||
| 79 | // Check that we successfully created the storage. | ||
| 80 | R_UNLESS(work_header_storage != nullptr, ResultAllocationMemoryFailedInNcaReaderA); | ||
| 81 | |||
| 82 | // Read the header. | ||
| 83 | work_header_storage->ReadObject(std::addressof(m_header), 0); | ||
| 84 | |||
| 85 | // Validate the magic. | ||
| 86 | if (const Result magic_result = CheckNcaMagic(m_header.magic); R_FAILED(magic_result)) { | ||
| 87 | // Try to use a plaintext header. | ||
| 88 | base_storage->ReadObject(std::addressof(m_header), 0); | ||
| 89 | R_UNLESS(R_SUCCEEDED(CheckNcaMagic(m_header.magic)), magic_result); | ||
| 90 | |||
| 91 | // Configure to use the plaintext header. | ||
| 92 | auto base_storage_size = base_storage->GetSize(); | ||
| 93 | work_header_storage = std::make_shared<OffsetVfsFile>(base_storage, base_storage_size, 0); | ||
| 94 | R_UNLESS(work_header_storage != nullptr, ResultAllocationMemoryFailedInNcaReaderA); | ||
| 95 | |||
| 96 | // Set encryption type as plaintext. | ||
| 97 | m_header_encryption_type = NcaHeader::EncryptionType::None; | ||
| 98 | } | ||
| 99 | |||
| 100 | // Verify the header sign1. | ||
| 101 | if (crypto_cfg.verify_sign1 != nullptr) { | ||
| 102 | const u8* sig = m_header.header_sign_1.data(); | ||
| 103 | const size_t sig_size = NcaHeader::HeaderSignSize; | ||
| 104 | const u8* msg = | ||
| 105 | static_cast<const u8*>(static_cast<const void*>(std::addressof(m_header.magic))); | ||
| 106 | const size_t msg_size = | ||
| 107 | NcaHeader::Size - NcaHeader::HeaderSignSize * NcaHeader::HeaderSignCount; | ||
| 108 | |||
| 109 | m_is_header_sign1_signature_valid = crypto_cfg.verify_sign1( | ||
| 110 | sig, sig_size, msg, msg_size, m_header.header1_signature_key_generation); | ||
| 111 | |||
| 112 | if (!m_is_header_sign1_signature_valid) { | ||
| 113 | LOG_WARNING(Common_Filesystem, "Invalid NCA header sign1"); | ||
| 114 | } | ||
| 115 | } | ||
| 116 | |||
| 117 | // Validate the sdk version. | ||
| 118 | R_UNLESS(m_header.sdk_addon_version >= SdkAddonVersionMin, ResultUnsupportedSdkVersion); | ||
| 119 | |||
| 120 | // Validate the key index. | ||
| 121 | R_UNLESS(m_header.key_index < NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount || | ||
| 122 | m_header.key_index == NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexZeroKey, | ||
| 123 | ResultInvalidNcaKeyIndex); | ||
| 124 | |||
| 125 | // Check if we have a rights id. | ||
| 126 | constexpr const std::array<u8, NcaHeader::RightsIdSize> ZeroRightsId{}; | ||
| 127 | if (std::memcmp(ZeroRightsId.data(), m_header.rights_id.data(), NcaHeader::RightsIdSize) == 0) { | ||
| 128 | // If we don't, then we don't have an external key, so we need to generate decryption keys. | ||
| 129 | crypto_cfg.generate_key( | ||
| 130 | m_decryption_keys[NcaHeader::DecryptionKey_AesCtr].data(), Aes128KeySize, | ||
| 131 | m_header.encrypted_key_area.data() + NcaHeader::DecryptionKey_AesCtr * Aes128KeySize, | ||
| 132 | Aes128KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration())); | ||
| 133 | crypto_cfg.generate_key( | ||
| 134 | m_decryption_keys[NcaHeader::DecryptionKey_AesXts1].data(), Aes128KeySize, | ||
| 135 | m_header.encrypted_key_area.data() + NcaHeader::DecryptionKey_AesXts1 * Aes128KeySize, | ||
| 136 | Aes128KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration())); | ||
| 137 | crypto_cfg.generate_key( | ||
| 138 | m_decryption_keys[NcaHeader::DecryptionKey_AesXts2].data(), Aes128KeySize, | ||
| 139 | m_header.encrypted_key_area.data() + NcaHeader::DecryptionKey_AesXts2 * Aes128KeySize, | ||
| 140 | Aes128KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration())); | ||
| 141 | crypto_cfg.generate_key( | ||
| 142 | m_decryption_keys[NcaHeader::DecryptionKey_AesCtrEx].data(), Aes128KeySize, | ||
| 143 | m_header.encrypted_key_area.data() + NcaHeader::DecryptionKey_AesCtrEx * Aes128KeySize, | ||
| 144 | Aes128KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration())); | ||
| 145 | |||
| 146 | // Copy the hardware speed emulation key. | ||
| 147 | std::memcpy(m_decryption_keys[NcaHeader::DecryptionKey_AesCtrHw].data(), | ||
| 148 | m_header.encrypted_key_area.data() + | ||
| 149 | NcaHeader::DecryptionKey_AesCtrHw * Aes128KeySize, | ||
| 150 | Aes128KeySize); | ||
| 151 | } | ||
| 152 | |||
| 153 | // Clear the external decryption key. | ||
| 154 | std::memset(m_external_decryption_key.data(), 0, m_external_decryption_key.size()); | ||
| 155 | |||
| 156 | // Set software key availability. | ||
| 157 | m_is_available_sw_key = crypto_cfg.is_available_sw_key; | ||
| 158 | |||
| 159 | // Set our decompressor function getter. | ||
| 160 | m_get_decompressor = compression_cfg.get_decompressor; | ||
| 161 | |||
| 162 | // Set our storages. | ||
| 163 | m_header_storage = std::move(work_header_storage); | ||
| 164 | m_body_storage = std::move(base_storage); | ||
| 165 | |||
| 166 | R_SUCCEED(); | ||
| 167 | } | ||
| 168 | |||
| 169 | VirtualFile NcaReader::GetSharedBodyStorage() { | ||
| 170 | ASSERT(m_body_storage != nullptr); | ||
| 171 | return m_body_storage; | ||
| 172 | } | ||
| 173 | |||
| 174 | u32 NcaReader::GetMagic() const { | ||
| 175 | ASSERT(m_body_storage != nullptr); | ||
| 176 | return m_header.magic; | ||
| 177 | } | ||
| 178 | |||
| 179 | NcaHeader::DistributionType NcaReader::GetDistributionType() const { | ||
| 180 | ASSERT(m_body_storage != nullptr); | ||
| 181 | return m_header.distribution_type; | ||
| 182 | } | ||
| 183 | |||
| 184 | NcaHeader::ContentType NcaReader::GetContentType() const { | ||
| 185 | ASSERT(m_body_storage != nullptr); | ||
| 186 | return m_header.content_type; | ||
| 187 | } | ||
| 188 | |||
| 189 | u8 NcaReader::GetHeaderSign1KeyGeneration() const { | ||
| 190 | ASSERT(m_body_storage != nullptr); | ||
| 191 | return m_header.header1_signature_key_generation; | ||
| 192 | } | ||
| 193 | |||
| 194 | u8 NcaReader::GetKeyGeneration() const { | ||
| 195 | ASSERT(m_body_storage != nullptr); | ||
| 196 | return m_header.GetProperKeyGeneration(); | ||
| 197 | } | ||
| 198 | |||
| 199 | u8 NcaReader::GetKeyIndex() const { | ||
| 200 | ASSERT(m_body_storage != nullptr); | ||
| 201 | return m_header.key_index; | ||
| 202 | } | ||
| 203 | |||
| 204 | u64 NcaReader::GetContentSize() const { | ||
| 205 | ASSERT(m_body_storage != nullptr); | ||
| 206 | return m_header.content_size; | ||
| 207 | } | ||
| 208 | |||
| 209 | u64 NcaReader::GetProgramId() const { | ||
| 210 | ASSERT(m_body_storage != nullptr); | ||
| 211 | return m_header.program_id; | ||
| 212 | } | ||
| 213 | |||
| 214 | u32 NcaReader::GetContentIndex() const { | ||
| 215 | ASSERT(m_body_storage != nullptr); | ||
| 216 | return m_header.content_index; | ||
| 217 | } | ||
| 218 | |||
| 219 | u32 NcaReader::GetSdkAddonVersion() const { | ||
| 220 | ASSERT(m_body_storage != nullptr); | ||
| 221 | return m_header.sdk_addon_version; | ||
| 222 | } | ||
| 223 | |||
| 224 | void NcaReader::GetRightsId(u8* dst, size_t dst_size) const { | ||
| 225 | ASSERT(dst != nullptr); | ||
| 226 | ASSERT(dst_size >= NcaHeader::RightsIdSize); | ||
| 227 | |||
| 228 | std::memcpy(dst, m_header.rights_id.data(), NcaHeader::RightsIdSize); | ||
| 229 | } | ||
| 230 | |||
| 231 | bool NcaReader::HasFsInfo(s32 index) const { | ||
| 232 | ASSERT(0 <= index && index < NcaHeader::FsCountMax); | ||
| 233 | return m_header.fs_info[index].start_sector != 0 || m_header.fs_info[index].end_sector != 0; | ||
| 234 | } | ||
| 235 | |||
| 236 | s32 NcaReader::GetFsCount() const { | ||
| 237 | ASSERT(m_body_storage != nullptr); | ||
| 238 | for (s32 i = 0; i < NcaHeader::FsCountMax; i++) { | ||
| 239 | if (!this->HasFsInfo(i)) { | ||
| 240 | return i; | ||
| 241 | } | ||
| 242 | } | ||
| 243 | return NcaHeader::FsCountMax; | ||
| 244 | } | ||
| 245 | |||
| 246 | const Hash& NcaReader::GetFsHeaderHash(s32 index) const { | ||
| 247 | ASSERT(m_body_storage != nullptr); | ||
| 248 | ASSERT(0 <= index && index < NcaHeader::FsCountMax); | ||
| 249 | return m_header.fs_header_hash[index]; | ||
| 250 | } | ||
| 251 | |||
| 252 | void NcaReader::GetFsHeaderHash(Hash* dst, s32 index) const { | ||
| 253 | ASSERT(m_body_storage != nullptr); | ||
| 254 | ASSERT(0 <= index && index < NcaHeader::FsCountMax); | ||
| 255 | ASSERT(dst != nullptr); | ||
| 256 | std::memcpy(dst, std::addressof(m_header.fs_header_hash[index]), sizeof(*dst)); | ||
| 257 | } | ||
| 258 | |||
| 259 | void NcaReader::GetFsInfo(NcaHeader::FsInfo* dst, s32 index) const { | ||
| 260 | ASSERT(m_body_storage != nullptr); | ||
| 261 | ASSERT(0 <= index && index < NcaHeader::FsCountMax); | ||
| 262 | ASSERT(dst != nullptr); | ||
| 263 | std::memcpy(dst, std::addressof(m_header.fs_info[index]), sizeof(*dst)); | ||
| 264 | } | ||
| 265 | |||
| 266 | u64 NcaReader::GetFsOffset(s32 index) const { | ||
| 267 | ASSERT(m_body_storage != nullptr); | ||
| 268 | ASSERT(0 <= index && index < NcaHeader::FsCountMax); | ||
| 269 | return NcaHeader::SectorToByte(m_header.fs_info[index].start_sector); | ||
| 270 | } | ||
| 271 | |||
| 272 | u64 NcaReader::GetFsEndOffset(s32 index) const { | ||
| 273 | ASSERT(m_body_storage != nullptr); | ||
| 274 | ASSERT(0 <= index && index < NcaHeader::FsCountMax); | ||
| 275 | return NcaHeader::SectorToByte(m_header.fs_info[index].end_sector); | ||
| 276 | } | ||
| 277 | |||
| 278 | u64 NcaReader::GetFsSize(s32 index) const { | ||
| 279 | ASSERT(m_body_storage != nullptr); | ||
| 280 | ASSERT(0 <= index && index < NcaHeader::FsCountMax); | ||
| 281 | return NcaHeader::SectorToByte(m_header.fs_info[index].end_sector - | ||
| 282 | m_header.fs_info[index].start_sector); | ||
| 283 | } | ||
| 284 | |||
| 285 | void NcaReader::GetEncryptedKey(void* dst, size_t size) const { | ||
| 286 | ASSERT(m_body_storage != nullptr); | ||
| 287 | ASSERT(dst != nullptr); | ||
| 288 | ASSERT(size >= NcaHeader::EncryptedKeyAreaSize); | ||
| 289 | |||
| 290 | std::memcpy(dst, m_header.encrypted_key_area.data(), NcaHeader::EncryptedKeyAreaSize); | ||
| 291 | } | ||
| 292 | |||
| 293 | const void* NcaReader::GetDecryptionKey(s32 index) const { | ||
| 294 | ASSERT(m_body_storage != nullptr); | ||
| 295 | ASSERT(0 <= index && index < NcaHeader::DecryptionKey_Count); | ||
| 296 | return m_decryption_keys[index].data(); | ||
| 297 | } | ||
| 298 | |||
| 299 | bool NcaReader::HasValidInternalKey() const { | ||
| 300 | for (s32 i = 0; i < NcaHeader::DecryptionKey_Count; i++) { | ||
| 301 | if (std::memcmp(ZeroKey.data(), m_header.encrypted_key_area.data() + i * Aes128KeySize, | ||
| 302 | Aes128KeySize) != 0) { | ||
| 303 | return true; | ||
| 304 | } | ||
| 305 | } | ||
| 306 | return false; | ||
| 307 | } | ||
| 308 | |||
| 309 | bool NcaReader::HasInternalDecryptionKeyForAesHw() const { | ||
| 310 | return std::memcmp(ZeroKey.data(), this->GetDecryptionKey(NcaHeader::DecryptionKey_AesCtrHw), | ||
| 311 | Aes128KeySize) != 0; | ||
| 312 | } | ||
| 313 | |||
| 314 | bool NcaReader::IsSoftwareAesPrioritized() const { | ||
| 315 | return m_is_software_aes_prioritized; | ||
| 316 | } | ||
| 317 | |||
| 318 | void NcaReader::PrioritizeSoftwareAes() { | ||
| 319 | m_is_software_aes_prioritized = true; | ||
| 320 | } | ||
| 321 | |||
| 322 | bool NcaReader::IsAvailableSwKey() const { | ||
| 323 | return m_is_available_sw_key; | ||
| 324 | } | ||
| 325 | |||
| 326 | bool NcaReader::HasExternalDecryptionKey() const { | ||
| 327 | return std::memcmp(ZeroKey.data(), this->GetExternalDecryptionKey(), Aes128KeySize) != 0; | ||
| 328 | } | ||
| 329 | |||
| 330 | const void* NcaReader::GetExternalDecryptionKey() const { | ||
| 331 | return m_external_decryption_key.data(); | ||
| 332 | } | ||
| 333 | |||
| 334 | void NcaReader::SetExternalDecryptionKey(const void* src, size_t size) { | ||
| 335 | ASSERT(src != nullptr); | ||
| 336 | ASSERT(size == sizeof(m_external_decryption_key)); | ||
| 337 | |||
| 338 | std::memcpy(m_external_decryption_key.data(), src, sizeof(m_external_decryption_key)); | ||
| 339 | } | ||
| 340 | |||
| 341 | void NcaReader::GetRawData(void* dst, size_t dst_size) const { | ||
| 342 | ASSERT(m_body_storage != nullptr); | ||
| 343 | ASSERT(dst != nullptr); | ||
| 344 | ASSERT(dst_size >= sizeof(NcaHeader)); | ||
| 345 | |||
| 346 | std::memcpy(dst, std::addressof(m_header), sizeof(NcaHeader)); | ||
| 347 | } | ||
| 348 | |||
| 349 | GetDecompressorFunction NcaReader::GetDecompressor() const { | ||
| 350 | ASSERT(m_get_decompressor != nullptr); | ||
| 351 | return m_get_decompressor; | ||
| 352 | } | ||
| 353 | |||
| 354 | NcaHeader::EncryptionType NcaReader::GetEncryptionType() const { | ||
| 355 | return m_header_encryption_type; | ||
| 356 | } | ||
| 357 | |||
| 358 | Result NcaReader::ReadHeader(NcaFsHeader* dst, s32 index) const { | ||
| 359 | ASSERT(dst != nullptr); | ||
| 360 | ASSERT(0 <= index && index < NcaHeader::FsCountMax); | ||
| 361 | |||
| 362 | const s64 offset = sizeof(NcaHeader) + sizeof(NcaFsHeader) * index; | ||
| 363 | m_header_storage->ReadObject(dst, offset); | ||
| 364 | |||
| 365 | R_SUCCEED(); | ||
| 366 | } | ||
| 367 | |||
| 368 | bool NcaReader::GetHeaderSign1Valid() const { | ||
| 369 | return m_is_header_sign1_signature_valid; | ||
| 370 | } | ||
| 371 | |||
| 372 | void NcaReader::GetHeaderSign2(void* dst, size_t size) const { | ||
| 373 | ASSERT(dst != nullptr); | ||
| 374 | ASSERT(size == NcaHeader::HeaderSignSize); | ||
| 375 | |||
| 376 | std::memcpy(dst, m_header.header_sign_2.data(), size); | ||
| 377 | } | ||
| 378 | |||
| 379 | Result NcaFsHeaderReader::Initialize(const NcaReader& reader, s32 index) { | ||
| 380 | // Reset ourselves to uninitialized. | ||
| 381 | m_fs_index = -1; | ||
| 382 | |||
| 383 | // Read the header. | ||
| 384 | R_TRY(reader.ReadHeader(std::addressof(m_data), index)); | ||
| 385 | |||
| 386 | // Set our index. | ||
| 387 | m_fs_index = index; | ||
| 388 | R_SUCCEED(); | ||
| 389 | } | ||
| 390 | |||
| 391 | void NcaFsHeaderReader::GetRawData(void* dst, size_t dst_size) const { | ||
| 392 | ASSERT(this->IsInitialized()); | ||
| 393 | ASSERT(dst != nullptr); | ||
| 394 | ASSERT(dst_size >= sizeof(NcaFsHeader)); | ||
| 395 | |||
| 396 | std::memcpy(dst, std::addressof(m_data), sizeof(NcaFsHeader)); | ||
| 397 | } | ||
| 398 | |||
| 399 | NcaFsHeader::HashData& NcaFsHeaderReader::GetHashData() { | ||
| 400 | ASSERT(this->IsInitialized()); | ||
| 401 | return m_data.hash_data; | ||
| 402 | } | ||
| 403 | |||
| 404 | const NcaFsHeader::HashData& NcaFsHeaderReader::GetHashData() const { | ||
| 405 | ASSERT(this->IsInitialized()); | ||
| 406 | return m_data.hash_data; | ||
| 407 | } | ||
| 408 | |||
| 409 | u16 NcaFsHeaderReader::GetVersion() const { | ||
| 410 | ASSERT(this->IsInitialized()); | ||
| 411 | return m_data.version; | ||
| 412 | } | ||
| 413 | |||
| 414 | s32 NcaFsHeaderReader::GetFsIndex() const { | ||
| 415 | ASSERT(this->IsInitialized()); | ||
| 416 | return m_fs_index; | ||
| 417 | } | ||
| 418 | |||
| 419 | NcaFsHeader::FsType NcaFsHeaderReader::GetFsType() const { | ||
| 420 | ASSERT(this->IsInitialized()); | ||
| 421 | return m_data.fs_type; | ||
| 422 | } | ||
| 423 | |||
| 424 | NcaFsHeader::HashType NcaFsHeaderReader::GetHashType() const { | ||
| 425 | ASSERT(this->IsInitialized()); | ||
| 426 | return m_data.hash_type; | ||
| 427 | } | ||
| 428 | |||
| 429 | NcaFsHeader::EncryptionType NcaFsHeaderReader::GetEncryptionType() const { | ||
| 430 | ASSERT(this->IsInitialized()); | ||
| 431 | return m_data.encryption_type; | ||
| 432 | } | ||
| 433 | |||
| 434 | NcaPatchInfo& NcaFsHeaderReader::GetPatchInfo() { | ||
| 435 | ASSERT(this->IsInitialized()); | ||
| 436 | return m_data.patch_info; | ||
| 437 | } | ||
| 438 | |||
| 439 | const NcaPatchInfo& NcaFsHeaderReader::GetPatchInfo() const { | ||
| 440 | ASSERT(this->IsInitialized()); | ||
| 441 | return m_data.patch_info; | ||
| 442 | } | ||
| 443 | |||
| 444 | const NcaAesCtrUpperIv NcaFsHeaderReader::GetAesCtrUpperIv() const { | ||
| 445 | ASSERT(this->IsInitialized()); | ||
| 446 | return m_data.aes_ctr_upper_iv; | ||
| 447 | } | ||
| 448 | |||
| 449 | bool NcaFsHeaderReader::IsSkipLayerHashEncryption() const { | ||
| 450 | ASSERT(this->IsInitialized()); | ||
| 451 | return m_data.IsSkipLayerHashEncryption(); | ||
| 452 | } | ||
| 453 | |||
| 454 | Result NcaFsHeaderReader::GetHashTargetOffset(s64* out) const { | ||
| 455 | ASSERT(out != nullptr); | ||
| 456 | ASSERT(this->IsInitialized()); | ||
| 457 | |||
| 458 | R_RETURN(m_data.GetHashTargetOffset(out)); | ||
| 459 | } | ||
| 460 | |||
| 461 | bool NcaFsHeaderReader::ExistsSparseLayer() const { | ||
| 462 | ASSERT(this->IsInitialized()); | ||
| 463 | return m_data.sparse_info.generation != 0; | ||
| 464 | } | ||
| 465 | |||
| 466 | NcaSparseInfo& NcaFsHeaderReader::GetSparseInfo() { | ||
| 467 | ASSERT(this->IsInitialized()); | ||
| 468 | return m_data.sparse_info; | ||
| 469 | } | ||
| 470 | |||
| 471 | const NcaSparseInfo& NcaFsHeaderReader::GetSparseInfo() const { | ||
| 472 | ASSERT(this->IsInitialized()); | ||
| 473 | return m_data.sparse_info; | ||
| 474 | } | ||
| 475 | |||
| 476 | bool NcaFsHeaderReader::ExistsCompressionLayer() const { | ||
| 477 | ASSERT(this->IsInitialized()); | ||
| 478 | return m_data.compression_info.bucket.offset != 0 && m_data.compression_info.bucket.size != 0; | ||
| 479 | } | ||
| 480 | |||
| 481 | NcaCompressionInfo& NcaFsHeaderReader::GetCompressionInfo() { | ||
| 482 | ASSERT(this->IsInitialized()); | ||
| 483 | return m_data.compression_info; | ||
| 484 | } | ||
| 485 | |||
| 486 | const NcaCompressionInfo& NcaFsHeaderReader::GetCompressionInfo() const { | ||
| 487 | ASSERT(this->IsInitialized()); | ||
| 488 | return m_data.compression_info; | ||
| 489 | } | ||
| 490 | |||
| 491 | bool NcaFsHeaderReader::ExistsPatchMetaHashLayer() const { | ||
| 492 | ASSERT(this->IsInitialized()); | ||
| 493 | return m_data.meta_data_hash_data_info.size != 0 && this->GetPatchInfo().HasIndirectTable(); | ||
| 494 | } | ||
| 495 | |||
| 496 | NcaMetaDataHashDataInfo& NcaFsHeaderReader::GetPatchMetaDataHashDataInfo() { | ||
| 497 | ASSERT(this->IsInitialized()); | ||
| 498 | return m_data.meta_data_hash_data_info; | ||
| 499 | } | ||
| 500 | |||
| 501 | const NcaMetaDataHashDataInfo& NcaFsHeaderReader::GetPatchMetaDataHashDataInfo() const { | ||
| 502 | ASSERT(this->IsInitialized()); | ||
| 503 | return m_data.meta_data_hash_data_info; | ||
| 504 | } | ||
| 505 | |||
| 506 | NcaFsHeader::MetaDataHashType NcaFsHeaderReader::GetPatchMetaHashType() const { | ||
| 507 | ASSERT(this->IsInitialized()); | ||
| 508 | return m_data.meta_data_hash_type; | ||
| 509 | } | ||
| 510 | |||
| 511 | bool NcaFsHeaderReader::ExistsSparseMetaHashLayer() const { | ||
| 512 | ASSERT(this->IsInitialized()); | ||
| 513 | return m_data.meta_data_hash_data_info.size != 0 && this->ExistsSparseLayer(); | ||
| 514 | } | ||
| 515 | |||
| 516 | NcaMetaDataHashDataInfo& NcaFsHeaderReader::GetSparseMetaDataHashDataInfo() { | ||
| 517 | ASSERT(this->IsInitialized()); | ||
| 518 | return m_data.meta_data_hash_data_info; | ||
| 519 | } | ||
| 520 | |||
| 521 | const NcaMetaDataHashDataInfo& NcaFsHeaderReader::GetSparseMetaDataHashDataInfo() const { | ||
| 522 | ASSERT(this->IsInitialized()); | ||
| 523 | return m_data.meta_data_hash_data_info; | ||
| 524 | } | ||
| 525 | |||
| 526 | NcaFsHeader::MetaDataHashType NcaFsHeaderReader::GetSparseMetaHashType() const { | ||
| 527 | ASSERT(this->IsInitialized()); | ||
| 528 | return m_data.meta_data_hash_type; | ||
| 529 | } | ||
| 530 | |||
| 531 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "common/alignment.h" | ||
| 5 | #include "core/file_sys/fssystem/fssystem_pooled_buffer.h" | ||
| 6 | |||
| 7 | namespace FileSys { | ||
| 8 | |||
| 9 | namespace { | ||
| 10 | |||
| 11 | constexpr size_t HeapBlockSize = BufferPoolAlignment; | ||
| 12 | static_assert(HeapBlockSize == 4_KiB); | ||
| 13 | |||
| 14 | // A heap block is 4KiB. An order is a power of two. | ||
| 15 | // This gives blocks of the order 32KiB, 512KiB, 4MiB. | ||
| 16 | constexpr s32 HeapOrderMax = 7; | ||
| 17 | constexpr s32 HeapOrderMaxForLarge = HeapOrderMax + 3; | ||
| 18 | |||
| 19 | constexpr size_t HeapAllocatableSizeMax = HeapBlockSize * (static_cast<size_t>(1) << HeapOrderMax); | ||
| 20 | constexpr size_t HeapAllocatableSizeMaxForLarge = | ||
| 21 | HeapBlockSize * (static_cast<size_t>(1) << HeapOrderMaxForLarge); | ||
| 22 | |||
| 23 | } // namespace | ||
| 24 | |||
| 25 | size_t PooledBuffer::GetAllocatableSizeMaxCore(bool large) { | ||
| 26 | return large ? HeapAllocatableSizeMaxForLarge : HeapAllocatableSizeMax; | ||
| 27 | } | ||
| 28 | |||
| 29 | void PooledBuffer::AllocateCore(size_t ideal_size, size_t required_size, bool large) { | ||
| 30 | // Ensure preconditions. | ||
| 31 | ASSERT(m_buffer == nullptr); | ||
| 32 | |||
| 33 | // Check that we can allocate this size. | ||
| 34 | ASSERT(required_size <= GetAllocatableSizeMaxCore(large)); | ||
| 35 | |||
| 36 | const size_t target_size = | ||
| 37 | std::min(std::max(ideal_size, required_size), GetAllocatableSizeMaxCore(large)); | ||
| 38 | |||
| 39 | // Dummy implementation for allocate. | ||
| 40 | if (target_size > 0) { | ||
| 41 | m_buffer = | ||
| 42 | reinterpret_cast<char*>(::operator new(target_size, std::align_val_t{HeapBlockSize})); | ||
| 43 | m_size = target_size; | ||
| 44 | |||
| 45 | // Ensure postconditions. | ||
| 46 | ASSERT(m_buffer != nullptr); | ||
| 47 | } | ||
| 48 | } | ||
| 49 | |||
| 50 | void PooledBuffer::Shrink(size_t ideal_size) { | ||
| 51 | ASSERT(ideal_size <= GetAllocatableSizeMaxCore(true)); | ||
| 52 | |||
| 53 | // Shrinking to zero means that we have no buffer. | ||
| 54 | if (ideal_size == 0) { | ||
| 55 | ::operator delete(m_buffer, std::align_val_t{HeapBlockSize}); | ||
| 56 | m_buffer = nullptr; | ||
| 57 | m_size = ideal_size; | ||
| 58 | } | ||
| 59 | } | ||
| 60 | |||
| 61 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "common/common_funcs.h" | ||
| 7 | #include "common/common_types.h" | ||
| 8 | #include "common/literals.h" | ||
| 9 | #include "core/hle/result.h" | ||
| 10 | |||
| 11 | namespace FileSys { | ||
| 12 | |||
| 13 | using namespace Common::Literals; | ||
| 14 | |||
| 15 | constexpr inline size_t BufferPoolAlignment = 4_KiB; | ||
| 16 | constexpr inline size_t BufferPoolWorkSize = 320; | ||
| 17 | |||
| 18 | class PooledBuffer { | ||
| 19 | YUZU_NON_COPYABLE(PooledBuffer); | ||
| 20 | |||
| 21 | public: | ||
| 22 | // Constructor/Destructor. | ||
| 23 | constexpr PooledBuffer() : m_buffer(), m_size() {} | ||
| 24 | |||
| 25 | PooledBuffer(size_t ideal_size, size_t required_size) : m_buffer(), m_size() { | ||
| 26 | this->Allocate(ideal_size, required_size); | ||
| 27 | } | ||
| 28 | |||
| 29 | ~PooledBuffer() { | ||
| 30 | this->Deallocate(); | ||
| 31 | } | ||
| 32 | |||
| 33 | // Move and assignment. | ||
| 34 | explicit PooledBuffer(PooledBuffer&& rhs) : m_buffer(rhs.m_buffer), m_size(rhs.m_size) { | ||
| 35 | rhs.m_buffer = nullptr; | ||
| 36 | rhs.m_size = 0; | ||
| 37 | } | ||
| 38 | |||
| 39 | PooledBuffer& operator=(PooledBuffer&& rhs) { | ||
| 40 | PooledBuffer(std::move(rhs)).Swap(*this); | ||
| 41 | return *this; | ||
| 42 | } | ||
| 43 | |||
| 44 | // Allocation API. | ||
| 45 | void Allocate(size_t ideal_size, size_t required_size) { | ||
| 46 | return this->AllocateCore(ideal_size, required_size, false); | ||
| 47 | } | ||
| 48 | |||
| 49 | void AllocateParticularlyLarge(size_t ideal_size, size_t required_size) { | ||
| 50 | return this->AllocateCore(ideal_size, required_size, true); | ||
| 51 | } | ||
| 52 | |||
| 53 | void Shrink(size_t ideal_size); | ||
| 54 | |||
| 55 | void Deallocate() { | ||
| 56 | // Shrink the buffer to empty. | ||
| 57 | this->Shrink(0); | ||
| 58 | ASSERT(m_buffer == nullptr); | ||
| 59 | } | ||
| 60 | |||
| 61 | char* GetBuffer() const { | ||
| 62 | ASSERT(m_buffer != nullptr); | ||
| 63 | return m_buffer; | ||
| 64 | } | ||
| 65 | |||
| 66 | size_t GetSize() const { | ||
| 67 | ASSERT(m_buffer != nullptr); | ||
| 68 | return m_size; | ||
| 69 | } | ||
| 70 | |||
| 71 | public: | ||
| 72 | static size_t GetAllocatableSizeMax() { | ||
| 73 | return GetAllocatableSizeMaxCore(false); | ||
| 74 | } | ||
| 75 | static size_t GetAllocatableParticularlyLargeSizeMax() { | ||
| 76 | return GetAllocatableSizeMaxCore(true); | ||
| 77 | } | ||
| 78 | |||
| 79 | private: | ||
| 80 | static size_t GetAllocatableSizeMaxCore(bool large); | ||
| 81 | |||
| 82 | private: | ||
| 83 | void Swap(PooledBuffer& rhs) { | ||
| 84 | std::swap(m_buffer, rhs.m_buffer); | ||
| 85 | std::swap(m_size, rhs.m_size); | ||
| 86 | } | ||
| 87 | |||
| 88 | void AllocateCore(size_t ideal_size, size_t required_size, bool large); | ||
| 89 | |||
| 90 | private: | ||
| 91 | char* m_buffer; | ||
| 92 | size_t m_size; | ||
| 93 | }; | ||
| 94 | |||
| 95 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "core/file_sys/fssystem/fssystem_sparse_storage.h" | ||
| 5 | |||
| 6 | namespace FileSys { | ||
| 7 | |||
| 8 | size_t SparseStorage::Read(u8* buffer, size_t size, size_t offset) const { | ||
| 9 | // Validate preconditions. | ||
| 10 | ASSERT(this->IsInitialized()); | ||
| 11 | ASSERT(buffer != nullptr); | ||
| 12 | |||
| 13 | // Allow zero size. | ||
| 14 | if (size == 0) { | ||
| 15 | return size; | ||
| 16 | } | ||
| 17 | |||
| 18 | SparseStorage* self = const_cast<SparseStorage*>(this); | ||
| 19 | |||
| 20 | if (self->GetEntryTable().IsEmpty()) { | ||
| 21 | BucketTree::Offsets table_offsets; | ||
| 22 | ASSERT(R_SUCCEEDED(self->GetEntryTable().GetOffsets(std::addressof(table_offsets)))); | ||
| 23 | ASSERT(table_offsets.IsInclude(offset, size)); | ||
| 24 | |||
| 25 | std::memset(buffer, 0, size); | ||
| 26 | } else { | ||
| 27 | self->OperatePerEntry<false, true>( | ||
| 28 | offset, size, | ||
| 29 | [=](VirtualFile storage, s64 data_offset, s64 cur_offset, s64 cur_size) -> Result { | ||
| 30 | storage->Read(reinterpret_cast<u8*>(buffer) + (cur_offset - offset), | ||
| 31 | static_cast<size_t>(cur_size), data_offset); | ||
| 32 | R_SUCCEED(); | ||
| 33 | }); | ||
| 34 | } | ||
| 35 | |||
| 36 | return size; | ||
| 37 | } | ||
| 38 | |||
| 39 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "core/file_sys/fssystem/fssystem_indirect_storage.h" | ||
| 7 | |||
| 8 | namespace FileSys { | ||
| 9 | |||
| 10 | class SparseStorage : public IndirectStorage { | ||
| 11 | YUZU_NON_COPYABLE(SparseStorage); | ||
| 12 | YUZU_NON_MOVEABLE(SparseStorage); | ||
| 13 | |||
| 14 | private: | ||
| 15 | class ZeroStorage : public IReadOnlyStorage { | ||
| 16 | public: | ||
| 17 | ZeroStorage() {} | ||
| 18 | virtual ~ZeroStorage() {} | ||
| 19 | |||
| 20 | virtual size_t GetSize() const override { | ||
| 21 | return std::numeric_limits<size_t>::max(); | ||
| 22 | } | ||
| 23 | |||
| 24 | virtual size_t Read(u8* buffer, size_t size, size_t offset) const override { | ||
| 25 | ASSERT(buffer != nullptr || size == 0); | ||
| 26 | |||
| 27 | if (size > 0) { | ||
| 28 | std::memset(buffer, 0, size); | ||
| 29 | } | ||
| 30 | |||
| 31 | return size; | ||
| 32 | } | ||
| 33 | }; | ||
| 34 | |||
| 35 | public: | ||
| 36 | SparseStorage() : IndirectStorage(), m_zero_storage(std::make_shared<ZeroStorage>()) {} | ||
| 37 | virtual ~SparseStorage() {} | ||
| 38 | |||
| 39 | using IndirectStorage::Initialize; | ||
| 40 | |||
| 41 | void Initialize(s64 end_offset) { | ||
| 42 | this->GetEntryTable().Initialize(NodeSize, end_offset); | ||
| 43 | this->SetZeroStorage(); | ||
| 44 | } | ||
| 45 | |||
| 46 | void SetDataStorage(VirtualFile storage) { | ||
| 47 | ASSERT(this->IsInitialized()); | ||
| 48 | |||
| 49 | this->SetStorage(0, storage); | ||
| 50 | this->SetZeroStorage(); | ||
| 51 | } | ||
| 52 | |||
| 53 | template <typename T> | ||
| 54 | void SetDataStorage(T storage, s64 offset, s64 size) { | ||
| 55 | ASSERT(this->IsInitialized()); | ||
| 56 | |||
| 57 | this->SetStorage(0, storage, offset, size); | ||
| 58 | this->SetZeroStorage(); | ||
| 59 | } | ||
| 60 | |||
| 61 | virtual size_t Read(u8* buffer, size_t size, size_t offset) const override; | ||
| 62 | |||
| 63 | private: | ||
| 64 | void SetZeroStorage() { | ||
| 65 | return this->SetStorage(1, m_zero_storage, 0, std::numeric_limits<s64>::max()); | ||
| 66 | } | ||
| 67 | |||
| 68 | private: | ||
| 69 | VirtualFile m_zero_storage; | ||
| 70 | }; | ||
| 71 | |||
| 72 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "core/file_sys/fssystem/fs_i_storage.h" | ||
| 7 | |||
| 8 | namespace FileSys { | ||
| 9 | |||
| 10 | class RegionSwitchStorage : public IReadOnlyStorage { | ||
| 11 | YUZU_NON_COPYABLE(RegionSwitchStorage); | ||
| 12 | YUZU_NON_MOVEABLE(RegionSwitchStorage); | ||
| 13 | |||
| 14 | public: | ||
| 15 | struct Region { | ||
| 16 | s64 offset; | ||
| 17 | s64 size; | ||
| 18 | }; | ||
| 19 | |||
| 20 | public: | ||
| 21 | RegionSwitchStorage(VirtualFile&& i, VirtualFile&& o, Region r) | ||
| 22 | : m_inside_region_storage(std::move(i)), m_outside_region_storage(std::move(o)), | ||
| 23 | m_region(r) {} | ||
| 24 | |||
| 25 | virtual size_t Read(u8* buffer, size_t size, size_t offset) const override { | ||
| 26 | // Process until we're done. | ||
| 27 | size_t processed = 0; | ||
| 28 | while (processed < size) { | ||
| 29 | // Process on the appropriate storage. | ||
| 30 | s64 cur_size = 0; | ||
| 31 | if (this->CheckRegions(std::addressof(cur_size), offset + processed, | ||
| 32 | size - processed)) { | ||
| 33 | m_inside_region_storage->Read(buffer + processed, cur_size, offset + processed); | ||
| 34 | } else { | ||
| 35 | m_outside_region_storage->Read(buffer + processed, cur_size, offset + processed); | ||
| 36 | } | ||
| 37 | |||
| 38 | // Advance. | ||
| 39 | processed += cur_size; | ||
| 40 | } | ||
| 41 | |||
| 42 | return size; | ||
| 43 | } | ||
| 44 | |||
| 45 | virtual size_t GetSize() const override { | ||
| 46 | return m_inside_region_storage->GetSize(); | ||
| 47 | } | ||
| 48 | |||
| 49 | private: | ||
| 50 | bool CheckRegions(s64* out_current_size, s64 offset, s64 size) const { | ||
| 51 | // Check if our region contains the access. | ||
| 52 | if (m_region.offset <= offset) { | ||
| 53 | if (offset < m_region.offset + m_region.size) { | ||
| 54 | if (m_region.offset + m_region.size <= offset + size) { | ||
| 55 | *out_current_size = m_region.offset + m_region.size - offset; | ||
| 56 | } else { | ||
| 57 | *out_current_size = size; | ||
| 58 | } | ||
| 59 | return true; | ||
| 60 | } else { | ||
| 61 | *out_current_size = size; | ||
| 62 | return false; | ||
| 63 | } | ||
| 64 | } else { | ||
| 65 | if (m_region.offset <= offset + size) { | ||
| 66 | *out_current_size = m_region.offset - offset; | ||
| 67 | } else { | ||
| 68 | *out_current_size = size; | ||
| 69 | } | ||
| 70 | return false; | ||
| 71 | } | ||
| 72 | } | ||
| 73 | |||
| 74 | private: | ||
| 75 | VirtualFile m_inside_region_storage; | ||
| 76 | VirtualFile m_outside_region_storage; | ||
| 77 | Region m_region; | ||
| 78 | }; | ||
| 79 | |||
| 80 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "core/file_sys/fssystem/fssystem_utility.h" | ||
| 5 | |||
| 6 | namespace FileSys { | ||
| 7 | |||
| 8 | void AddCounter(void* counter_, size_t counter_size, u64 value) { | ||
| 9 | u8* counter = static_cast<u8*>(counter_); | ||
| 10 | u64 remaining = value; | ||
| 11 | u8 carry = 0; | ||
| 12 | |||
| 13 | for (size_t i = 0; i < counter_size; i++) { | ||
| 14 | auto sum = counter[counter_size - 1 - i] + (remaining & 0xFF) + carry; | ||
| 15 | carry = static_cast<u8>(sum >> (sizeof(u8) * 8)); | ||
| 16 | auto sum8 = static_cast<u8>(sum & 0xFF); | ||
| 17 | |||
| 18 | counter[counter_size - 1 - i] = sum8; | ||
| 19 | |||
| 20 | remaining >>= (sizeof(u8) * 8); | ||
| 21 | if (carry == 0 && remaining == 0) { | ||
| 22 | break; | ||
| 23 | } | ||
| 24 | } | ||
| 25 | } | ||
| 26 | |||
| 27 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "common/common_funcs.h" | ||
| 7 | |||
| 8 | namespace FileSys { | ||
| 9 | |||
| 10 | void AddCounter(void* counter, size_t counter_size, u64 value); | ||
| 11 | |||
| 12 | } | ||
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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include <algorithm> | ||
| 5 | #include <array> | ||
| 6 | #include <cstddef> | ||
| 7 | #include <cstring> | ||
| 8 | |||
| 9 | #include "common/assert.h" | ||
| 10 | #include "core/crypto/aes_util.h" | ||
| 11 | #include "core/file_sys/nca_patch.h" | ||
| 12 | |||
| 13 | namespace FileSys { | ||
| 14 | namespace { | ||
| 15 | template <bool Subsection, typename BlockType, typename BucketType> | ||
| 16 | std::pair<std::size_t, std::size_t> SearchBucketEntry(u64 offset, const BlockType& block, | ||
| 17 | const BucketType& buckets) { | ||
| 18 | if constexpr (Subsection) { | ||
| 19 | const auto& last_bucket = buckets[block.number_buckets - 1]; | ||
| 20 | if (offset >= last_bucket.entries[last_bucket.number_entries].address_patch) { | ||
| 21 | return {block.number_buckets - 1, last_bucket.number_entries}; | ||
| 22 | } | ||
| 23 | } else { | ||
| 24 | ASSERT_MSG(offset <= block.size, "Offset is out of bounds in BKTR relocation block."); | ||
| 25 | } | ||
| 26 | |||
| 27 | std::size_t bucket_id = std::count_if( | ||
| 28 | block.base_offsets.begin() + 1, block.base_offsets.begin() + block.number_buckets, | ||
| 29 | [&offset](u64 base_offset) { return base_offset <= offset; }); | ||
| 30 | |||
| 31 | const auto& bucket = buckets[bucket_id]; | ||
| 32 | |||
| 33 | if (bucket.number_entries == 1) { | ||
| 34 | return {bucket_id, 0}; | ||
| 35 | } | ||
| 36 | |||
| 37 | std::size_t low = 0; | ||
| 38 | std::size_t mid = 0; | ||
| 39 | std::size_t high = bucket.number_entries - 1; | ||
| 40 | while (low <= high) { | ||
| 41 | mid = (low + high) / 2; | ||
| 42 | if (bucket.entries[mid].address_patch > offset) { | ||
| 43 | high = mid - 1; | ||
| 44 | } else { | ||
| 45 | if (mid == bucket.number_entries - 1 || | ||
| 46 | bucket.entries[mid + 1].address_patch > offset) { | ||
| 47 | return {bucket_id, mid}; | ||
| 48 | } | ||
| 49 | |||
| 50 | low = mid + 1; | ||
| 51 | } | ||
| 52 | } | ||
| 53 | ASSERT_MSG(false, "Offset could not be found in BKTR block."); | ||
| 54 | return {0, 0}; | ||
| 55 | } | ||
| 56 | } // Anonymous namespace | ||
| 57 | |||
| 58 | BKTR::BKTR(VirtualFile base_romfs_, VirtualFile bktr_romfs_, RelocationBlock relocation_, | ||
| 59 | std::vector<RelocationBucket> relocation_buckets_, SubsectionBlock subsection_, | ||
| 60 | std::vector<SubsectionBucket> subsection_buckets_, bool is_encrypted_, | ||
| 61 | Core::Crypto::Key128 key_, u64 base_offset_, u64 ivfc_offset_, | ||
| 62 | std::array<u8, 8> section_ctr_) | ||
| 63 | : relocation(relocation_), relocation_buckets(std::move(relocation_buckets_)), | ||
| 64 | subsection(subsection_), subsection_buckets(std::move(subsection_buckets_)), | ||
| 65 | base_romfs(std::move(base_romfs_)), bktr_romfs(std::move(bktr_romfs_)), | ||
| 66 | encrypted(is_encrypted_), key(key_), base_offset(base_offset_), ivfc_offset(ivfc_offset_), | ||
| 67 | section_ctr(section_ctr_) { | ||
| 68 | for (std::size_t i = 0; i < relocation.number_buckets - 1; ++i) { | ||
| 69 | relocation_buckets[i].entries.push_back({relocation.base_offsets[i + 1], 0, 0}); | ||
| 70 | } | ||
| 71 | |||
| 72 | for (std::size_t i = 0; i < subsection.number_buckets - 1; ++i) { | ||
| 73 | subsection_buckets[i].entries.push_back({subsection_buckets[i + 1].entries[0].address_patch, | ||
| 74 | {0}, | ||
| 75 | subsection_buckets[i + 1].entries[0].ctr}); | ||
| 76 | } | ||
| 77 | |||
| 78 | relocation_buckets.back().entries.push_back({relocation.size, 0, 0}); | ||
| 79 | } | ||
| 80 | |||
| 81 | BKTR::~BKTR() = default; | ||
| 82 | |||
| 83 | std::size_t BKTR::Read(u8* data, std::size_t length, std::size_t offset) const { | ||
| 84 | // Read out of bounds. | ||
| 85 | if (offset >= relocation.size) { | ||
| 86 | return 0; | ||
| 87 | } | ||
| 88 | |||
| 89 | const auto relocation_entry = GetRelocationEntry(offset); | ||
| 90 | const auto section_offset = | ||
| 91 | offset - relocation_entry.address_patch + relocation_entry.address_source; | ||
| 92 | const auto bktr_read = relocation_entry.from_patch; | ||
| 93 | |||
| 94 | const auto next_relocation = GetNextRelocationEntry(offset); | ||
| 95 | |||
| 96 | if (offset + length > next_relocation.address_patch) { | ||
| 97 | const u64 partition = next_relocation.address_patch - offset; | ||
| 98 | return Read(data, partition, offset) + | ||
| 99 | Read(data + partition, length - partition, offset + partition); | ||
| 100 | } | ||
| 101 | |||
| 102 | if (!bktr_read) { | ||
| 103 | ASSERT_MSG(section_offset >= ivfc_offset, "Offset calculation negative."); | ||
| 104 | return base_romfs->Read(data, length, section_offset - ivfc_offset); | ||
| 105 | } | ||
| 106 | |||
| 107 | if (!encrypted) { | ||
| 108 | return bktr_romfs->Read(data, length, section_offset); | ||
| 109 | } | ||
| 110 | |||
| 111 | const auto subsection_entry = GetSubsectionEntry(section_offset); | ||
| 112 | Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(key, Core::Crypto::Mode::CTR); | ||
| 113 | |||
| 114 | // Calculate AES IV | ||
| 115 | std::array<u8, 16> iv{}; | ||
| 116 | auto subsection_ctr = subsection_entry.ctr; | ||
| 117 | auto offset_iv = section_offset + base_offset; | ||
| 118 | for (std::size_t i = 0; i < section_ctr.size(); ++i) { | ||
| 119 | iv[i] = section_ctr[0x8 - i - 1]; | ||
| 120 | } | ||
| 121 | offset_iv >>= 4; | ||
| 122 | for (std::size_t i = 0; i < sizeof(u64); ++i) { | ||
| 123 | iv[0xF - i] = static_cast<u8>(offset_iv & 0xFF); | ||
| 124 | offset_iv >>= 8; | ||
| 125 | } | ||
| 126 | for (std::size_t i = 0; i < sizeof(u32); ++i) { | ||
| 127 | iv[0x7 - i] = static_cast<u8>(subsection_ctr & 0xFF); | ||
| 128 | subsection_ctr >>= 8; | ||
| 129 | } | ||
| 130 | cipher.SetIV(iv); | ||
| 131 | |||
| 132 | const auto next_subsection = GetNextSubsectionEntry(section_offset); | ||
| 133 | |||
| 134 | if (section_offset + length > next_subsection.address_patch) { | ||
| 135 | const u64 partition = next_subsection.address_patch - section_offset; | ||
| 136 | return Read(data, partition, offset) + | ||
| 137 | Read(data + partition, length - partition, offset + partition); | ||
| 138 | } | ||
| 139 | |||
| 140 | const auto block_offset = section_offset & 0xF; | ||
| 141 | if (block_offset != 0) { | ||
| 142 | auto block = bktr_romfs->ReadBytes(0x10, section_offset & ~0xF); | ||
| 143 | cipher.Transcode(block.data(), block.size(), block.data(), Core::Crypto::Op::Decrypt); | ||
| 144 | if (length + block_offset < 0x10) { | ||
| 145 | std::memcpy(data, block.data() + block_offset, std::min(length, block.size())); | ||
| 146 | return std::min(length, block.size()); | ||
| 147 | } | ||
| 148 | |||
| 149 | const auto read = 0x10 - block_offset; | ||
| 150 | std::memcpy(data, block.data() + block_offset, read); | ||
| 151 | return read + Read(data + read, length - read, offset + read); | ||
| 152 | } | ||
| 153 | |||
| 154 | const auto raw_read = bktr_romfs->Read(data, length, section_offset); | ||
| 155 | cipher.Transcode(data, raw_read, data, Core::Crypto::Op::Decrypt); | ||
| 156 | return raw_read; | ||
| 157 | } | ||
| 158 | |||
| 159 | RelocationEntry BKTR::GetRelocationEntry(u64 offset) const { | ||
| 160 | const auto res = SearchBucketEntry<false>(offset, relocation, relocation_buckets); | ||
| 161 | return relocation_buckets[res.first].entries[res.second]; | ||
| 162 | } | ||
| 163 | |||
| 164 | RelocationEntry BKTR::GetNextRelocationEntry(u64 offset) const { | ||
| 165 | const auto res = SearchBucketEntry<false>(offset, relocation, relocation_buckets); | ||
| 166 | const auto bucket = relocation_buckets[res.first]; | ||
| 167 | if (res.second + 1 < bucket.entries.size()) | ||
| 168 | return bucket.entries[res.second + 1]; | ||
| 169 | return relocation_buckets[res.first + 1].entries[0]; | ||
| 170 | } | ||
| 171 | |||
| 172 | SubsectionEntry BKTR::GetSubsectionEntry(u64 offset) const { | ||
| 173 | const auto res = SearchBucketEntry<true>(offset, subsection, subsection_buckets); | ||
| 174 | return subsection_buckets[res.first].entries[res.second]; | ||
| 175 | } | ||
| 176 | |||
| 177 | SubsectionEntry BKTR::GetNextSubsectionEntry(u64 offset) const { | ||
| 178 | const auto res = SearchBucketEntry<true>(offset, subsection, subsection_buckets); | ||
| 179 | const auto bucket = subsection_buckets[res.first]; | ||
| 180 | if (res.second + 1 < bucket.entries.size()) | ||
| 181 | return bucket.entries[res.second + 1]; | ||
| 182 | return subsection_buckets[res.first + 1].entries[0]; | ||
| 183 | } | ||
| 184 | |||
| 185 | std::string BKTR::GetName() const { | ||
| 186 | return base_romfs->GetName(); | ||
| 187 | } | ||
| 188 | |||
| 189 | std::size_t BKTR::GetSize() const { | ||
| 190 | return relocation.size; | ||
| 191 | } | ||
| 192 | |||
| 193 | bool BKTR::Resize(std::size_t new_size) { | ||
| 194 | return false; | ||
| 195 | } | ||
| 196 | |||
| 197 | VirtualDir BKTR::GetContainingDirectory() const { | ||
| 198 | return base_romfs->GetContainingDirectory(); | ||
| 199 | } | ||
| 200 | |||
| 201 | bool BKTR::IsWritable() const { | ||
| 202 | return false; | ||
| 203 | } | ||
| 204 | |||
| 205 | bool BKTR::IsReadable() const { | ||
| 206 | return true; | ||
| 207 | } | ||
| 208 | |||
| 209 | std::size_t BKTR::Write(const u8* data, std::size_t length, std::size_t offset) { | ||
| 210 | return 0; | ||
| 211 | } | ||
| 212 | |||
| 213 | bool BKTR::Rename(std::string_view name) { | ||
| 214 | return base_romfs->Rename(name); | ||
| 215 | } | ||
| 216 | |||
| 217 | } // 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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <array> | ||
| 7 | #include <memory> | ||
| 8 | #include <vector> | ||
| 9 | |||
| 10 | #include "common/common_funcs.h" | ||
| 11 | #include "common/common_types.h" | ||
| 12 | #include "common/swap.h" | ||
| 13 | #include "core/crypto/key_manager.h" | ||
| 14 | |||
| 15 | namespace FileSys { | ||
| 16 | |||
| 17 | #pragma pack(push, 1) | ||
| 18 | struct RelocationEntry { | ||
| 19 | u64_le address_patch; | ||
| 20 | u64_le address_source; | ||
| 21 | u32 from_patch; | ||
| 22 | }; | ||
| 23 | #pragma pack(pop) | ||
| 24 | static_assert(sizeof(RelocationEntry) == 0x14, "RelocationEntry has incorrect size."); | ||
| 25 | |||
| 26 | struct RelocationBucketRaw { | ||
| 27 | INSERT_PADDING_BYTES(4); | ||
| 28 | u32_le number_entries; | ||
| 29 | u64_le end_offset; | ||
| 30 | std::array<RelocationEntry, 0x332> relocation_entries; | ||
| 31 | INSERT_PADDING_BYTES(8); | ||
| 32 | }; | ||
| 33 | static_assert(sizeof(RelocationBucketRaw) == 0x4000, "RelocationBucketRaw has incorrect size."); | ||
| 34 | |||
| 35 | // Vector version of RelocationBucketRaw | ||
| 36 | struct RelocationBucket { | ||
| 37 | u32 number_entries; | ||
| 38 | u64 end_offset; | ||
| 39 | std::vector<RelocationEntry> entries; | ||
| 40 | }; | ||
| 41 | |||
| 42 | struct RelocationBlock { | ||
| 43 | INSERT_PADDING_BYTES(4); | ||
| 44 | u32_le number_buckets; | ||
| 45 | u64_le size; | ||
| 46 | std::array<u64, 0x7FE> base_offsets; | ||
| 47 | }; | ||
| 48 | static_assert(sizeof(RelocationBlock) == 0x4000, "RelocationBlock has incorrect size."); | ||
| 49 | |||
| 50 | struct SubsectionEntry { | ||
| 51 | u64_le address_patch; | ||
| 52 | INSERT_PADDING_BYTES(0x4); | ||
| 53 | u32_le ctr; | ||
| 54 | }; | ||
| 55 | static_assert(sizeof(SubsectionEntry) == 0x10, "SubsectionEntry has incorrect size."); | ||
| 56 | |||
| 57 | struct SubsectionBucketRaw { | ||
| 58 | INSERT_PADDING_BYTES(4); | ||
| 59 | u32_le number_entries; | ||
| 60 | u64_le end_offset; | ||
| 61 | std::array<SubsectionEntry, 0x3FF> subsection_entries; | ||
| 62 | }; | ||
| 63 | static_assert(sizeof(SubsectionBucketRaw) == 0x4000, "SubsectionBucketRaw has incorrect size."); | ||
| 64 | |||
| 65 | // Vector version of SubsectionBucketRaw | ||
| 66 | struct SubsectionBucket { | ||
| 67 | u32 number_entries; | ||
| 68 | u64 end_offset; | ||
| 69 | std::vector<SubsectionEntry> entries; | ||
| 70 | }; | ||
| 71 | |||
| 72 | struct SubsectionBlock { | ||
| 73 | INSERT_PADDING_BYTES(4); | ||
| 74 | u32_le number_buckets; | ||
| 75 | u64_le size; | ||
| 76 | std::array<u64, 0x7FE> base_offsets; | ||
| 77 | }; | ||
| 78 | static_assert(sizeof(SubsectionBlock) == 0x4000, "SubsectionBlock has incorrect size."); | ||
| 79 | |||
| 80 | inline RelocationBucket ConvertRelocationBucketRaw(RelocationBucketRaw raw) { | ||
| 81 | return {raw.number_entries, | ||
| 82 | raw.end_offset, | ||
| 83 | {raw.relocation_entries.begin(), raw.relocation_entries.begin() + raw.number_entries}}; | ||
| 84 | } | ||
| 85 | |||
| 86 | inline SubsectionBucket ConvertSubsectionBucketRaw(SubsectionBucketRaw raw) { | ||
| 87 | return {raw.number_entries, | ||
| 88 | raw.end_offset, | ||
| 89 | {raw.subsection_entries.begin(), raw.subsection_entries.begin() + raw.number_entries}}; | ||
| 90 | } | ||
| 91 | |||
| 92 | class BKTR : public VfsFile { | ||
| 93 | public: | ||
| 94 | BKTR(VirtualFile base_romfs, VirtualFile bktr_romfs, RelocationBlock relocation, | ||
| 95 | std::vector<RelocationBucket> relocation_buckets, SubsectionBlock subsection, | ||
| 96 | std::vector<SubsectionBucket> subsection_buckets, bool is_encrypted, | ||
| 97 | Core::Crypto::Key128 key, u64 base_offset, u64 ivfc_offset, std::array<u8, 8> section_ctr); | ||
| 98 | ~BKTR() override; | ||
| 99 | |||
| 100 | std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override; | ||
| 101 | |||
| 102 | std::string GetName() const override; | ||
| 103 | |||
| 104 | std::size_t GetSize() const override; | ||
| 105 | |||
| 106 | bool Resize(std::size_t new_size) override; | ||
| 107 | |||
| 108 | VirtualDir GetContainingDirectory() const override; | ||
| 109 | |||
| 110 | bool IsWritable() const override; | ||
| 111 | |||
| 112 | bool IsReadable() const override; | ||
| 113 | |||
| 114 | std::size_t Write(const u8* data, std::size_t length, std::size_t offset) override; | ||
| 115 | |||
| 116 | bool Rename(std::string_view name) override; | ||
| 117 | |||
| 118 | private: | ||
| 119 | RelocationEntry GetRelocationEntry(u64 offset) const; | ||
| 120 | RelocationEntry GetNextRelocationEntry(u64 offset) const; | ||
| 121 | |||
| 122 | SubsectionEntry GetSubsectionEntry(u64 offset) const; | ||
| 123 | SubsectionEntry GetNextSubsectionEntry(u64 offset) const; | ||
| 124 | |||
| 125 | RelocationBlock relocation; | ||
| 126 | std::vector<RelocationBucket> relocation_buckets; | ||
| 127 | SubsectionBlock subsection; | ||
| 128 | std::vector<SubsectionBucket> subsection_buckets; | ||
| 129 | |||
| 130 | // Should be the raw base romfs, decrypted. | ||
| 131 | VirtualFile base_romfs; | ||
| 132 | // Should be the raw BKTR romfs, (located at media_offset with size media_size). | ||
| 133 | VirtualFile bktr_romfs; | ||
| 134 | |||
| 135 | bool encrypted; | ||
| 136 | Core::Crypto::Key128 key; | ||
| 137 | |||
| 138 | // Base offset into NCA, used for IV calculation. | ||
| 139 | u64 base_offset; | ||
| 140 | // Distance between IVFC start and RomFS start, used for base reads | ||
| 141 | u64 ivfc_offset; | ||
| 142 | std::array<u8, 8> section_ctr; | ||
| 143 | }; | ||
| 144 | |||
| 145 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp index 2ba1b34a4..a4baddb15 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 { | |||
| 141 | const auto update_tid = GetUpdateTitleID(title_id); | 141 | const auto update_tid = GetUpdateTitleID(title_id); |
| 142 | const auto update = content_provider.GetEntry(update_tid, ContentRecordType::Program); | 142 | const auto update = content_provider.GetEntry(update_tid, ContentRecordType::Program); |
| 143 | 143 | ||
| 144 | if (!update_disabled && update != nullptr && update->GetExeFS() != nullptr && | 144 | if (!update_disabled && update != nullptr && update->GetExeFS() != nullptr) { |
| 145 | update->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) { | ||
| 146 | LOG_INFO(Loader, " ExeFS: Update ({}) applied successfully", | 145 | LOG_INFO(Loader, " ExeFS: Update ({}) applied successfully", |
| 147 | FormatTitleVersion(content_provider.GetEntryVersion(update_tid).value_or(0))); | 146 | FormatTitleVersion(content_provider.GetEntryVersion(update_tid).value_or(0))); |
| 148 | exefs = update->GetExeFS(); | 147 | exefs = update->GetExeFS(); |
| @@ -353,16 +352,12 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t | |||
| 353 | const Service::FileSystem::FileSystemController& fs_controller) { | 352 | const Service::FileSystem::FileSystemController& fs_controller) { |
| 354 | const auto load_dir = fs_controller.GetModificationLoadRoot(title_id); | 353 | const auto load_dir = fs_controller.GetModificationLoadRoot(title_id); |
| 355 | const auto sdmc_load_dir = fs_controller.GetSDMCModificationLoadRoot(title_id); | 354 | const auto sdmc_load_dir = fs_controller.GetSDMCModificationLoadRoot(title_id); |
| 356 | if ((type != ContentRecordType::Program && type != ContentRecordType::Data) || | 355 | if ((type != ContentRecordType::Program && type != ContentRecordType::Data && |
| 356 | type != ContentRecordType::HtmlDocument) || | ||
| 357 | (load_dir == nullptr && sdmc_load_dir == nullptr)) { | 357 | (load_dir == nullptr && sdmc_load_dir == nullptr)) { |
| 358 | return; | 358 | return; |
| 359 | } | 359 | } |
| 360 | 360 | ||
| 361 | auto extracted = ExtractRomFS(romfs); | ||
| 362 | if (extracted == nullptr) { | ||
| 363 | return; | ||
| 364 | } | ||
| 365 | |||
| 366 | const auto& disabled = Settings::values.disabled_addons[title_id]; | 361 | const auto& disabled = Settings::values.disabled_addons[title_id]; |
| 367 | std::vector<VirtualDir> patch_dirs = load_dir->GetSubdirectories(); | 362 | std::vector<VirtualDir> patch_dirs = load_dir->GetSubdirectories(); |
| 368 | if (std::find(disabled.cbegin(), disabled.cend(), "SDMC") == disabled.cend()) { | 363 | if (std::find(disabled.cbegin(), disabled.cend(), "SDMC") == disabled.cend()) { |
| @@ -387,6 +382,12 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t | |||
| 387 | auto ext_dir = FindSubdirectoryCaseless(subdir, "romfs_ext"); | 382 | auto ext_dir = FindSubdirectoryCaseless(subdir, "romfs_ext"); |
| 388 | if (ext_dir != nullptr) | 383 | if (ext_dir != nullptr) |
| 389 | layers_ext.push_back(std::make_shared<CachedVfsDirectory>(ext_dir)); | 384 | layers_ext.push_back(std::make_shared<CachedVfsDirectory>(ext_dir)); |
| 385 | |||
| 386 | if (type == ContentRecordType::HtmlDocument) { | ||
| 387 | auto manual_dir = FindSubdirectoryCaseless(subdir, "manual_html"); | ||
| 388 | if (manual_dir != nullptr) | ||
| 389 | layers.push_back(std::make_shared<CachedVfsDirectory>(manual_dir)); | ||
| 390 | } | ||
| 390 | } | 391 | } |
| 391 | 392 | ||
| 392 | // When there are no layers to apply, return early as there is no need to rebuild the RomFS | 393 | // When there are no layers to apply, return early as there is no need to rebuild the RomFS |
| @@ -394,6 +395,11 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t | |||
| 394 | return; | 395 | return; |
| 395 | } | 396 | } |
| 396 | 397 | ||
| 398 | auto extracted = ExtractRomFS(romfs); | ||
| 399 | if (extracted == nullptr) { | ||
| 400 | return; | ||
| 401 | } | ||
| 402 | |||
| 397 | layers.push_back(std::move(extracted)); | 403 | layers.push_back(std::move(extracted)); |
| 398 | 404 | ||
| 399 | auto layered = LayeredVfsDirectory::MakeLayeredDirectory(std::move(layers)); | 405 | auto layered = LayeredVfsDirectory::MakeLayeredDirectory(std::move(layers)); |
| @@ -412,39 +418,43 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t | |||
| 412 | romfs = std::move(packed); | 418 | romfs = std::move(packed); |
| 413 | } | 419 | } |
| 414 | 420 | ||
| 415 | VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, ContentRecordType type, | 421 | VirtualFile PatchManager::PatchRomFS(const NCA* base_nca, VirtualFile base_romfs, |
| 416 | VirtualFile update_raw, bool apply_layeredfs) const { | 422 | ContentRecordType type, VirtualFile packed_update_raw, |
| 423 | bool apply_layeredfs) const { | ||
| 417 | const auto log_string = fmt::format("Patching RomFS for title_id={:016X}, type={:02X}", | 424 | const auto log_string = fmt::format("Patching RomFS for title_id={:016X}, type={:02X}", |
| 418 | title_id, static_cast<u8>(type)); | 425 | title_id, static_cast<u8>(type)); |
| 419 | |||
| 420 | if (type == ContentRecordType::Program || type == ContentRecordType::Data) { | 426 | if (type == ContentRecordType::Program || type == ContentRecordType::Data) { |
| 421 | LOG_INFO(Loader, "{}", log_string); | 427 | LOG_INFO(Loader, "{}", log_string); |
| 422 | } else { | 428 | } else { |
| 423 | LOG_DEBUG(Loader, "{}", log_string); | 429 | LOG_DEBUG(Loader, "{}", log_string); |
| 424 | } | 430 | } |
| 425 | 431 | ||
| 426 | if (romfs == nullptr) { | 432 | if (base_romfs == nullptr) { |
| 427 | return romfs; | 433 | return base_romfs; |
| 428 | } | 434 | } |
| 429 | 435 | ||
| 436 | auto romfs = base_romfs; | ||
| 437 | |||
| 430 | // Game Updates | 438 | // Game Updates |
| 431 | const auto update_tid = GetUpdateTitleID(title_id); | 439 | const auto update_tid = GetUpdateTitleID(title_id); |
| 432 | const auto update = content_provider.GetEntryRaw(update_tid, type); | 440 | const auto update_raw = content_provider.GetEntryRaw(update_tid, type); |
| 433 | 441 | ||
| 434 | const auto& disabled = Settings::values.disabled_addons[title_id]; | 442 | const auto& disabled = Settings::values.disabled_addons[title_id]; |
| 435 | const auto update_disabled = | 443 | const auto update_disabled = |
| 436 | std::find(disabled.cbegin(), disabled.cend(), "Update") != disabled.cend(); | 444 | std::find(disabled.cbegin(), disabled.cend(), "Update") != disabled.cend(); |
| 437 | 445 | ||
| 438 | if (!update_disabled && update != nullptr) { | 446 | if (!update_disabled && update_raw != nullptr && base_nca != nullptr) { |
| 439 | const auto new_nca = std::make_shared<NCA>(update, romfs, ivfc_offset); | 447 | const auto new_nca = std::make_shared<NCA>(update_raw, base_nca); |
| 440 | if (new_nca->GetStatus() == Loader::ResultStatus::Success && | 448 | if (new_nca->GetStatus() == Loader::ResultStatus::Success && |
| 441 | new_nca->GetRomFS() != nullptr) { | 449 | new_nca->GetRomFS() != nullptr) { |
| 442 | LOG_INFO(Loader, " RomFS: Update ({}) applied successfully", | 450 | LOG_INFO(Loader, " RomFS: Update ({}) applied successfully", |
| 443 | FormatTitleVersion(content_provider.GetEntryVersion(update_tid).value_or(0))); | 451 | FormatTitleVersion(content_provider.GetEntryVersion(update_tid).value_or(0))); |
| 444 | romfs = new_nca->GetRomFS(); | 452 | romfs = new_nca->GetRomFS(); |
| 453 | const auto version = | ||
| 454 | FormatTitleVersion(content_provider.GetEntryVersion(update_tid).value_or(0)); | ||
| 445 | } | 455 | } |
| 446 | } else if (!update_disabled && update_raw != nullptr) { | 456 | } else if (!update_disabled && packed_update_raw != nullptr && base_nca != nullptr) { |
| 447 | const auto new_nca = std::make_shared<NCA>(update_raw, romfs, ivfc_offset); | 457 | const auto new_nca = std::make_shared<NCA>(packed_update_raw, base_nca); |
| 448 | if (new_nca->GetStatus() == Loader::ResultStatus::Success && | 458 | if (new_nca->GetStatus() == Loader::ResultStatus::Success && |
| 449 | new_nca->GetRomFS() != nullptr) { | 459 | new_nca->GetRomFS() != nullptr) { |
| 450 | LOG_INFO(Loader, " RomFS: Update (PACKED) applied successfully"); | 460 | LOG_INFO(Loader, " RomFS: Update (PACKED) applied successfully"); |
| @@ -608,7 +618,7 @@ PatchManager::Metadata PatchManager::ParseControlNCA(const NCA& nca) const { | |||
| 608 | return {}; | 618 | return {}; |
| 609 | } | 619 | } |
| 610 | 620 | ||
| 611 | const auto romfs = PatchRomFS(base_romfs, nca.GetBaseIVFCOffset(), ContentRecordType::Control); | 621 | const auto romfs = PatchRomFS(&nca, base_romfs, ContentRecordType::Control); |
| 612 | if (romfs == nullptr) { | 622 | if (romfs == nullptr) { |
| 613 | return {}; | 623 | return {}; |
| 614 | } | 624 | } |
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: | |||
| 61 | // Currently tracked RomFS patches: | 61 | // Currently tracked RomFS patches: |
| 62 | // - Game Updates | 62 | // - Game Updates |
| 63 | // - LayeredFS | 63 | // - LayeredFS |
| 64 | [[nodiscard]] VirtualFile PatchRomFS(VirtualFile base, u64 ivfc_offset, | 64 | [[nodiscard]] VirtualFile PatchRomFS(const NCA* base_nca, VirtualFile base_romfs, |
| 65 | ContentRecordType type = ContentRecordType::Program, | 65 | ContentRecordType type = ContentRecordType::Program, |
| 66 | VirtualFile update_raw = nullptr, | 66 | VirtualFile packed_update_raw = nullptr, |
| 67 | bool apply_layeredfs = true) const; | 67 | bool apply_layeredfs = true) const; |
| 68 | 68 | ||
| 69 | // Returns a vector of pairs between patch names and patch versions. | 69 | // 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) { | |||
| 416 | 416 | ||
| 417 | if (file == nullptr) | 417 | if (file == nullptr) |
| 418 | continue; | 418 | continue; |
| 419 | const auto nca = std::make_shared<NCA>(parser(file, id), nullptr, 0); | 419 | const auto nca = std::make_shared<NCA>(parser(file, id)); |
| 420 | if (nca->GetStatus() != Loader::ResultStatus::Success || | 420 | if (nca->GetStatus() != Loader::ResultStatus::Success || |
| 421 | nca->GetType() != NCAContentType::Meta) { | 421 | nca->GetType() != NCAContentType::Meta || nca->GetSubdirectories().empty()) { |
| 422 | continue; | 422 | continue; |
| 423 | } | 423 | } |
| 424 | 424 | ||
| @@ -500,7 +500,7 @@ std::unique_ptr<NCA> RegisteredCache::GetEntry(u64 title_id, ContentRecordType t | |||
| 500 | const auto raw = GetEntryRaw(title_id, type); | 500 | const auto raw = GetEntryRaw(title_id, type); |
| 501 | if (raw == nullptr) | 501 | if (raw == nullptr) |
| 502 | return nullptr; | 502 | return nullptr; |
| 503 | return std::make_unique<NCA>(raw, nullptr, 0); | 503 | return std::make_unique<NCA>(raw); |
| 504 | } | 504 | } |
| 505 | 505 | ||
| 506 | template <typename T> | 506 | template <typename T> |
| @@ -964,7 +964,7 @@ std::unique_ptr<NCA> ManualContentProvider::GetEntry(u64 title_id, ContentRecord | |||
| 964 | const auto res = GetEntryRaw(title_id, type); | 964 | const auto res = GetEntryRaw(title_id, type); |
| 965 | if (res == nullptr) | 965 | if (res == nullptr) |
| 966 | return nullptr; | 966 | return nullptr; |
| 967 | return std::make_unique<NCA>(res, nullptr, 0); | 967 | return std::make_unique<NCA>(res); |
| 968 | } | 968 | } |
| 969 | 969 | ||
| 970 | std::vector<ContentProviderEntry> ManualContentProvider::ListEntriesFilter( | 970 | std::vector<ContentProviderEntry> ManualContentProvider::ListEntriesFilter( |
diff --git a/src/core/file_sys/romfs_factory.cpp b/src/core/file_sys/romfs_factory.cpp index aa4726cfa..1bc07dae5 100644 --- a/src/core/file_sys/romfs_factory.cpp +++ b/src/core/file_sys/romfs_factory.cpp | |||
| @@ -26,13 +26,12 @@ RomFSFactory::RomFSFactory(Loader::AppLoader& app_loader, ContentProvider& provi | |||
| 26 | } | 26 | } |
| 27 | 27 | ||
| 28 | updatable = app_loader.IsRomFSUpdatable(); | 28 | updatable = app_loader.IsRomFSUpdatable(); |
| 29 | ivfc_offset = app_loader.ReadRomFSIVFCOffset(); | ||
| 30 | } | 29 | } |
| 31 | 30 | ||
| 32 | RomFSFactory::~RomFSFactory() = default; | 31 | RomFSFactory::~RomFSFactory() = default; |
| 33 | 32 | ||
| 34 | void RomFSFactory::SetPackedUpdate(VirtualFile update_raw_file) { | 33 | void RomFSFactory::SetPackedUpdate(VirtualFile update_raw_file) { |
| 35 | update_raw = std::move(update_raw_file); | 34 | packed_update_raw = std::move(update_raw_file); |
| 36 | } | 35 | } |
| 37 | 36 | ||
| 38 | VirtualFile RomFSFactory::OpenCurrentProcess(u64 current_process_title_id) const { | 37 | VirtualFile RomFSFactory::OpenCurrentProcess(u64 current_process_title_id) const { |
| @@ -40,9 +39,11 @@ VirtualFile RomFSFactory::OpenCurrentProcess(u64 current_process_title_id) const | |||
| 40 | return file; | 39 | return file; |
| 41 | } | 40 | } |
| 42 | 41 | ||
| 42 | const auto type = ContentRecordType::Program; | ||
| 43 | const auto nca = content_provider.GetEntry(current_process_title_id, type); | ||
| 43 | const PatchManager patch_manager{current_process_title_id, filesystem_controller, | 44 | const PatchManager patch_manager{current_process_title_id, filesystem_controller, |
| 44 | content_provider}; | 45 | content_provider}; |
| 45 | return patch_manager.PatchRomFS(file, ivfc_offset, ContentRecordType::Program, update_raw); | 46 | return patch_manager.PatchRomFS(nca.get(), file, ContentRecordType::Program, packed_update_raw); |
| 46 | } | 47 | } |
| 47 | 48 | ||
| 48 | VirtualFile RomFSFactory::OpenPatchedRomFS(u64 title_id, ContentRecordType type) const { | 49 | VirtualFile RomFSFactory::OpenPatchedRomFS(u64 title_id, ContentRecordType type) const { |
| @@ -54,7 +55,7 @@ VirtualFile RomFSFactory::OpenPatchedRomFS(u64 title_id, ContentRecordType type) | |||
| 54 | 55 | ||
| 55 | const PatchManager patch_manager{title_id, filesystem_controller, content_provider}; | 56 | const PatchManager patch_manager{title_id, filesystem_controller, content_provider}; |
| 56 | 57 | ||
| 57 | return patch_manager.PatchRomFS(nca->GetRomFS(), nca->GetBaseIVFCOffset(), type); | 58 | return patch_manager.PatchRomFS(nca.get(), nca->GetRomFS(), type); |
| 58 | } | 59 | } |
| 59 | 60 | ||
| 60 | VirtualFile RomFSFactory::OpenPatchedRomFSWithProgramIndex(u64 title_id, u8 program_index, | 61 | VirtualFile RomFSFactory::OpenPatchedRomFSWithProgramIndex(u64 title_id, u8 program_index, |
diff --git a/src/core/file_sys/romfs_factory.h b/src/core/file_sys/romfs_factory.h index 7ec40d19d..e4809bc94 100644 --- a/src/core/file_sys/romfs_factory.h +++ b/src/core/file_sys/romfs_factory.h | |||
| @@ -40,21 +40,22 @@ public: | |||
| 40 | Service::FileSystem::FileSystemController& controller); | 40 | Service::FileSystem::FileSystemController& controller); |
| 41 | ~RomFSFactory(); | 41 | ~RomFSFactory(); |
| 42 | 42 | ||
| 43 | void SetPackedUpdate(VirtualFile update_raw_file); | 43 | void SetPackedUpdate(VirtualFile packed_update_raw); |
| 44 | [[nodiscard]] VirtualFile OpenCurrentProcess(u64 current_process_title_id) const; | 44 | [[nodiscard]] VirtualFile OpenCurrentProcess(u64 current_process_title_id) const; |
| 45 | [[nodiscard]] VirtualFile OpenPatchedRomFS(u64 title_id, ContentRecordType type) const; | 45 | [[nodiscard]] VirtualFile OpenPatchedRomFS(u64 title_id, ContentRecordType type) const; |
| 46 | [[nodiscard]] VirtualFile OpenPatchedRomFSWithProgramIndex(u64 title_id, u8 program_index, | 46 | [[nodiscard]] VirtualFile OpenPatchedRomFSWithProgramIndex(u64 title_id, u8 program_index, |
| 47 | ContentRecordType type) const; | 47 | ContentRecordType type) const; |
| 48 | [[nodiscard]] VirtualFile Open(u64 title_id, StorageId storage, ContentRecordType type) const; | 48 | [[nodiscard]] VirtualFile Open(u64 title_id, StorageId storage, ContentRecordType type) const; |
| 49 | |||
| 50 | private: | ||
| 51 | [[nodiscard]] std::shared_ptr<NCA> GetEntry(u64 title_id, StorageId storage, | 49 | [[nodiscard]] std::shared_ptr<NCA> GetEntry(u64 title_id, StorageId storage, |
| 52 | ContentRecordType type) const; | 50 | ContentRecordType type) const; |
| 53 | 51 | ||
| 52 | private: | ||
| 54 | VirtualFile file; | 53 | VirtualFile file; |
| 55 | VirtualFile update_raw; | 54 | VirtualFile packed_update_raw; |
| 55 | |||
| 56 | VirtualFile base; | ||
| 57 | |||
| 56 | bool updatable; | 58 | bool updatable; |
| 57 | u64 ivfc_offset; | ||
| 58 | 59 | ||
| 59 | ContentProvider& content_provider; | 60 | ContentProvider& content_provider; |
| 60 | Service::FileSystem::FileSystemController& filesystem_controller; | 61 | Service::FileSystem::FileSystemController& filesystem_controller; |
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) { | |||
| 249 | } | 249 | } |
| 250 | 250 | ||
| 251 | const auto nca = std::make_shared<NCA>(outer_file); | 251 | const auto nca = std::make_shared<NCA>(outer_file); |
| 252 | if (nca->GetStatus() != Loader::ResultStatus::Success) { | 252 | if (nca->GetStatus() != Loader::ResultStatus::Success || nca->GetSubdirectories().empty()) { |
| 253 | program_status[nca->GetTitleId()] = nca->GetStatus(); | 253 | program_status[nca->GetTitleId()] = nca->GetStatus(); |
| 254 | continue; | 254 | continue; |
| 255 | } | 255 | } |
| @@ -280,7 +280,7 @@ void NSP::ReadNCAs(const std::vector<VirtualFile>& files) { | |||
| 280 | continue; | 280 | continue; |
| 281 | } | 281 | } |
| 282 | 282 | ||
| 283 | auto next_nca = std::make_shared<NCA>(std::move(next_file), nullptr, 0); | 283 | auto next_nca = std::make_shared<NCA>(std::move(next_file)); |
| 284 | 284 | ||
| 285 | if (next_nca->GetType() == NCAContentType::Program) { | 285 | if (next_nca->GetType() == NCAContentType::Program) { |
| 286 | program_status[next_nca->GetTitleId()] = next_nca->GetStatus(); | 286 | program_status[next_nca->GetTitleId()] = next_nca->GetStatus(); |
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: | |||
| 19 | void Initialize(); | 19 | void Initialize(); |
| 20 | void Finalize(); | 20 | void Finalize(); |
| 21 | 21 | ||
| 22 | s64 GetCount() const { | 22 | s64 GetTick() const; |
| 23 | return GetTick(); | ||
| 24 | } | ||
| 25 | |||
| 26 | void RegisterTask(KTimerTask* task, s64 time_from_now) { | ||
| 27 | this->RegisterAbsoluteTask(task, GetTick() + time_from_now); | ||
| 28 | } | ||
| 29 | 23 | ||
| 30 | void RegisterAbsoluteTask(KTimerTask* task, s64 task_time) { | 24 | void RegisterAbsoluteTask(KTimerTask* task, s64 task_time) { |
| 31 | KScopedDisableDispatch dd{m_kernel}; | 25 | KScopedDisableDispatch dd{m_kernel}; |
| @@ -42,7 +36,6 @@ private: | |||
| 42 | void EnableInterrupt(s64 wakeup_time); | 36 | void EnableInterrupt(s64 wakeup_time); |
| 43 | void DisableInterrupt(); | 37 | void DisableInterrupt(); |
| 44 | bool GetInterruptEnabled(); | 38 | bool GetInterruptEnabled(); |
| 45 | s64 GetTick() const; | ||
| 46 | void DoTask(); | 39 | void DoTask(); |
| 47 | 40 | ||
| 48 | private: | 41 | private: |
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 @@ | |||
| 5 | #include "common/overflow.h" | 5 | #include "common/overflow.h" |
| 6 | #include "core/core.h" | 6 | #include "core/core.h" |
| 7 | #include "core/core_timing.h" | 7 | #include "core/core_timing.h" |
| 8 | #include "core/hle/kernel/k_hardware_timer.h" | ||
| 8 | #include "core/hle/kernel/k_resource_limit.h" | 9 | #include "core/hle/kernel/k_resource_limit.h" |
| 9 | #include "core/hle/kernel/svc_results.h" | 10 | #include "core/hle/kernel/svc_results.h" |
| 10 | 11 | ||
| @@ -15,9 +16,7 @@ KResourceLimit::KResourceLimit(KernelCore& kernel) | |||
| 15 | : KAutoObjectWithSlabHeapAndContainer{kernel}, m_lock{m_kernel}, m_cond_var{m_kernel} {} | 16 | : KAutoObjectWithSlabHeapAndContainer{kernel}, m_lock{m_kernel}, m_cond_var{m_kernel} {} |
| 16 | KResourceLimit::~KResourceLimit() = default; | 17 | KResourceLimit::~KResourceLimit() = default; |
| 17 | 18 | ||
| 18 | void KResourceLimit::Initialize(const Core::Timing::CoreTiming* core_timing) { | 19 | void KResourceLimit::Initialize() {} |
| 19 | m_core_timing = core_timing; | ||
| 20 | } | ||
| 21 | 20 | ||
| 22 | void KResourceLimit::Finalize() {} | 21 | void KResourceLimit::Finalize() {} |
| 23 | 22 | ||
| @@ -86,7 +85,7 @@ Result KResourceLimit::SetLimitValue(LimitableResource which, s64 value) { | |||
| 86 | } | 85 | } |
| 87 | 86 | ||
| 88 | bool KResourceLimit::Reserve(LimitableResource which, s64 value) { | 87 | bool KResourceLimit::Reserve(LimitableResource which, s64 value) { |
| 89 | return Reserve(which, value, m_core_timing->GetGlobalTimeNs().count() + DefaultTimeout); | 88 | return Reserve(which, value, m_kernel.HardwareTimer().GetTick() + DefaultTimeout); |
| 90 | } | 89 | } |
| 91 | 90 | ||
| 92 | bool KResourceLimit::Reserve(LimitableResource which, s64 value, s64 timeout) { | 91 | bool KResourceLimit::Reserve(LimitableResource which, s64 value, s64 timeout) { |
| @@ -117,7 +116,7 @@ bool KResourceLimit::Reserve(LimitableResource which, s64 value, s64 timeout) { | |||
| 117 | } | 116 | } |
| 118 | 117 | ||
| 119 | if (m_current_hints[index] + value <= m_limit_values[index] && | 118 | if (m_current_hints[index] + value <= m_limit_values[index] && |
| 120 | (timeout < 0 || m_core_timing->GetGlobalTimeNs().count() < timeout)) { | 119 | (timeout < 0 || m_kernel.HardwareTimer().GetTick() < timeout)) { |
| 121 | m_waiter_count++; | 120 | m_waiter_count++; |
| 122 | m_cond_var.Wait(std::addressof(m_lock), timeout, false); | 121 | m_cond_var.Wait(std::addressof(m_lock), timeout, false); |
| 123 | m_waiter_count--; | 122 | m_waiter_count--; |
| @@ -154,7 +153,7 @@ void KResourceLimit::Release(LimitableResource which, s64 value, s64 hint) { | |||
| 154 | 153 | ||
| 155 | KResourceLimit* CreateResourceLimitForProcess(Core::System& system, s64 physical_memory_size) { | 154 | KResourceLimit* CreateResourceLimitForProcess(Core::System& system, s64 physical_memory_size) { |
| 156 | auto* resource_limit = KResourceLimit::Create(system.Kernel()); | 155 | auto* resource_limit = KResourceLimit::Create(system.Kernel()); |
| 157 | resource_limit->Initialize(std::addressof(system.CoreTiming())); | 156 | resource_limit->Initialize(); |
| 158 | 157 | ||
| 159 | // Initialize default resource limit values. | 158 | // Initialize default resource limit values. |
| 160 | // TODO(bunnei): These values are the system defaults, the limits for service processes are | 159 | // 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: | |||
| 31 | explicit KResourceLimit(KernelCore& kernel); | 31 | explicit KResourceLimit(KernelCore& kernel); |
| 32 | ~KResourceLimit() override; | 32 | ~KResourceLimit() override; |
| 33 | 33 | ||
| 34 | void Initialize(const Core::Timing::CoreTiming* core_timing); | 34 | void Initialize(); |
| 35 | void Finalize() override; | 35 | void Finalize() override; |
| 36 | 36 | ||
| 37 | s64 GetLimitValue(LimitableResource which) const; | 37 | s64 GetLimitValue(LimitableResource which) const; |
| @@ -57,7 +57,6 @@ private: | |||
| 57 | mutable KLightLock m_lock; | 57 | mutable KLightLock m_lock; |
| 58 | s32 m_waiter_count{}; | 58 | s32 m_waiter_count{}; |
| 59 | KLightConditionVariable m_cond_var; | 59 | KLightConditionVariable m_cond_var; |
| 60 | const Core::Timing::CoreTiming* m_core_timing{}; | ||
| 61 | }; | 60 | }; |
| 62 | 61 | ||
| 63 | KResourceLimit* CreateResourceLimitForProcess(Core::System& system, s64 physical_memory_size); | 62 | 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: | |||
| 28 | ~KScopedSchedulerLockAndSleep() { | 28 | ~KScopedSchedulerLockAndSleep() { |
| 29 | // Register the sleep. | 29 | // Register the sleep. |
| 30 | if (m_timeout_tick > 0) { | 30 | if (m_timeout_tick > 0) { |
| 31 | m_timer->RegisterTask(m_thread, m_timeout_tick); | 31 | m_timer->RegisterAbsoluteTask(m_thread, m_timeout_tick); |
| 32 | } | 32 | } |
| 33 | 33 | ||
| 34 | // Unlock the scheduler. | 34 | // 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 { | |||
| 231 | void InitializeSystemResourceLimit(KernelCore& kernel, | 231 | void InitializeSystemResourceLimit(KernelCore& kernel, |
| 232 | const Core::Timing::CoreTiming& core_timing) { | 232 | const Core::Timing::CoreTiming& core_timing) { |
| 233 | system_resource_limit = KResourceLimit::Create(system.Kernel()); | 233 | system_resource_limit = KResourceLimit::Create(system.Kernel()); |
| 234 | system_resource_limit->Initialize(&core_timing); | 234 | system_resource_limit->Initialize(); |
| 235 | KResourceLimit::Register(kernel, system_resource_limit); | 235 | KResourceLimit::Register(kernel, system_resource_limit); |
| 236 | 236 | ||
| 237 | const auto sizes{memory_layout->GetTotalAndKernelMemorySizes()}; | 237 | 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 @@ | |||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | 2 | // SPDX-License-Identifier: GPL-2.0-or-later |
| 3 | 3 | ||
| 4 | #include "core/core.h" | 4 | #include "core/core.h" |
| 5 | #include "core/hle/kernel/k_hardware_timer.h" | ||
| 5 | #include "core/hle/kernel/k_memory_layout.h" | 6 | #include "core/hle/kernel/k_memory_layout.h" |
| 6 | #include "core/hle/kernel/k_process.h" | 7 | #include "core/hle/kernel/k_process.h" |
| 7 | #include "core/hle/kernel/kernel.h" | 8 | #include "core/hle/kernel/kernel.h" |
| @@ -52,7 +53,7 @@ Result WaitForAddress(Core::System& system, u64 address, ArbitrationType arb_typ | |||
| 52 | if (timeout_ns > 0) { | 53 | if (timeout_ns > 0) { |
| 53 | const s64 offset_tick(timeout_ns); | 54 | const s64 offset_tick(timeout_ns); |
| 54 | if (offset_tick > 0) { | 55 | if (offset_tick > 0) { |
| 55 | timeout = offset_tick + 2; | 56 | timeout = system.Kernel().HardwareTimer().GetTick() + offset_tick + 2; |
| 56 | if (timeout <= 0) { | 57 | if (timeout <= 0) { |
| 57 | timeout = std::numeric_limits<s64>::max(); | 58 | timeout = std::numeric_limits<s64>::max(); |
| 58 | } | 59 | } |
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 @@ | |||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | 2 | // SPDX-License-Identifier: GPL-2.0-or-later |
| 3 | 3 | ||
| 4 | #include "core/core.h" | 4 | #include "core/core.h" |
| 5 | #include "core/hle/kernel/k_hardware_timer.h" | ||
| 5 | #include "core/hle/kernel/k_memory_layout.h" | 6 | #include "core/hle/kernel/k_memory_layout.h" |
| 6 | #include "core/hle/kernel/k_process.h" | 7 | #include "core/hle/kernel/k_process.h" |
| 7 | #include "core/hle/kernel/kernel.h" | 8 | #include "core/hle/kernel/kernel.h" |
| @@ -25,7 +26,7 @@ Result WaitProcessWideKeyAtomic(Core::System& system, u64 address, u64 cv_key, u | |||
| 25 | if (timeout_ns > 0) { | 26 | if (timeout_ns > 0) { |
| 26 | const s64 offset_tick(timeout_ns); | 27 | const s64 offset_tick(timeout_ns); |
| 27 | if (offset_tick > 0) { | 28 | if (offset_tick > 0) { |
| 28 | timeout = offset_tick + 2; | 29 | timeout = system.Kernel().HardwareTimer().GetTick() + offset_tick + 2; |
| 29 | if (timeout <= 0) { | 30 | if (timeout <= 0) { |
| 30 | timeout = std::numeric_limits<s64>::max(); | 31 | timeout = std::numeric_limits<s64>::max(); |
| 31 | } | 32 | } |
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 @@ | |||
| 5 | #include "common/scratch_buffer.h" | 5 | #include "common/scratch_buffer.h" |
| 6 | #include "core/core.h" | 6 | #include "core/core.h" |
| 7 | #include "core/hle/kernel/k_client_session.h" | 7 | #include "core/hle/kernel/k_client_session.h" |
| 8 | #include "core/hle/kernel/k_hardware_timer.h" | ||
| 8 | #include "core/hle/kernel/k_process.h" | 9 | #include "core/hle/kernel/k_process.h" |
| 9 | #include "core/hle/kernel/k_server_session.h" | 10 | #include "core/hle/kernel/k_server_session.h" |
| 10 | #include "core/hle/kernel/svc.h" | 11 | #include "core/hle/kernel/svc.h" |
| @@ -82,12 +83,29 @@ Result ReplyAndReceive(Core::System& system, s32* out_index, uint64_t handles_ad | |||
| 82 | R_TRY(session->SendReply()); | 83 | R_TRY(session->SendReply()); |
| 83 | } | 84 | } |
| 84 | 85 | ||
| 86 | // Convert the timeout from nanoseconds to ticks. | ||
| 87 | // NOTE: Nintendo does not use this conversion logic in WaitSynchronization... | ||
| 88 | s64 timeout; | ||
| 89 | if (timeout_ns > 0) { | ||
| 90 | const s64 offset_tick(timeout_ns); | ||
| 91 | if (offset_tick > 0) { | ||
| 92 | timeout = kernel.HardwareTimer().GetTick() + offset_tick + 2; | ||
| 93 | if (timeout <= 0) { | ||
| 94 | timeout = std::numeric_limits<s64>::max(); | ||
| 95 | } | ||
| 96 | } else { | ||
| 97 | timeout = std::numeric_limits<s64>::max(); | ||
| 98 | } | ||
| 99 | } else { | ||
| 100 | timeout = timeout_ns; | ||
| 101 | } | ||
| 102 | |||
| 85 | // Wait for a message. | 103 | // Wait for a message. |
| 86 | while (true) { | 104 | while (true) { |
| 87 | // Wait for an object. | 105 | // Wait for an object. |
| 88 | s32 index; | 106 | s32 index; |
| 89 | Result result = KSynchronizationObject::Wait(kernel, std::addressof(index), objs.data(), | 107 | Result result = KSynchronizationObject::Wait(kernel, std::addressof(index), objs.data(), |
| 90 | num_handles, timeout_ns); | 108 | num_handles, timeout); |
| 91 | if (result == ResultTimedOut) { | 109 | if (result == ResultTimedOut) { |
| 92 | R_RETURN(result); | 110 | R_RETURN(result); |
| 93 | } | 111 | } |
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) { | |||
| 21 | SCOPE_EXIT({ resource_limit->Close(); }); | 21 | SCOPE_EXIT({ resource_limit->Close(); }); |
| 22 | 22 | ||
| 23 | // Initialize the resource limit. | 23 | // Initialize the resource limit. |
| 24 | resource_limit->Initialize(std::addressof(system.CoreTiming())); | 24 | resource_limit->Initialize(); |
| 25 | 25 | ||
| 26 | // Register the limit. | 26 | // Register the limit. |
| 27 | KResourceLimit::Register(kernel, resource_limit); | 27 | 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 @@ | |||
| 4 | #include "common/scope_exit.h" | 4 | #include "common/scope_exit.h" |
| 5 | #include "common/scratch_buffer.h" | 5 | #include "common/scratch_buffer.h" |
| 6 | #include "core/core.h" | 6 | #include "core/core.h" |
| 7 | #include "core/hle/kernel/k_hardware_timer.h" | ||
| 7 | #include "core/hle/kernel/k_process.h" | 8 | #include "core/hle/kernel/k_process.h" |
| 8 | #include "core/hle/kernel/k_readable_event.h" | 9 | #include "core/hle/kernel/k_readable_event.h" |
| 9 | #include "core/hle/kernel/svc.h" | 10 | #include "core/hle/kernel/svc.h" |
| @@ -83,9 +84,20 @@ Result WaitSynchronization(Core::System& system, int32_t* out_index, u64 user_ha | |||
| 83 | } | 84 | } |
| 84 | }); | 85 | }); |
| 85 | 86 | ||
| 87 | // Convert the timeout from nanoseconds to ticks. | ||
| 88 | s64 timeout; | ||
| 89 | if (timeout_ns > 0) { | ||
| 90 | u64 ticks = kernel.HardwareTimer().GetTick(); | ||
| 91 | ticks += timeout_ns; | ||
| 92 | ticks += 2; | ||
| 93 | |||
| 94 | timeout = ticks; | ||
| 95 | } else { | ||
| 96 | timeout = timeout_ns; | ||
| 97 | } | ||
| 98 | |||
| 86 | // Wait on the objects. | 99 | // Wait on the objects. |
| 87 | Result res = | 100 | Result res = KSynchronizationObject::Wait(kernel, out_index, objs.data(), num_handles, timeout); |
| 88 | KSynchronizationObject::Wait(kernel, out_index, objs.data(), num_handles, timeout_ns); | ||
| 89 | 101 | ||
| 90 | R_SUCCEED_IF(res == ResultSessionClosed); | 102 | R_SUCCEED_IF(res == ResultSessionClosed); |
| 91 | R_RETURN(res); | 103 | 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 @@ | |||
| 4 | #include "common/scope_exit.h" | 4 | #include "common/scope_exit.h" |
| 5 | #include "core/core.h" | 5 | #include "core/core.h" |
| 6 | #include "core/core_timing.h" | 6 | #include "core/core_timing.h" |
| 7 | #include "core/hle/kernel/k_hardware_timer.h" | ||
| 7 | #include "core/hle/kernel/k_process.h" | 8 | #include "core/hle/kernel/k_process.h" |
| 8 | #include "core/hle/kernel/k_scoped_resource_reservation.h" | 9 | #include "core/hle/kernel/k_scoped_resource_reservation.h" |
| 9 | #include "core/hle/kernel/k_thread.h" | 10 | #include "core/hle/kernel/k_thread.h" |
| @@ -42,9 +43,9 @@ Result CreateThread(Core::System& system, Handle* out_handle, u64 entry_point, u | |||
| 42 | R_UNLESS(process.CheckThreadPriority(priority), ResultInvalidPriority); | 43 | R_UNLESS(process.CheckThreadPriority(priority), ResultInvalidPriority); |
| 43 | 44 | ||
| 44 | // Reserve a new thread from the process resource limit (waiting up to 100ms). | 45 | // Reserve a new thread from the process resource limit (waiting up to 100ms). |
| 45 | KScopedResourceReservation thread_reservation( | 46 | KScopedResourceReservation thread_reservation(std::addressof(process), |
| 46 | std::addressof(process), LimitableResource::ThreadCountMax, 1, | 47 | LimitableResource::ThreadCountMax, 1, |
| 47 | system.CoreTiming().GetGlobalTimeNs().count() + 100000000); | 48 | kernel.HardwareTimer().GetTick() + 100000000); |
| 48 | R_UNLESS(thread_reservation.Succeeded(), ResultLimitReached); | 49 | R_UNLESS(thread_reservation.Succeeded(), ResultLimitReached); |
| 49 | 50 | ||
| 50 | // Create the thread. | 51 | // Create the thread. |
| @@ -102,20 +103,31 @@ void ExitThread(Core::System& system) { | |||
| 102 | } | 103 | } |
| 103 | 104 | ||
| 104 | /// Sleep the current thread | 105 | /// Sleep the current thread |
| 105 | void SleepThread(Core::System& system, s64 nanoseconds) { | 106 | void SleepThread(Core::System& system, s64 ns) { |
| 106 | auto& kernel = system.Kernel(); | 107 | auto& kernel = system.Kernel(); |
| 107 | const auto yield_type = static_cast<Svc::YieldType>(nanoseconds); | 108 | const auto yield_type = static_cast<Svc::YieldType>(ns); |
| 108 | 109 | ||
| 109 | LOG_TRACE(Kernel_SVC, "called nanoseconds={}", nanoseconds); | 110 | LOG_TRACE(Kernel_SVC, "called nanoseconds={}", ns); |
| 110 | 111 | ||
| 111 | // When the input tick is positive, sleep. | 112 | // When the input tick is positive, sleep. |
| 112 | if (nanoseconds > 0) { | 113 | if (ns > 0) { |
| 113 | // Convert the timeout from nanoseconds to ticks. | 114 | // Convert the timeout from nanoseconds to ticks. |
| 114 | // NOTE: Nintendo does not use this conversion logic in WaitSynchronization... | 115 | // NOTE: Nintendo does not use this conversion logic in WaitSynchronization... |
| 116 | s64 timeout; | ||
| 117 | |||
| 118 | const s64 offset_tick(ns); | ||
| 119 | if (offset_tick > 0) { | ||
| 120 | timeout = kernel.HardwareTimer().GetTick() + offset_tick + 2; | ||
| 121 | if (timeout <= 0) { | ||
| 122 | timeout = std::numeric_limits<s64>::max(); | ||
| 123 | } | ||
| 124 | } else { | ||
| 125 | timeout = std::numeric_limits<s64>::max(); | ||
| 126 | } | ||
| 115 | 127 | ||
| 116 | // Sleep. | 128 | // Sleep. |
| 117 | // NOTE: Nintendo does not check the result of this sleep. | 129 | // NOTE: Nintendo does not check the result of this sleep. |
| 118 | static_cast<void>(GetCurrentThread(kernel).Sleep(nanoseconds)); | 130 | static_cast<void>(GetCurrentThread(kernel).Sleep(timeout)); |
| 119 | } else if (yield_type == Svc::YieldType::WithoutCoreMigration) { | 131 | } else if (yield_type == Svc::YieldType::WithoutCoreMigration) { |
| 120 | KScheduler::YieldWithoutCoreMigration(kernel); | 132 | KScheduler::YieldWithoutCoreMigration(kernel); |
| 121 | } else if (yield_type == Svc::YieldType::WithCoreMigration) { | 133 | } else if (yield_type == Svc::YieldType::WithCoreMigration) { |
| @@ -124,7 +136,6 @@ void SleepThread(Core::System& system, s64 nanoseconds) { | |||
| 124 | KScheduler::YieldToAnyThread(kernel); | 136 | KScheduler::YieldToAnyThread(kernel); |
| 125 | } else { | 137 | } else { |
| 126 | // Nintendo does nothing at all if an otherwise invalid value is passed. | 138 | // Nintendo does nothing at all if an otherwise invalid value is passed. |
| 127 | ASSERT_MSG(false, "Unimplemented sleep yield type '{:016X}'!", nanoseconds); | ||
| 128 | } | 139 | } |
| 129 | } | 140 | } |
| 130 | 141 | ||
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, | |||
| 139 | const FileSys::PatchManager pm{title_id, system.GetFileSystemController(), | 139 | const FileSys::PatchManager pm{title_id, system.GetFileSystemController(), |
| 140 | system.GetContentProvider()}; | 140 | system.GetContentProvider()}; |
| 141 | 141 | ||
| 142 | return pm.PatchRomFS(nca->GetRomFS(), nca->GetBaseIVFCOffset(), nca_type); | 142 | return pm.PatchRomFS(nca.get(), nca->GetRomFS(), nca_type); |
| 143 | } | 143 | } |
| 144 | } | 144 | } |
| 145 | 145 | ||
diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp index ac465d5a9..4c1ea1a5b 100644 --- a/src/core/hle/service/filesystem/filesystem.cpp +++ b/src/core/hle/service/filesystem/filesystem.cpp | |||
| @@ -373,6 +373,11 @@ FileSys::VirtualFile FileSystemController::OpenRomFS(u64 title_id, FileSys::Stor | |||
| 373 | return romfs_factory->Open(title_id, storage_id, type); | 373 | return romfs_factory->Open(title_id, storage_id, type); |
| 374 | } | 374 | } |
| 375 | 375 | ||
| 376 | std::shared_ptr<FileSys::NCA> FileSystemController::OpenBaseNca( | ||
| 377 | u64 title_id, FileSys::StorageId storage_id, FileSys::ContentRecordType type) const { | ||
| 378 | return romfs_factory->GetEntry(title_id, storage_id, type); | ||
| 379 | } | ||
| 380 | |||
| 376 | Result FileSystemController::CreateSaveData(FileSys::VirtualDir* out_save_data, | 381 | Result FileSystemController::CreateSaveData(FileSys::VirtualDir* out_save_data, |
| 377 | FileSys::SaveDataSpaceId space, | 382 | FileSys::SaveDataSpaceId space, |
| 378 | const FileSys::SaveDataAttribute& save_struct) const { | 383 | const FileSys::SaveDataAttribute& save_struct) const { |
diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h index fd991f976..e7e7c4c28 100644 --- a/src/core/hle/service/filesystem/filesystem.h +++ b/src/core/hle/service/filesystem/filesystem.h | |||
| @@ -15,6 +15,7 @@ class System; | |||
| 15 | 15 | ||
| 16 | namespace FileSys { | 16 | namespace FileSys { |
| 17 | class BISFactory; | 17 | class BISFactory; |
| 18 | class NCA; | ||
| 18 | class RegisteredCache; | 19 | class RegisteredCache; |
| 19 | class RegisteredCacheUnion; | 20 | class RegisteredCacheUnion; |
| 20 | class PlaceholderCache; | 21 | class PlaceholderCache; |
| @@ -70,6 +71,8 @@ public: | |||
| 70 | FileSys::ContentRecordType type) const; | 71 | FileSys::ContentRecordType type) const; |
| 71 | FileSys::VirtualFile OpenRomFS(u64 title_id, FileSys::StorageId storage_id, | 72 | FileSys::VirtualFile OpenRomFS(u64 title_id, FileSys::StorageId storage_id, |
| 72 | FileSys::ContentRecordType type) const; | 73 | FileSys::ContentRecordType type) const; |
| 74 | std::shared_ptr<FileSys::NCA> OpenBaseNca(u64 title_id, FileSys::StorageId storage_id, | ||
| 75 | FileSys::ContentRecordType type) const; | ||
| 73 | 76 | ||
| 74 | Result CreateSaveData(FileSys::VirtualDir* out_save_data, FileSys::SaveDataSpaceId space, | 77 | Result CreateSaveData(FileSys::VirtualDir* out_save_data, FileSys::SaveDataSpaceId space, |
| 75 | const FileSys::SaveDataAttribute& save_struct) const; | 78 | const FileSys::SaveDataAttribute& save_struct) const; |
diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp index 423a814cb..6e4d26b1e 100644 --- a/src/core/hle/service/filesystem/fsp_srv.cpp +++ b/src/core/hle/service/filesystem/fsp_srv.cpp | |||
| @@ -1029,8 +1029,9 @@ void FSP_SRV::OpenDataStorageByDataId(HLERequestContext& ctx) { | |||
| 1029 | 1029 | ||
| 1030 | const FileSys::PatchManager pm{title_id, fsc, content_provider}; | 1030 | const FileSys::PatchManager pm{title_id, fsc, content_provider}; |
| 1031 | 1031 | ||
| 1032 | auto base = fsc.OpenBaseNca(title_id, storage_id, FileSys::ContentRecordType::Data); | ||
| 1032 | auto storage = std::make_shared<IStorage>( | 1033 | auto storage = std::make_shared<IStorage>( |
| 1033 | system, pm.PatchRomFS(std::move(data), 0, FileSys::ContentRecordType::Data)); | 1034 | system, pm.PatchRomFS(base.get(), std::move(data), FileSys::ContentRecordType::Data)); |
| 1034 | 1035 | ||
| 1035 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; | 1036 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; |
| 1036 | rb.Push(ResultSuccess); | 1037 | rb.Push(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 { | |||
| 18 | AGAIN = 11, | 18 | AGAIN = 11, |
| 19 | INVAL = 22, | 19 | INVAL = 22, |
| 20 | MFILE = 24, | 20 | MFILE = 24, |
| 21 | PIPE = 32, | ||
| 21 | MSGSIZE = 90, | 22 | MSGSIZE = 90, |
| 23 | CONNABORTED = 103, | ||
| 22 | CONNRESET = 104, | 24 | CONNRESET = 104, |
| 23 | NOTCONN = 107, | 25 | NOTCONN = 107, |
| 24 | TIMEDOUT = 110, | 26 | 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) { | |||
| 23 | return Errno::INVAL; | 23 | return Errno::INVAL; |
| 24 | case Network::Errno::MFILE: | 24 | case Network::Errno::MFILE: |
| 25 | return Errno::MFILE; | 25 | return Errno::MFILE; |
| 26 | case Network::Errno::PIPE: | ||
| 27 | return Errno::PIPE; | ||
| 26 | case Network::Errno::NOTCONN: | 28 | case Network::Errno::NOTCONN: |
| 27 | return Errno::NOTCONN; | 29 | return Errno::NOTCONN; |
| 28 | case Network::Errno::TIMEDOUT: | 30 | case Network::Errno::TIMEDOUT: |
| 29 | return Errno::TIMEDOUT; | 31 | return Errno::TIMEDOUT; |
| 32 | case Network::Errno::CONNABORTED: | ||
| 33 | return Errno::CONNABORTED; | ||
| 30 | case Network::Errno::CONNRESET: | 34 | case Network::Errno::CONNRESET: |
| 31 | return Errno::CONNRESET; | 35 | return Errno::CONNRESET; |
| 32 | case Network::Errno::INPROGRESS: | 36 | case Network::Errno::INPROGRESS: |
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 { | |||
| 39 | 39 | ||
| 40 | namespace { | 40 | namespace { |
| 41 | 41 | ||
| 42 | enum class CallType { | ||
| 43 | Send, | ||
| 44 | Other, | ||
| 45 | }; | ||
| 46 | |||
| 42 | #ifdef _WIN32 | 47 | #ifdef _WIN32 |
| 43 | 48 | ||
| 44 | using socklen_t = int; | 49 | using socklen_t = int; |
| @@ -96,7 +101,7 @@ bool EnableNonBlock(SOCKET fd, bool enable) { | |||
| 96 | return ioctlsocket(fd, FIONBIO, &value) != SOCKET_ERROR; | 101 | return ioctlsocket(fd, FIONBIO, &value) != SOCKET_ERROR; |
| 97 | } | 102 | } |
| 98 | 103 | ||
| 99 | Errno TranslateNativeError(int e) { | 104 | Errno TranslateNativeError(int e, CallType call_type = CallType::Other) { |
| 100 | switch (e) { | 105 | switch (e) { |
| 101 | case 0: | 106 | case 0: |
| 102 | return Errno::SUCCESS; | 107 | return Errno::SUCCESS; |
| @@ -112,6 +117,14 @@ Errno TranslateNativeError(int e) { | |||
| 112 | return Errno::AGAIN; | 117 | return Errno::AGAIN; |
| 113 | case WSAECONNREFUSED: | 118 | case WSAECONNREFUSED: |
| 114 | return Errno::CONNREFUSED; | 119 | return Errno::CONNREFUSED; |
| 120 | case WSAECONNABORTED: | ||
| 121 | if (call_type == CallType::Send) { | ||
| 122 | // Winsock yields WSAECONNABORTED from `send` in situations where Unix | ||
| 123 | // systems, and actual Switches, yield EPIPE. | ||
| 124 | return Errno::PIPE; | ||
| 125 | } else { | ||
| 126 | return Errno::CONNABORTED; | ||
| 127 | } | ||
| 115 | case WSAECONNRESET: | 128 | case WSAECONNRESET: |
| 116 | return Errno::CONNRESET; | 129 | return Errno::CONNRESET; |
| 117 | case WSAEHOSTUNREACH: | 130 | case WSAEHOSTUNREACH: |
| @@ -198,7 +211,7 @@ bool EnableNonBlock(int fd, bool enable) { | |||
| 198 | return fcntl(fd, F_SETFL, flags) == 0; | 211 | return fcntl(fd, F_SETFL, flags) == 0; |
| 199 | } | 212 | } |
| 200 | 213 | ||
| 201 | Errno TranslateNativeError(int e) { | 214 | Errno TranslateNativeError(int e, CallType call_type = CallType::Other) { |
| 202 | switch (e) { | 215 | switch (e) { |
| 203 | case 0: | 216 | case 0: |
| 204 | return Errno::SUCCESS; | 217 | return Errno::SUCCESS; |
| @@ -208,6 +221,10 @@ Errno TranslateNativeError(int e) { | |||
| 208 | return Errno::INVAL; | 221 | return Errno::INVAL; |
| 209 | case EMFILE: | 222 | case EMFILE: |
| 210 | return Errno::MFILE; | 223 | return Errno::MFILE; |
| 224 | case EPIPE: | ||
| 225 | return Errno::PIPE; | ||
| 226 | case ECONNABORTED: | ||
| 227 | return Errno::CONNABORTED; | ||
| 211 | case ENOTCONN: | 228 | case ENOTCONN: |
| 212 | return Errno::NOTCONN; | 229 | return Errno::NOTCONN; |
| 213 | case EAGAIN: | 230 | case EAGAIN: |
| @@ -236,13 +253,13 @@ Errno TranslateNativeError(int e) { | |||
| 236 | 253 | ||
| 237 | #endif | 254 | #endif |
| 238 | 255 | ||
| 239 | Errno GetAndLogLastError() { | 256 | Errno GetAndLogLastError(CallType call_type = CallType::Other) { |
| 240 | #ifdef _WIN32 | 257 | #ifdef _WIN32 |
| 241 | int e = WSAGetLastError(); | 258 | int e = WSAGetLastError(); |
| 242 | #else | 259 | #else |
| 243 | int e = errno; | 260 | int e = errno; |
| 244 | #endif | 261 | #endif |
| 245 | const Errno err = TranslateNativeError(e); | 262 | const Errno err = TranslateNativeError(e, call_type); |
| 246 | if (err == Errno::AGAIN || err == Errno::TIMEDOUT || err == Errno::INPROGRESS) { | 263 | if (err == Errno::AGAIN || err == Errno::TIMEDOUT || err == Errno::INPROGRESS) { |
| 247 | // These happen during normal operation, so only log them at debug level. | 264 | // These happen during normal operation, so only log them at debug level. |
| 248 | LOG_DEBUG(Network, "Socket operation error: {}", Common::NativeErrorToString(e)); | 265 | LOG_DEBUG(Network, "Socket operation error: {}", Common::NativeErrorToString(e)); |
| @@ -476,7 +493,13 @@ NetworkInstance::~NetworkInstance() { | |||
| 476 | std::optional<IPv4Address> GetHostIPv4Address() { | 493 | std::optional<IPv4Address> GetHostIPv4Address() { |
| 477 | const auto network_interface = Network::GetSelectedNetworkInterface(); | 494 | const auto network_interface = Network::GetSelectedNetworkInterface(); |
| 478 | if (!network_interface.has_value()) { | 495 | if (!network_interface.has_value()) { |
| 479 | LOG_DEBUG(Network, "GetSelectedNetworkInterface returned no interface"); | 496 | // Only print the error once to avoid log spam |
| 497 | static bool print_error = true; | ||
| 498 | if (print_error) { | ||
| 499 | LOG_ERROR(Network, "GetSelectedNetworkInterface returned no interface"); | ||
| 500 | print_error = false; | ||
| 501 | } | ||
| 502 | |||
| 480 | return {}; | 503 | return {}; |
| 481 | } | 504 | } |
| 482 | 505 | ||
| @@ -725,13 +748,17 @@ std::pair<s32, Errno> Socket::Send(std::span<const u8> message, int flags) { | |||
| 725 | ASSERT(message.size() < static_cast<size_t>(std::numeric_limits<int>::max())); | 748 | ASSERT(message.size() < static_cast<size_t>(std::numeric_limits<int>::max())); |
| 726 | ASSERT(flags == 0); | 749 | ASSERT(flags == 0); |
| 727 | 750 | ||
| 751 | int native_flags = 0; | ||
| 752 | #if YUZU_UNIX | ||
| 753 | native_flags |= MSG_NOSIGNAL; // do not send us SIGPIPE | ||
| 754 | #endif | ||
| 728 | const auto result = send(fd, reinterpret_cast<const char*>(message.data()), | 755 | const auto result = send(fd, reinterpret_cast<const char*>(message.data()), |
| 729 | static_cast<int>(message.size()), 0); | 756 | static_cast<int>(message.size()), native_flags); |
| 730 | if (result != SOCKET_ERROR) { | 757 | if (result != SOCKET_ERROR) { |
| 731 | return {static_cast<s32>(result), Errno::SUCCESS}; | 758 | return {static_cast<s32>(result), Errno::SUCCESS}; |
| 732 | } | 759 | } |
| 733 | 760 | ||
| 734 | return {-1, GetAndLogLastError()}; | 761 | return {-1, GetAndLogLastError(CallType::Send)}; |
| 735 | } | 762 | } |
| 736 | 763 | ||
| 737 | std::pair<s32, Errno> Socket::SendTo(u32 flags, std::span<const u8> message, | 764 | 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, | |||
| 753 | return {static_cast<s32>(result), Errno::SUCCESS}; | 780 | return {static_cast<s32>(result), Errno::SUCCESS}; |
| 754 | } | 781 | } |
| 755 | 782 | ||
| 756 | return {-1, GetAndLogLastError()}; | 783 | return {-1, GetAndLogLastError(CallType::Send)}; |
| 757 | } | 784 | } |
| 758 | 785 | ||
| 759 | Errno Socket::Close() { | 786 | 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 { | |||
| 33 | BADF, | 33 | BADF, |
| 34 | INVAL, | 34 | INVAL, |
| 35 | MFILE, | 35 | MFILE, |
| 36 | PIPE, | ||
| 36 | NOTCONN, | 37 | NOTCONN, |
| 37 | AGAIN, | 38 | AGAIN, |
| 38 | CONNREFUSED, | 39 | CONNREFUSED, |
| 39 | CONNRESET, | 40 | CONNRESET, |
| 41 | CONNABORTED, | ||
| 40 | HOSTUNREACH, | 42 | HOSTUNREACH, |
| 41 | NETDOWN, | 43 | NETDOWN, |
| 42 | NETUNREACH, | 44 | 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() { | |||
| 200 | }); | 200 | }); |
| 201 | 201 | ||
| 202 | if (res == network_interfaces.end()) { | 202 | if (res == network_interfaces.end()) { |
| 203 | LOG_DEBUG(Network, "Couldn't find selected interface \"{}\"", selected_network_interface); | 203 | // Only print the error once to avoid log spam |
| 204 | static bool print_error = true; | ||
| 205 | if (print_error) { | ||
| 206 | LOG_ERROR(Network, "Couldn't find selected interface \"{}\"", | ||
| 207 | selected_network_interface); | ||
| 208 | print_error = false; | ||
| 209 | } | ||
| 210 | |||
| 204 | return std::nullopt; | 211 | return std::nullopt; |
| 205 | } | 212 | } |
| 206 | 213 | ||
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{ | |||
| 135 | "The titlekey and/or titlekek is incorrect or the section header is invalid.", | 135 | "The titlekey and/or titlekek is incorrect or the section header is invalid.", |
| 136 | "The XCI file is missing a Program-type NCA.", | 136 | "The XCI file is missing a Program-type NCA.", |
| 137 | "The NCA file is not an application.", | 137 | "The NCA file is not an application.", |
| 138 | "The ExeFS partition could not be found.", | 138 | "The Program-type NCA contains no executable. An update may be required.", |
| 139 | "The XCI file has a bad header.", | 139 | "The XCI file has a bad header.", |
| 140 | "The XCI file is missing a partition.", | 140 | "The XCI file is missing a partition.", |
| 141 | "The file could not be found or does not exist.", | 141 | "The file could not be found or does not exist.", |
| @@ -169,7 +169,7 @@ constexpr std::array<const char*, 66> RESULT_MESSAGES{ | |||
| 169 | "The BKTR-type NCA has a bad Subsection block.", | 169 | "The BKTR-type NCA has a bad Subsection block.", |
| 170 | "The BKTR-type NCA has a bad Relocation bucket.", | 170 | "The BKTR-type NCA has a bad Relocation bucket.", |
| 171 | "The BKTR-type NCA has a bad Subsection bucket.", | 171 | "The BKTR-type NCA has a bad Subsection bucket.", |
| 172 | "The BKTR-type NCA is missing the base RomFS.", | 172 | "Game updates cannot be loaded directly. Load the base game instead.", |
| 173 | "The NSP or XCI does not contain an update in addition to the base game.", | 173 | "The NSP or XCI does not contain an update in addition to the base game.", |
| 174 | "The KIP file has a bad header.", | 174 | "The KIP file has a bad header.", |
| 175 | "The KIP BLZ decompression of the section failed unexpectedly.", | 175 | "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 { | |||
| 79 | ErrorBadPFSHeader, | 79 | ErrorBadPFSHeader, |
| 80 | ErrorIncorrectPFSFileSize, | 80 | ErrorIncorrectPFSFileSize, |
| 81 | ErrorBadNCAHeader, | 81 | ErrorBadNCAHeader, |
| 82 | ErrorCompressedNCA, | ||
| 83 | ErrorSparseNCA, | ||
| 84 | ErrorMissingProductionKeyFile, | 82 | ErrorMissingProductionKeyFile, |
| 85 | ErrorMissingHeaderKey, | 83 | ErrorMissingHeaderKey, |
| 86 | ErrorIncorrectHeaderKey, | 84 | ErrorIncorrectHeaderKey, |
| @@ -276,16 +274,6 @@ public: | |||
| 276 | } | 274 | } |
| 277 | 275 | ||
| 278 | /** | 276 | /** |
| 279 | * Gets the difference between the start of the IVFC header and the start of level 6 (RomFS) | ||
| 280 | * data. Needed for BKTR patching. | ||
| 281 | * | ||
| 282 | * @return IVFC offset for RomFS. | ||
| 283 | */ | ||
| 284 | virtual u64 ReadRomFSIVFCOffset() const { | ||
| 285 | return 0; | ||
| 286 | } | ||
| 287 | |||
| 288 | /** | ||
| 289 | * Get the title of the application | 277 | * Get the title of the application |
| 290 | * | 278 | * |
| 291 | * @param[out] title Reference to store the application title into | 279 | * @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) { | |||
| 76 | return nca_loader->ReadRomFS(dir); | 76 | return nca_loader->ReadRomFS(dir); |
| 77 | } | 77 | } |
| 78 | 78 | ||
| 79 | u64 AppLoader_NAX::ReadRomFSIVFCOffset() const { | ||
| 80 | return nca_loader->ReadRomFSIVFCOffset(); | ||
| 81 | } | ||
| 82 | |||
| 83 | ResultStatus AppLoader_NAX::ReadProgramId(u64& out_program_id) { | 79 | ResultStatus AppLoader_NAX::ReadProgramId(u64& out_program_id) { |
| 84 | return nca_loader->ReadProgramId(out_program_id); | 80 | return nca_loader->ReadProgramId(out_program_id); |
| 85 | } | 81 | } |
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: | |||
| 39 | LoadResult Load(Kernel::KProcess& process, Core::System& system) override; | 39 | LoadResult Load(Kernel::KProcess& process, Core::System& system) override; |
| 40 | 40 | ||
| 41 | ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; | 41 | ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; |
| 42 | u64 ReadRomFSIVFCOffset() const override; | ||
| 43 | ResultStatus ReadProgramId(u64& out_program_id) override; | 42 | ResultStatus ReadProgramId(u64& out_program_id) override; |
| 44 | 43 | ||
| 45 | ResultStatus ReadBanner(std::vector<u8>& buffer) override; | 44 | 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 @@ | |||
| 5 | 5 | ||
| 6 | #include "core/core.h" | 6 | #include "core/core.h" |
| 7 | #include "core/file_sys/content_archive.h" | 7 | #include "core/file_sys/content_archive.h" |
| 8 | #include "core/file_sys/nca_metadata.h" | ||
| 9 | #include "core/file_sys/registered_cache.h" | ||
| 8 | #include "core/file_sys/romfs_factory.h" | 10 | #include "core/file_sys/romfs_factory.h" |
| 9 | #include "core/hle/kernel/k_process.h" | 11 | #include "core/hle/kernel/k_process.h" |
| 10 | #include "core/hle/service/filesystem/filesystem.h" | 12 | #include "core/hle/service/filesystem/filesystem.h" |
| @@ -43,9 +45,23 @@ AppLoader_NCA::LoadResult AppLoader_NCA::Load(Kernel::KProcess& process, Core::S | |||
| 43 | return {ResultStatus::ErrorNCANotProgram, {}}; | 45 | return {ResultStatus::ErrorNCANotProgram, {}}; |
| 44 | } | 46 | } |
| 45 | 47 | ||
| 46 | const auto exefs = nca->GetExeFS(); | 48 | auto exefs = nca->GetExeFS(); |
| 47 | if (exefs == nullptr) { | 49 | if (exefs == nullptr) { |
| 48 | return {ResultStatus::ErrorNoExeFS, {}}; | 50 | LOG_INFO(Loader, "No ExeFS found in NCA, looking for ExeFS from update"); |
| 51 | |||
| 52 | // This NCA may be a sparse base of an installed title. | ||
| 53 | // Try to fetch the ExeFS from the installed update. | ||
| 54 | const auto& installed = system.GetContentProvider(); | ||
| 55 | const auto update_nca = installed.GetEntry(FileSys::GetUpdateTitleID(nca->GetTitleId()), | ||
| 56 | FileSys::ContentRecordType::Program); | ||
| 57 | |||
| 58 | if (update_nca) { | ||
| 59 | exefs = update_nca->GetExeFS(); | ||
| 60 | } | ||
| 61 | |||
| 62 | if (exefs == nullptr) { | ||
| 63 | return {ResultStatus::ErrorNoExeFS, {}}; | ||
| 64 | } | ||
| 49 | } | 65 | } |
| 50 | 66 | ||
| 51 | directory_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>(exefs, true); | 67 | directory_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>(exefs, true); |
| @@ -77,14 +93,6 @@ ResultStatus AppLoader_NCA::ReadRomFS(FileSys::VirtualFile& dir) { | |||
| 77 | return ResultStatus::Success; | 93 | return ResultStatus::Success; |
| 78 | } | 94 | } |
| 79 | 95 | ||
| 80 | u64 AppLoader_NCA::ReadRomFSIVFCOffset() const { | ||
| 81 | if (nca == nullptr) { | ||
| 82 | return 0; | ||
| 83 | } | ||
| 84 | |||
| 85 | return nca->GetBaseIVFCOffset(); | ||
| 86 | } | ||
| 87 | |||
| 88 | ResultStatus AppLoader_NCA::ReadProgramId(u64& out_program_id) { | 96 | ResultStatus AppLoader_NCA::ReadProgramId(u64& out_program_id) { |
| 89 | if (nca == nullptr || nca->GetStatus() != ResultStatus::Success) { | 97 | if (nca == nullptr || nca->GetStatus() != ResultStatus::Success) { |
| 90 | return ResultStatus::ErrorNotInitialized; | 98 | 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: | |||
| 40 | LoadResult Load(Kernel::KProcess& process, Core::System& system) override; | 40 | LoadResult Load(Kernel::KProcess& process, Core::System& system) override; |
| 41 | 41 | ||
| 42 | ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; | 42 | ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; |
| 43 | u64 ReadRomFSIVFCOffset() const override; | ||
| 44 | ResultStatus ReadProgramId(u64& out_program_id) override; | 43 | ResultStatus ReadProgramId(u64& out_program_id) override; |
| 45 | 44 | ||
| 46 | ResultStatus ReadBanner(std::vector<u8>& buffer) override; | 45 | 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) { | |||
| 121 | return secondary_loader->ReadRomFS(out_file); | 121 | return secondary_loader->ReadRomFS(out_file); |
| 122 | } | 122 | } |
| 123 | 123 | ||
| 124 | u64 AppLoader_NSP::ReadRomFSIVFCOffset() const { | ||
| 125 | return secondary_loader->ReadRomFSIVFCOffset(); | ||
| 126 | } | ||
| 127 | |||
| 128 | ResultStatus AppLoader_NSP::ReadUpdateRaw(FileSys::VirtualFile& out_file) { | 124 | ResultStatus AppLoader_NSP::ReadUpdateRaw(FileSys::VirtualFile& out_file) { |
| 129 | if (nsp->IsExtractedType()) { | 125 | if (nsp->IsExtractedType()) { |
| 130 | return ResultStatus::ErrorNoPackedUpdate; | 126 | 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: | |||
| 46 | LoadResult Load(Kernel::KProcess& process, Core::System& system) override; | 46 | LoadResult Load(Kernel::KProcess& process, Core::System& system) override; |
| 47 | 47 | ||
| 48 | ResultStatus ReadRomFS(FileSys::VirtualFile& out_file) override; | 48 | ResultStatus ReadRomFS(FileSys::VirtualFile& out_file) override; |
| 49 | u64 ReadRomFSIVFCOffset() const override; | ||
| 50 | ResultStatus ReadUpdateRaw(FileSys::VirtualFile& out_file) override; | 49 | ResultStatus ReadUpdateRaw(FileSys::VirtualFile& out_file) override; |
| 51 | ResultStatus ReadProgramId(u64& out_program_id) override; | 50 | ResultStatus ReadProgramId(u64& out_program_id) override; |
| 52 | ResultStatus ReadProgramIds(std::vector<u64>& out_program_ids) override; | 51 | 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) { | |||
| 89 | return nca_loader->ReadRomFS(out_file); | 89 | return nca_loader->ReadRomFS(out_file); |
| 90 | } | 90 | } |
| 91 | 91 | ||
| 92 | u64 AppLoader_XCI::ReadRomFSIVFCOffset() const { | ||
| 93 | return nca_loader->ReadRomFSIVFCOffset(); | ||
| 94 | } | ||
| 95 | |||
| 96 | ResultStatus AppLoader_XCI::ReadUpdateRaw(FileSys::VirtualFile& out_file) { | 92 | ResultStatus AppLoader_XCI::ReadUpdateRaw(FileSys::VirtualFile& out_file) { |
| 97 | u64 program_id{}; | 93 | u64 program_id{}; |
| 98 | nca_loader->ReadProgramId(program_id); | 94 | 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: | |||
| 46 | LoadResult Load(Kernel::KProcess& process, Core::System& system) override; | 46 | LoadResult Load(Kernel::KProcess& process, Core::System& system) override; |
| 47 | 47 | ||
| 48 | ResultStatus ReadRomFS(FileSys::VirtualFile& out_file) override; | 48 | ResultStatus ReadRomFS(FileSys::VirtualFile& out_file) override; |
| 49 | u64 ReadRomFSIVFCOffset() const override; | ||
| 50 | ResultStatus ReadUpdateRaw(FileSys::VirtualFile& out_file) override; | 49 | ResultStatus ReadUpdateRaw(FileSys::VirtualFile& out_file) override; |
| 51 | ResultStatus ReadProgramId(u64& out_program_id) override; | 50 | ResultStatus ReadProgramId(u64& out_program_id) override; |
| 52 | ResultStatus ReadProgramIds(std::vector<u64>& out_program_ids) override; | 51 | ResultStatus ReadProgramIds(std::vector<u64>& out_program_ids) override; |
diff --git a/src/shader_recompiler/backend/glasm/emit_glasm_image.cpp b/src/shader_recompiler/backend/glasm/emit_glasm_image.cpp index 85ee27333..d0e308124 100644 --- a/src/shader_recompiler/backend/glasm/emit_glasm_image.cpp +++ b/src/shader_recompiler/backend/glasm/emit_glasm_image.cpp | |||
| @@ -558,12 +558,15 @@ void EmitImageGradient(EmitContext& ctx, IR::Inst& inst, const IR::Value& index, | |||
| 558 | const IR::Value& coord, const IR::Value& derivatives, | 558 | const IR::Value& coord, const IR::Value& derivatives, |
| 559 | const IR::Value& offset, const IR::Value& lod_clamp) { | 559 | const IR::Value& offset, const IR::Value& lod_clamp) { |
| 560 | const auto info{inst.Flags<IR::TextureInstInfo>()}; | 560 | const auto info{inst.Flags<IR::TextureInstInfo>()}; |
| 561 | ScopedRegister dpdx, dpdy; | 561 | ScopedRegister dpdx, dpdy, coords; |
| 562 | const bool multi_component{info.num_derivates > 1 || info.has_lod_clamp}; | 562 | const bool multi_component{info.num_derivates > 1 || info.has_lod_clamp}; |
| 563 | if (multi_component) { | 563 | if (multi_component) { |
| 564 | // Allocate this early to avoid aliasing other registers | 564 | // Allocate this early to avoid aliasing other registers |
| 565 | dpdx = ScopedRegister{ctx.reg_alloc}; | 565 | dpdx = ScopedRegister{ctx.reg_alloc}; |
| 566 | dpdy = ScopedRegister{ctx.reg_alloc}; | 566 | dpdy = ScopedRegister{ctx.reg_alloc}; |
| 567 | if (info.num_derivates >= 3) { | ||
| 568 | coords = ScopedRegister{ctx.reg_alloc}; | ||
| 569 | } | ||
| 567 | } | 570 | } |
| 568 | const auto sparse_inst{PrepareSparse(inst)}; | 571 | const auto sparse_inst{PrepareSparse(inst)}; |
| 569 | const std::string_view sparse_mod{sparse_inst ? ".SPARSE" : ""}; | 572 | const std::string_view sparse_mod{sparse_inst ? ".SPARSE" : ""}; |
| @@ -580,15 +583,27 @@ void EmitImageGradient(EmitContext& ctx, IR::Inst& inst, const IR::Value& index, | |||
| 580 | "MOV.F {}.y,{}.w;", | 583 | "MOV.F {}.y,{}.w;", |
| 581 | dpdx.reg, derivatives_vec, dpdx.reg, derivatives_vec, dpdy.reg, derivatives_vec, | 584 | dpdx.reg, derivatives_vec, dpdx.reg, derivatives_vec, dpdy.reg, derivatives_vec, |
| 582 | dpdy.reg, derivatives_vec); | 585 | dpdy.reg, derivatives_vec); |
| 586 | Register final_coord; | ||
| 587 | if (info.num_derivates >= 3) { | ||
| 588 | ctx.Add("MOV.F {}.z,{}.x;" | ||
| 589 | "MOV.F {}.z,{}.y;", | ||
| 590 | dpdx.reg, coord_vec, dpdy.reg, coord_vec); | ||
| 591 | ctx.Add("MOV.F {}.x,0;" | ||
| 592 | "MOV.F {}.y,0;", | ||
| 593 | "MOV.F {}.z,0;", coords.reg, coords.reg, coords.reg); | ||
| 594 | final_coord = coords.reg; | ||
| 595 | } else { | ||
| 596 | final_coord = coord_vec; | ||
| 597 | } | ||
| 583 | if (info.has_lod_clamp) { | 598 | if (info.has_lod_clamp) { |
| 584 | const ScalarF32 lod_clamp_value{ctx.reg_alloc.Consume(lod_clamp)}; | 599 | const ScalarF32 lod_clamp_value{ctx.reg_alloc.Consume(lod_clamp)}; |
| 585 | ctx.Add("MOV.F {}.w,{};" | 600 | ctx.Add("MOV.F {}.w,{};" |
| 586 | "TXD.F.LODCLAMP{} {},{},{},{},{},{}{};", | 601 | "TXD.F.LODCLAMP{} {},{},{},{},{},{}{};", |
| 587 | dpdy.reg, lod_clamp_value, sparse_mod, ret, coord_vec, dpdx.reg, dpdy.reg, | 602 | dpdy.reg, lod_clamp_value, sparse_mod, ret, final_coord, dpdx.reg, dpdy.reg, |
| 588 | texture, type, offset_vec); | 603 | texture, type, offset_vec); |
| 589 | } else { | 604 | } else { |
| 590 | ctx.Add("TXD.F{} {},{},{},{},{},{}{};", sparse_mod, ret, coord_vec, dpdx.reg, dpdy.reg, | 605 | ctx.Add("TXD.F{} {},{},{},{},{},{}{};", sparse_mod, ret, final_coord, dpdx.reg, |
| 591 | texture, type, offset_vec); | 606 | dpdy.reg, texture, type, offset_vec); |
| 592 | } | 607 | } |
| 593 | } else { | 608 | } else { |
| 594 | ctx.Add("TXD.F{} {},{},{}.x,{}.y,{},{}{};", sparse_mod, ret, coord_vec, derivatives_vec, | 609 | ctx.Add("TXD.F{} {},{},{}.x,{}.y,{},{}{};", sparse_mod, ret, coord_vec, derivatives_vec, |
diff --git a/src/shader_recompiler/backend/glsl/emit_glsl_image.cpp b/src/shader_recompiler/backend/glsl/emit_glsl_image.cpp index 418505475..3ad668a47 100644 --- a/src/shader_recompiler/backend/glsl/emit_glsl_image.cpp +++ b/src/shader_recompiler/backend/glsl/emit_glsl_image.cpp | |||
| @@ -548,7 +548,7 @@ void EmitImageGradient(EmitContext& ctx, IR::Inst& inst, const IR::Value& index, | |||
| 548 | if (sparse_inst) { | 548 | if (sparse_inst) { |
| 549 | throw NotImplementedException("EmitImageGradient Sparse"); | 549 | throw NotImplementedException("EmitImageGradient Sparse"); |
| 550 | } | 550 | } |
| 551 | if (!offset.IsEmpty()) { | 551 | if (!offset.IsEmpty() && info.num_derivates <= 2) { |
| 552 | throw NotImplementedException("EmitImageGradient offset"); | 552 | throw NotImplementedException("EmitImageGradient offset"); |
| 553 | } | 553 | } |
| 554 | const auto texture{Texture(ctx, info, index)}; | 554 | const auto texture{Texture(ctx, info, index)}; |
| @@ -556,6 +556,12 @@ void EmitImageGradient(EmitContext& ctx, IR::Inst& inst, const IR::Value& index, | |||
| 556 | const bool multi_component{info.num_derivates > 1 || info.has_lod_clamp}; | 556 | const bool multi_component{info.num_derivates > 1 || info.has_lod_clamp}; |
| 557 | const auto derivatives_vec{ctx.var_alloc.Consume(derivatives)}; | 557 | const auto derivatives_vec{ctx.var_alloc.Consume(derivatives)}; |
| 558 | if (multi_component) { | 558 | if (multi_component) { |
| 559 | if (info.num_derivates >= 3) { | ||
| 560 | const auto offset_vec{ctx.var_alloc.Consume(offset)}; | ||
| 561 | ctx.Add("{}=textureGrad({},{},vec3({}.xz, {}.x),vec3({}.yz, {}.y));", texel, texture, | ||
| 562 | coords, derivatives_vec, offset_vec, derivatives_vec, offset_vec); | ||
| 563 | return; | ||
| 564 | } | ||
| 559 | ctx.Add("{}=textureGrad({},{},vec2({}.xz),vec2({}.yz));", texel, texture, coords, | 565 | ctx.Add("{}=textureGrad({},{},vec2({}.xz),vec2({}.yz));", texel, texture, coords, |
| 560 | derivatives_vec, derivatives_vec); | 566 | derivatives_vec, derivatives_vec); |
| 561 | } else { | 567 | } else { |
diff --git a/src/shader_recompiler/frontend/ir/modifiers.h b/src/shader_recompiler/frontend/ir/modifiers.h index 69035d462..1e9e8c8f5 100644 --- a/src/shader_recompiler/frontend/ir/modifiers.h +++ b/src/shader_recompiler/frontend/ir/modifiers.h | |||
| @@ -42,6 +42,7 @@ union TextureInstInfo { | |||
| 42 | BitField<23, 2, u32> gather_component; | 42 | BitField<23, 2, u32> gather_component; |
| 43 | BitField<25, 2, u32> num_derivates; | 43 | BitField<25, 2, u32> num_derivates; |
| 44 | BitField<27, 3, ImageFormat> image_format; | 44 | BitField<27, 3, ImageFormat> image_format; |
| 45 | BitField<30, 1, u32> ndv_is_active; | ||
| 45 | }; | 46 | }; |
| 46 | static_assert(sizeof(TextureInstInfo) <= sizeof(u32)); | 47 | static_assert(sizeof(TextureInstInfo) <= sizeof(u32)); |
| 47 | 48 | ||
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_swizzled_add.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_swizzled_add.cpp index ef4ffa54b..f00e20023 100644 --- a/src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_swizzled_add.cpp +++ b/src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_swizzled_add.cpp | |||
| @@ -19,7 +19,7 @@ void TranslatorVisitor::FSWZADD(u64 insn) { | |||
| 19 | } const fswzadd{insn}; | 19 | } const fswzadd{insn}; |
| 20 | 20 | ||
| 21 | if (fswzadd.ndv != 0) { | 21 | if (fswzadd.ndv != 0) { |
| 22 | throw NotImplementedException("FSWZADD NDV"); | 22 | LOG_WARNING(Shader, "(STUBBED) FSWZADD - NDV mode"); |
| 23 | } | 23 | } |
| 24 | 24 | ||
| 25 | const IR::F32 src_a{GetFloatReg8(insn)}; | 25 | const IR::F32 src_a{GetFloatReg8(insn)}; |
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/move_register.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/move_register.cpp index 82aec3b73..1ddfeab06 100644 --- a/src/shader_recompiler/frontend/maxwell/translate/impl/move_register.cpp +++ b/src/shader_recompiler/frontend/maxwell/translate/impl/move_register.cpp | |||
| @@ -16,8 +16,10 @@ void MOV(TranslatorVisitor& v, u64 insn, const IR::U32& src, bool is_mov32i = fa | |||
| 16 | BitField<12, 4, u64> mov32i_mask; | 16 | BitField<12, 4, u64> mov32i_mask; |
| 17 | } const mov{insn}; | 17 | } const mov{insn}; |
| 18 | 18 | ||
| 19 | if ((is_mov32i ? mov.mov32i_mask : mov.mask) != 0xf) { | 19 | u64 mask = is_mov32i ? mov.mov32i_mask : mov.mask; |
| 20 | throw NotImplementedException("Non-full move mask"); | 20 | if (mask != 0xf && mask != 0x1) { |
| 21 | LOG_WARNING(Shader, "(STUBBED) Masked Mov"); | ||
| 22 | return; | ||
| 21 | } | 23 | } |
| 22 | v.X(mov.dest_reg, src); | 24 | v.X(mov.dest_reg, src); |
| 23 | } | 25 | } |
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/not_implemented.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/not_implemented.cpp index 2f930f1ea..6203003b3 100644 --- a/src/shader_recompiler/frontend/maxwell/translate/impl/not_implemented.cpp +++ b/src/shader_recompiler/frontend/maxwell/translate/impl/not_implemented.cpp | |||
| @@ -209,7 +209,7 @@ void TranslatorVisitor::R2B(u64) { | |||
| 209 | } | 209 | } |
| 210 | 210 | ||
| 211 | void TranslatorVisitor::RAM(u64) { | 211 | void TranslatorVisitor::RAM(u64) { |
| 212 | ThrowNotImplemented(Opcode::RAM); | 212 | LOG_WARNING(Shader, "(STUBBED) RAM Instruction"); |
| 213 | } | 213 | } |
| 214 | 214 | ||
| 215 | void TranslatorVisitor::RET(u64) { | 215 | void TranslatorVisitor::RET(u64) { |
| @@ -221,7 +221,7 @@ void TranslatorVisitor::RTT(u64) { | |||
| 221 | } | 221 | } |
| 222 | 222 | ||
| 223 | void TranslatorVisitor::SAM(u64) { | 223 | void TranslatorVisitor::SAM(u64) { |
| 224 | ThrowNotImplemented(Opcode::SAM); | 224 | LOG_WARNING(Shader, "(STUBBED) SAM Instruction"); |
| 225 | } | 225 | } |
| 226 | 226 | ||
| 227 | void TranslatorVisitor::SETCRSPTR(u64) { | 227 | void TranslatorVisitor::SETCRSPTR(u64) { |
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/texture_fetch.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/texture_fetch.cpp index 2459fc30d..7a9b7fff8 100644 --- a/src/shader_recompiler/frontend/maxwell/translate/impl/texture_fetch.cpp +++ b/src/shader_recompiler/frontend/maxwell/translate/impl/texture_fetch.cpp | |||
| @@ -172,6 +172,7 @@ void Impl(TranslatorVisitor& v, u64 insn, bool aoffi, Blod blod, bool lc, | |||
| 172 | info.is_depth.Assign(tex.dc != 0 ? 1 : 0); | 172 | info.is_depth.Assign(tex.dc != 0 ? 1 : 0); |
| 173 | info.has_bias.Assign(blod == Blod::LB || blod == Blod::LBA ? 1 : 0); | 173 | info.has_bias.Assign(blod == Blod::LB || blod == Blod::LBA ? 1 : 0); |
| 174 | info.has_lod_clamp.Assign(lc ? 1 : 0); | 174 | info.has_lod_clamp.Assign(lc ? 1 : 0); |
| 175 | info.ndv_is_active.Assign(tex.ndv != 0 ? 1 : 0); | ||
| 175 | 176 | ||
| 176 | const IR::Value sample{[&]() -> IR::Value { | 177 | const IR::Value sample{[&]() -> IR::Value { |
| 177 | if (tex.dc == 0) { | 178 | if (tex.dc == 0) { |
diff --git a/src/shader_recompiler/ir_opt/constant_propagation_pass.cpp b/src/shader_recompiler/ir_opt/constant_propagation_pass.cpp index 4d81e9336..f46e55122 100644 --- a/src/shader_recompiler/ir_opt/constant_propagation_pass.cpp +++ b/src/shader_recompiler/ir_opt/constant_propagation_pass.cpp | |||
| @@ -10,6 +10,7 @@ | |||
| 10 | #include "shader_recompiler/environment.h" | 10 | #include "shader_recompiler/environment.h" |
| 11 | #include "shader_recompiler/exception.h" | 11 | #include "shader_recompiler/exception.h" |
| 12 | #include "shader_recompiler/frontend/ir/ir_emitter.h" | 12 | #include "shader_recompiler/frontend/ir/ir_emitter.h" |
| 13 | #include "shader_recompiler/frontend/ir/modifiers.h" | ||
| 13 | #include "shader_recompiler/frontend/ir/value.h" | 14 | #include "shader_recompiler/frontend/ir/value.h" |
| 14 | #include "shader_recompiler/ir_opt/passes.h" | 15 | #include "shader_recompiler/ir_opt/passes.h" |
| 15 | 16 | ||
| @@ -410,7 +411,49 @@ void FoldSelect(IR::Inst& inst) { | |||
| 410 | } | 411 | } |
| 411 | } | 412 | } |
| 412 | 413 | ||
| 414 | void FoldFPAdd32(IR::Inst& inst) { | ||
| 415 | if (FoldWhenAllImmediates(inst, [](f32 a, f32 b) { return a + b; })) { | ||
| 416 | return; | ||
| 417 | } | ||
| 418 | const IR::Value lhs_value{inst.Arg(0)}; | ||
| 419 | const IR::Value rhs_value{inst.Arg(1)}; | ||
| 420 | const auto check_neutral = [](const IR::Value& one_operand) { | ||
| 421 | return one_operand.IsImmediate() && std::abs(one_operand.F32()) == 0.0f; | ||
| 422 | }; | ||
| 423 | if (check_neutral(lhs_value)) { | ||
| 424 | inst.ReplaceUsesWith(rhs_value); | ||
| 425 | } | ||
| 426 | if (check_neutral(rhs_value)) { | ||
| 427 | inst.ReplaceUsesWith(lhs_value); | ||
| 428 | } | ||
| 429 | } | ||
| 430 | |||
| 431 | bool FoldDerivateYFromCorrection(IR::Inst& inst) { | ||
| 432 | const IR::Value lhs_value{inst.Arg(0)}; | ||
| 433 | const IR::Value rhs_value{inst.Arg(1)}; | ||
| 434 | IR::Inst* const lhs_op{lhs_value.InstRecursive()}; | ||
| 435 | IR::Inst* const rhs_op{rhs_value.InstRecursive()}; | ||
| 436 | if (lhs_op->GetOpcode() == IR::Opcode::YDirection) { | ||
| 437 | if (rhs_op->GetOpcode() != IR::Opcode::DPdyFine) { | ||
| 438 | return false; | ||
| 439 | } | ||
| 440 | inst.ReplaceUsesWith(rhs_value); | ||
| 441 | return true; | ||
| 442 | } | ||
| 443 | if (rhs_op->GetOpcode() != IR::Opcode::YDirection) { | ||
| 444 | return false; | ||
| 445 | } | ||
| 446 | if (lhs_op->GetOpcode() != IR::Opcode::DPdyFine) { | ||
| 447 | return false; | ||
| 448 | } | ||
| 449 | inst.ReplaceUsesWith(lhs_value); | ||
| 450 | return true; | ||
| 451 | } | ||
| 452 | |||
| 413 | void FoldFPMul32(IR::Inst& inst) { | 453 | void FoldFPMul32(IR::Inst& inst) { |
| 454 | if (FoldWhenAllImmediates(inst, [](f32 a, f32 b) { return a * b; })) { | ||
| 455 | return; | ||
| 456 | } | ||
| 414 | const auto control{inst.Flags<IR::FpControl>()}; | 457 | const auto control{inst.Flags<IR::FpControl>()}; |
| 415 | if (control.no_contraction) { | 458 | if (control.no_contraction) { |
| 416 | return; | 459 | return; |
| @@ -421,6 +464,9 @@ void FoldFPMul32(IR::Inst& inst) { | |||
| 421 | if (lhs_value.IsImmediate() || rhs_value.IsImmediate()) { | 464 | if (lhs_value.IsImmediate() || rhs_value.IsImmediate()) { |
| 422 | return; | 465 | return; |
| 423 | } | 466 | } |
| 467 | if (FoldDerivateYFromCorrection(inst)) { | ||
| 468 | return; | ||
| 469 | } | ||
| 424 | IR::Inst* const lhs_op{lhs_value.InstRecursive()}; | 470 | IR::Inst* const lhs_op{lhs_value.InstRecursive()}; |
| 425 | IR::Inst* const rhs_op{rhs_value.InstRecursive()}; | 471 | IR::Inst* const rhs_op{rhs_value.InstRecursive()}; |
| 426 | if (lhs_op->GetOpcode() != IR::Opcode::FPMul32 || | 472 | if (lhs_op->GetOpcode() != IR::Opcode::FPMul32 || |
| @@ -622,7 +668,12 @@ void FoldFSwizzleAdd(IR::Block& block, IR::Inst& inst) { | |||
| 622 | } | 668 | } |
| 623 | const IR::Value value_3{GetThroughCast(inst2->Arg(0).Resolve(), IR::Opcode::BitCastU32F32)}; | 669 | const IR::Value value_3{GetThroughCast(inst2->Arg(0).Resolve(), IR::Opcode::BitCastU32F32)}; |
| 624 | if (value_2 != value_3) { | 670 | if (value_2 != value_3) { |
| 625 | return; | 671 | if (!value_2.IsImmediate() || !value_3.IsImmediate()) { |
| 672 | return; | ||
| 673 | } | ||
| 674 | if (Common::BitCast<u32>(value_2.F32()) != value_3.U32()) { | ||
| 675 | return; | ||
| 676 | } | ||
| 626 | } | 677 | } |
| 627 | const IR::Value index{inst2->Arg(1)}; | 678 | const IR::Value index{inst2->Arg(1)}; |
| 628 | const IR::Value clamp{inst2->Arg(2)}; | 679 | const IR::Value clamp{inst2->Arg(2)}; |
| @@ -648,6 +699,169 @@ void FoldFSwizzleAdd(IR::Block& block, IR::Inst& inst) { | |||
| 648 | } | 699 | } |
| 649 | } | 700 | } |
| 650 | 701 | ||
| 702 | bool FindGradient3DDerivates(std::array<IR::Value, 3>& results, IR::Value coord) { | ||
| 703 | if (coord.IsImmediate()) { | ||
| 704 | return false; | ||
| 705 | } | ||
| 706 | const auto check_through_shuffle = [](IR::Value input, IR::Value& result) { | ||
| 707 | const IR::Value value_1{GetThroughCast(input.Resolve(), IR::Opcode::BitCastF32U32)}; | ||
| 708 | IR::Inst* const inst2{value_1.InstRecursive()}; | ||
| 709 | if (inst2->GetOpcode() != IR::Opcode::ShuffleIndex) { | ||
| 710 | return false; | ||
| 711 | } | ||
| 712 | const IR::Value index{inst2->Arg(1).Resolve()}; | ||
| 713 | const IR::Value clamp{inst2->Arg(2).Resolve()}; | ||
| 714 | const IR::Value segmentation_mask{inst2->Arg(3).Resolve()}; | ||
| 715 | if (!index.IsImmediate() || !clamp.IsImmediate() || !segmentation_mask.IsImmediate()) { | ||
| 716 | return false; | ||
| 717 | } | ||
| 718 | if (index.U32() != 3 && clamp.U32() != 3) { | ||
| 719 | return false; | ||
| 720 | } | ||
| 721 | result = GetThroughCast(inst2->Arg(0).Resolve(), IR::Opcode::BitCastU32F32); | ||
| 722 | return true; | ||
| 723 | }; | ||
| 724 | IR::Inst* const inst = coord.InstRecursive(); | ||
| 725 | if (inst->GetOpcode() != IR::Opcode::FSwizzleAdd) { | ||
| 726 | return false; | ||
| 727 | } | ||
| 728 | std::array<IR::Value, 3> temporary_values; | ||
| 729 | IR::Value value_1 = inst->Arg(0).Resolve(); | ||
| 730 | IR::Value value_2 = inst->Arg(1).Resolve(); | ||
| 731 | IR::Value value_3 = inst->Arg(2).Resolve(); | ||
| 732 | std::array<u32, 4> swizzles_mask_a{}; | ||
| 733 | std::array<u32, 4> swizzles_mask_b{}; | ||
| 734 | const auto resolve_mask = [](std::array<u32, 4>& mask_results, IR::Value mask) { | ||
| 735 | u32 value = mask.U32(); | ||
| 736 | for (size_t i = 0; i < 4; i++) { | ||
| 737 | mask_results[i] = (value >> (i * 2)) & 0x3; | ||
| 738 | } | ||
| 739 | }; | ||
| 740 | resolve_mask(swizzles_mask_a, value_3); | ||
| 741 | size_t coordinate_index = 0; | ||
| 742 | const auto resolve_pending = [&](IR::Value resolve_v) { | ||
| 743 | IR::Inst* const inst_r = resolve_v.InstRecursive(); | ||
| 744 | if (inst_r->GetOpcode() != IR::Opcode::FSwizzleAdd) { | ||
| 745 | return false; | ||
| 746 | } | ||
| 747 | if (!check_through_shuffle(inst_r->Arg(0).Resolve(), temporary_values[1])) { | ||
| 748 | return false; | ||
| 749 | } | ||
| 750 | if (!check_through_shuffle(inst_r->Arg(1).Resolve(), temporary_values[2])) { | ||
| 751 | return false; | ||
| 752 | } | ||
| 753 | resolve_mask(swizzles_mask_b, inst_r->Arg(2).Resolve()); | ||
| 754 | return true; | ||
| 755 | }; | ||
| 756 | if (value_1.IsImmediate() || value_2.IsImmediate()) { | ||
| 757 | return false; | ||
| 758 | } | ||
| 759 | bool should_continue = false; | ||
| 760 | if (resolve_pending(value_1)) { | ||
| 761 | should_continue = check_through_shuffle(value_2, temporary_values[0]); | ||
| 762 | coordinate_index = 0; | ||
| 763 | } | ||
| 764 | if (resolve_pending(value_2)) { | ||
| 765 | should_continue = check_through_shuffle(value_1, temporary_values[0]); | ||
| 766 | coordinate_index = 2; | ||
| 767 | } | ||
| 768 | if (!should_continue) { | ||
| 769 | return false; | ||
| 770 | } | ||
| 771 | // figure which is which | ||
| 772 | size_t zero_mask_a = 0; | ||
| 773 | size_t zero_mask_b = 0; | ||
| 774 | for (size_t i = 0; i < 4; i++) { | ||
| 775 | if (swizzles_mask_a[i] == 2 || swizzles_mask_b[i] == 2) { | ||
| 776 | // last operand can be inversed, we cannot determine a result. | ||
| 777 | return false; | ||
| 778 | } | ||
| 779 | zero_mask_a |= static_cast<size_t>(swizzles_mask_a[i] == 3 ? 1 : 0) << i; | ||
| 780 | zero_mask_b |= static_cast<size_t>(swizzles_mask_b[i] == 3 ? 1 : 0) << i; | ||
| 781 | } | ||
| 782 | static constexpr size_t ddx_pattern = 0b1010; | ||
| 783 | static constexpr size_t ddx_pattern_inv = ~ddx_pattern & 0b00001111; | ||
| 784 | if (std::popcount(zero_mask_a) != 2) { | ||
| 785 | return false; | ||
| 786 | } | ||
| 787 | if (std::popcount(zero_mask_b) != 2) { | ||
| 788 | return false; | ||
| 789 | } | ||
| 790 | if (zero_mask_a == zero_mask_b) { | ||
| 791 | return false; | ||
| 792 | } | ||
| 793 | results[0] = temporary_values[coordinate_index]; | ||
| 794 | |||
| 795 | if (coordinate_index == 0) { | ||
| 796 | if (zero_mask_b == ddx_pattern || zero_mask_b == ddx_pattern_inv) { | ||
| 797 | results[1] = temporary_values[1]; | ||
| 798 | results[2] = temporary_values[2]; | ||
| 799 | return true; | ||
| 800 | } | ||
| 801 | results[2] = temporary_values[1]; | ||
| 802 | results[1] = temporary_values[2]; | ||
| 803 | } else { | ||
| 804 | const auto assign_result = [&results](IR::Value temporary_value, size_t mask) { | ||
| 805 | if (mask == ddx_pattern || mask == ddx_pattern_inv) { | ||
| 806 | results[1] = temporary_value; | ||
| 807 | return; | ||
| 808 | } | ||
| 809 | results[2] = temporary_value; | ||
| 810 | }; | ||
| 811 | assign_result(temporary_values[1], zero_mask_b); | ||
| 812 | assign_result(temporary_values[0], zero_mask_a); | ||
| 813 | } | ||
| 814 | |||
| 815 | return true; | ||
| 816 | } | ||
| 817 | |||
| 818 | void FoldImageSampleImplicitLod(IR::Block& block, IR::Inst& inst) { | ||
| 819 | IR::TextureInstInfo info = inst.Flags<IR::TextureInstInfo>(); | ||
| 820 | auto orig_opcode = inst.GetOpcode(); | ||
| 821 | if (info.ndv_is_active == 0) { | ||
| 822 | return; | ||
| 823 | } | ||
| 824 | if (info.type != TextureType::Color3D) { | ||
| 825 | return; | ||
| 826 | } | ||
| 827 | const IR::Value handle{inst.Arg(0)}; | ||
| 828 | const IR::Value coords{inst.Arg(1)}; | ||
| 829 | const IR::Value bias_lc{inst.Arg(2)}; | ||
| 830 | const IR::Value offset{inst.Arg(3)}; | ||
| 831 | if (!offset.IsImmediate()) { | ||
| 832 | return; | ||
| 833 | } | ||
| 834 | IR::Inst* const inst2 = coords.InstRecursive(); | ||
| 835 | std::array<std::array<IR::Value, 3>, 3> results_matrix; | ||
| 836 | for (size_t i = 0; i < 3; i++) { | ||
| 837 | if (!FindGradient3DDerivates(results_matrix[i], inst2->Arg(i).Resolve())) { | ||
| 838 | return; | ||
| 839 | } | ||
| 840 | } | ||
| 841 | IR::F32 lod_clamp{}; | ||
| 842 | if (info.has_lod_clamp != 0) { | ||
| 843 | if (!bias_lc.IsImmediate()) { | ||
| 844 | lod_clamp = IR::F32{bias_lc.InstRecursive()->Arg(1).Resolve()}; | ||
| 845 | } else { | ||
| 846 | lod_clamp = IR::F32{bias_lc}; | ||
| 847 | } | ||
| 848 | } | ||
| 849 | IR::IREmitter ir{block, IR::Block::InstructionList::s_iterator_to(inst)}; | ||
| 850 | IR::Value new_coords = | ||
| 851 | ir.CompositeConstruct(results_matrix[0][0], results_matrix[1][0], results_matrix[2][0]); | ||
| 852 | IR::Value derivatives_1 = ir.CompositeConstruct(results_matrix[0][1], results_matrix[0][2], | ||
| 853 | results_matrix[1][1], results_matrix[1][2]); | ||
| 854 | IR::Value derivatives_2 = ir.CompositeConstruct(results_matrix[2][1], results_matrix[2][2]); | ||
| 855 | info.num_derivates.Assign(3); | ||
| 856 | IR::Value new_gradient_instruction = | ||
| 857 | ir.ImageGradient(handle, new_coords, derivatives_1, derivatives_2, lod_clamp, info); | ||
| 858 | IR::Inst* const new_inst = new_gradient_instruction.InstRecursive(); | ||
| 859 | if (orig_opcode == IR::Opcode::ImageSampleImplicitLod) { | ||
| 860 | new_inst->ReplaceOpcode(IR::Opcode::ImageGradient); | ||
| 861 | } | ||
| 862 | inst.ReplaceUsesWith(new_gradient_instruction); | ||
| 863 | } | ||
| 864 | |||
| 651 | void FoldConstBuffer(Environment& env, IR::Block& block, IR::Inst& inst) { | 865 | void FoldConstBuffer(Environment& env, IR::Block& block, IR::Inst& inst) { |
| 652 | const IR::Value bank{inst.Arg(0)}; | 866 | const IR::Value bank{inst.Arg(0)}; |
| 653 | const IR::Value offset{inst.Arg(1)}; | 867 | const IR::Value offset{inst.Arg(1)}; |
| @@ -743,6 +957,12 @@ void ConstantPropagation(Environment& env, IR::Block& block, IR::Inst& inst) { | |||
| 743 | case IR::Opcode::SelectF32: | 957 | case IR::Opcode::SelectF32: |
| 744 | case IR::Opcode::SelectF64: | 958 | case IR::Opcode::SelectF64: |
| 745 | return FoldSelect(inst); | 959 | return FoldSelect(inst); |
| 960 | case IR::Opcode::FPNeg32: | ||
| 961 | FoldWhenAllImmediates(inst, [](f32 a) { return -a; }); | ||
| 962 | return; | ||
| 963 | case IR::Opcode::FPAdd32: | ||
| 964 | FoldFPAdd32(inst); | ||
| 965 | return; | ||
| 746 | case IR::Opcode::FPMul32: | 966 | case IR::Opcode::FPMul32: |
| 747 | return FoldFPMul32(inst); | 967 | return FoldFPMul32(inst); |
| 748 | case IR::Opcode::LogicalAnd: | 968 | case IR::Opcode::LogicalAnd: |
| @@ -858,6 +1078,11 @@ void ConstantPropagation(Environment& env, IR::Block& block, IR::Inst& inst) { | |||
| 858 | FoldDriverConstBuffer(env, block, inst, 1); | 1078 | FoldDriverConstBuffer(env, block, inst, 1); |
| 859 | } | 1079 | } |
| 860 | break; | 1080 | break; |
| 1081 | case IR::Opcode::BindlessImageSampleImplicitLod: | ||
| 1082 | case IR::Opcode::BoundImageSampleImplicitLod: | ||
| 1083 | case IR::Opcode::ImageSampleImplicitLod: | ||
| 1084 | FoldImageSampleImplicitLod(block, inst); | ||
| 1085 | break; | ||
| 861 | default: | 1086 | default: |
| 862 | break; | 1087 | break; |
| 863 | } | 1088 | } |
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 | |||
| 50 | vulkan_blit_depth_stencil.frag | 50 | vulkan_blit_depth_stencil.frag |
| 51 | vulkan_color_clear.frag | 51 | vulkan_color_clear.frag |
| 52 | vulkan_color_clear.vert | 52 | vulkan_color_clear.vert |
| 53 | vulkan_depthstencil_clear.frag | ||
| 53 | vulkan_fidelityfx_fsr_easu_fp16.comp | 54 | vulkan_fidelityfx_fsr_easu_fp16.comp |
| 54 | vulkan_fidelityfx_fsr_easu_fp32.comp | 55 | vulkan_fidelityfx_fsr_easu_fp32.comp |
| 55 | vulkan_fidelityfx_fsr_rcas_fp16.comp | 56 | vulkan_fidelityfx_fsr_rcas_fp16.comp |
diff --git a/src/video_core/host_shaders/astc_decoder.comp b/src/video_core/host_shaders/astc_decoder.comp index bf2693559..5ff17cd0c 100644 --- a/src/video_core/host_shaders/astc_decoder.comp +++ b/src/video_core/host_shaders/astc_decoder.comp | |||
| @@ -33,26 +33,14 @@ UNIFORM(6) uint block_height_mask; | |||
| 33 | END_PUSH_CONSTANTS | 33 | END_PUSH_CONSTANTS |
| 34 | 34 | ||
| 35 | struct EncodingData { | 35 | struct EncodingData { |
| 36 | uint encoding; | 36 | uint data; |
| 37 | uint num_bits; | ||
| 38 | uint bit_value; | ||
| 39 | uint quint_trit_value; | ||
| 40 | }; | 37 | }; |
| 41 | 38 | ||
| 42 | struct TexelWeightParams { | 39 | layout(binding = BINDING_INPUT_BUFFER, std430) readonly restrict buffer InputBufferU32 { |
| 43 | uvec2 size; | ||
| 44 | uint max_weight; | ||
| 45 | bool dual_plane; | ||
| 46 | bool error_state; | ||
| 47 | bool void_extent_ldr; | ||
| 48 | bool void_extent_hdr; | ||
| 49 | }; | ||
| 50 | |||
| 51 | layout(binding = BINDING_INPUT_BUFFER, std430) readonly buffer InputBufferU32 { | ||
| 52 | uvec4 astc_data[]; | 40 | uvec4 astc_data[]; |
| 53 | }; | 41 | }; |
| 54 | 42 | ||
| 55 | layout(binding = BINDING_OUTPUT_IMAGE, rgba8) uniform writeonly image2DArray dest_image; | 43 | layout(binding = BINDING_OUTPUT_IMAGE, rgba8) uniform writeonly restrict image2DArray dest_image; |
| 56 | 44 | ||
| 57 | const uint GOB_SIZE_X_SHIFT = 6; | 45 | const uint GOB_SIZE_X_SHIFT = 6; |
| 58 | const uint GOB_SIZE_Y_SHIFT = 3; | 46 | const uint GOB_SIZE_Y_SHIFT = 3; |
| @@ -60,64 +48,21 @@ const uint GOB_SIZE_SHIFT = GOB_SIZE_X_SHIFT + GOB_SIZE_Y_SHIFT; | |||
| 60 | 48 | ||
| 61 | const uint BYTES_PER_BLOCK_LOG2 = 4; | 49 | const uint BYTES_PER_BLOCK_LOG2 = 4; |
| 62 | 50 | ||
| 63 | const int JUST_BITS = 0; | 51 | const uint JUST_BITS = 0u; |
| 64 | const int QUINT = 1; | 52 | const uint QUINT = 1u; |
| 65 | const int TRIT = 2; | 53 | const uint TRIT = 2u; |
| 66 | 54 | ||
| 67 | // ASTC Encodings data, sorted in ascending order based on their BitLength value | 55 | // ASTC Encodings data, sorted in ascending order based on their BitLength value |
| 68 | // (see GetBitLength() function) | 56 | // (see GetBitLength() function) |
| 69 | EncodingData encoding_values[22] = EncodingData[]( | 57 | const uint encoding_values[22] = uint[]( |
| 70 | EncodingData(JUST_BITS, 0, 0, 0), EncodingData(JUST_BITS, 1, 0, 0), EncodingData(TRIT, 0, 0, 0), | 58 | (JUST_BITS), (JUST_BITS | (1u << 8u)), (TRIT), (JUST_BITS | (2u << 8u)), |
| 71 | EncodingData(JUST_BITS, 2, 0, 0), EncodingData(QUINT, 0, 0, 0), EncodingData(TRIT, 1, 0, 0), | 59 | (QUINT), (TRIT | (1u << 8u)), (JUST_BITS | (3u << 8u)), (QUINT | (1u << 8u)), |
| 72 | EncodingData(JUST_BITS, 3, 0, 0), EncodingData(QUINT, 1, 0, 0), EncodingData(TRIT, 2, 0, 0), | 60 | (TRIT | (2u << 8u)), (JUST_BITS | (4u << 8u)), (QUINT | (2u << 8u)), (TRIT | (3u << 8u)), |
| 73 | EncodingData(JUST_BITS, 4, 0, 0), EncodingData(QUINT, 2, 0, 0), EncodingData(TRIT, 3, 0, 0), | 61 | (JUST_BITS | (5u << 8u)), (QUINT | (3u << 8u)), (TRIT | (4u << 8u)), (JUST_BITS | (6u << 8u)), |
| 74 | EncodingData(JUST_BITS, 5, 0, 0), EncodingData(QUINT, 3, 0, 0), EncodingData(TRIT, 4, 0, 0), | 62 | (QUINT | (4u << 8u)), (TRIT | (5u << 8u)), (JUST_BITS | (7u << 8u)), (QUINT | (5u << 8u)), |
| 75 | EncodingData(JUST_BITS, 6, 0, 0), EncodingData(QUINT, 4, 0, 0), EncodingData(TRIT, 5, 0, 0), | 63 | (TRIT | (6u << 8u)), (JUST_BITS | (8u << 8u))); |
| 76 | EncodingData(JUST_BITS, 7, 0, 0), EncodingData(QUINT, 5, 0, 0), EncodingData(TRIT, 6, 0, 0), | ||
| 77 | EncodingData(JUST_BITS, 8, 0, 0) | ||
| 78 | ); | ||
| 79 | |||
| 80 | // The following constants are expanded variants of the Replicate() | ||
| 81 | // function calls corresponding to the following arguments: | ||
| 82 | // value: index into the generated table | ||
| 83 | // num_bits: the after "REPLICATE" in the table name. i.e. 4 is num_bits in REPLICATE_4. | ||
| 84 | // to_bit: the integer after "TO_" | ||
| 85 | const uint REPLICATE_BIT_TO_7_TABLE[2] = uint[](0, 127); | ||
| 86 | const uint REPLICATE_1_BIT_TO_9_TABLE[2] = uint[](0, 511); | ||
| 87 | |||
| 88 | const uint REPLICATE_1_BIT_TO_8_TABLE[2] = uint[](0, 255); | ||
| 89 | const uint REPLICATE_2_BIT_TO_8_TABLE[4] = uint[](0, 85, 170, 255); | ||
| 90 | const uint REPLICATE_3_BIT_TO_8_TABLE[8] = uint[](0, 36, 73, 109, 146, 182, 219, 255); | ||
| 91 | const uint REPLICATE_4_BIT_TO_8_TABLE[16] = | ||
| 92 | uint[](0, 17, 34, 51, 68, 85, 102, 119, 136, 153, 170, 187, 204, 221, 238, 255); | ||
| 93 | const uint REPLICATE_5_BIT_TO_8_TABLE[32] = | ||
| 94 | uint[](0, 8, 16, 24, 33, 41, 49, 57, 66, 74, 82, 90, 99, 107, 115, 123, 132, 140, 148, 156, 165, | ||
| 95 | 173, 181, 189, 198, 206, 214, 222, 231, 239, 247, 255); | ||
| 96 | const uint REPLICATE_1_BIT_TO_6_TABLE[2] = uint[](0, 63); | ||
| 97 | const uint REPLICATE_2_BIT_TO_6_TABLE[4] = uint[](0, 21, 42, 63); | ||
| 98 | const uint REPLICATE_3_BIT_TO_6_TABLE[8] = uint[](0, 9, 18, 27, 36, 45, 54, 63); | ||
| 99 | const uint REPLICATE_4_BIT_TO_6_TABLE[16] = | ||
| 100 | uint[](0, 4, 8, 12, 17, 21, 25, 29, 34, 38, 42, 46, 51, 55, 59, 63); | ||
| 101 | const uint REPLICATE_5_BIT_TO_6_TABLE[32] = | ||
| 102 | uint[](0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 33, 35, 37, 39, 41, 43, 45, | ||
| 103 | 47, 49, 51, 53, 55, 57, 59, 61, 63); | ||
| 104 | const uint REPLICATE_6_BIT_TO_8_TABLE[64] = | ||
| 105 | uint[](0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 65, 69, 73, 77, 81, 85, 89, | ||
| 106 | 93, 97, 101, 105, 109, 113, 117, 121, 125, 130, 134, 138, 142, 146, 150, 154, 158, 162, | ||
| 107 | 166, 170, 174, 178, 182, 186, 190, 195, 199, 203, 207, 211, 215, 219, 223, 227, 231, 235, | ||
| 108 | 239, 243, 247, 251, 255); | ||
| 109 | const uint REPLICATE_7_BIT_TO_8_TABLE[128] = | ||
| 110 | uint[](0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, | ||
| 111 | 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, | ||
| 112 | 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, | ||
| 113 | 129, 131, 133, 135, 137, 139, 141, 143, 145, 147, 149, 151, 153, 155, 157, 159, 161, 163, | ||
| 114 | 165, 167, 169, 171, 173, 175, 177, 179, 181, 183, 185, 187, 189, 191, 193, 195, 197, 199, | ||
| 115 | 201, 203, 205, 207, 209, 211, 213, 215, 217, 219, 221, 223, 225, 227, 229, 231, 233, 235, | ||
| 116 | 237, 239, 241, 243, 245, 247, 249, 251, 253, 255); | ||
| 117 | 64 | ||
| 118 | // Input ASTC texture globals | 65 | // Input ASTC texture globals |
| 119 | uint current_index = 0; | ||
| 120 | int bitsread = 0; | ||
| 121 | int total_bitsread = 0; | 66 | int total_bitsread = 0; |
| 122 | uvec4 local_buff; | 67 | uvec4 local_buff; |
| 123 | 68 | ||
| @@ -125,50 +70,60 @@ uvec4 local_buff; | |||
| 125 | uvec4 color_endpoint_data; | 70 | uvec4 color_endpoint_data; |
| 126 | int color_bitsread = 0; | 71 | int color_bitsread = 0; |
| 127 | 72 | ||
| 128 | // Four values, two endpoints, four maximum partitions | 73 | // Global "vector" to be pushed into when decoding |
| 129 | uint color_values[32]; | 74 | // At most will require BLOCK_WIDTH x BLOCK_HEIGHT in single plane mode |
| 130 | int colvals_index = 0; | 75 | // At most will require BLOCK_WIDTH x BLOCK_HEIGHT x 2 in dual plane mode |
| 131 | 76 | // So the maximum would be 144 (12 x 12) elements, x 2 for two planes | |
| 132 | // Weight data globals | 77 | #define DIVCEIL(number, divisor) (number + divisor - 1) / divisor |
| 133 | uvec4 texel_weight_data; | 78 | #define ARRAY_NUM_ELEMENTS 144 |
| 134 | int texel_bitsread = 0; | 79 | #define VECTOR_ARRAY_SIZE DIVCEIL(ARRAY_NUM_ELEMENTS * 2, 4) |
| 80 | uint result_vector[ARRAY_NUM_ELEMENTS * 2]; | ||
| 135 | 81 | ||
| 136 | bool texel_flag = false; | ||
| 137 | |||
| 138 | // Global "vectors" to be pushed into when decoding | ||
| 139 | EncodingData result_vector[144]; | ||
| 140 | int result_index = 0; | 82 | int result_index = 0; |
| 83 | uint result_vector_max_index; | ||
| 84 | bool result_limit_reached = false; | ||
| 141 | 85 | ||
| 142 | EncodingData texel_vector[144]; | 86 | // EncodingData helpers |
| 143 | int texel_vector_index = 0; | 87 | uint Encoding(EncodingData val) { |
| 88 | return bitfieldExtract(val.data, 0, 8); | ||
| 89 | } | ||
| 90 | uint NumBits(EncodingData val) { | ||
| 91 | return bitfieldExtract(val.data, 8, 8); | ||
| 92 | } | ||
| 93 | uint BitValue(EncodingData val) { | ||
| 94 | return bitfieldExtract(val.data, 16, 8); | ||
| 95 | } | ||
| 96 | uint QuintTritValue(EncodingData val) { | ||
| 97 | return bitfieldExtract(val.data, 24, 8); | ||
| 98 | } | ||
| 144 | 99 | ||
| 145 | uint unquantized_texel_weights[2][144]; | 100 | void Encoding(inout EncodingData val, uint v) { |
| 101 | val.data = bitfieldInsert(val.data, v, 0, 8); | ||
| 102 | } | ||
| 103 | void NumBits(inout EncodingData val, uint v) { | ||
| 104 | val.data = bitfieldInsert(val.data, v, 8, 8); | ||
| 105 | } | ||
| 106 | void BitValue(inout EncodingData val, uint v) { | ||
| 107 | val.data = bitfieldInsert(val.data, v, 16, 8); | ||
| 108 | } | ||
| 109 | void QuintTritValue(inout EncodingData val, uint v) { | ||
| 110 | val.data = bitfieldInsert(val.data, v, 24, 8); | ||
| 111 | } | ||
| 146 | 112 | ||
| 147 | uint SwizzleOffset(uvec2 pos) { | 113 | EncodingData CreateEncodingData(uint encoding, uint num_bits, uint bit_val, uint quint_trit_val) { |
| 148 | uint x = pos.x; | 114 | return EncodingData(((encoding) << 0u) | ((num_bits) << 8u) | |
| 149 | uint y = pos.y; | 115 | ((bit_val) << 16u) | ((quint_trit_val) << 24u)); |
| 150 | return ((x % 64) / 32) * 256 + ((y % 8) / 2) * 64 + ((x % 32) / 16) * 32 + | ||
| 151 | (y % 2) * 16 + (x % 16); | ||
| 152 | } | 116 | } |
| 153 | 117 | ||
| 154 | // Replicates low num_bits such that [(to_bit - 1):(to_bit - 1 - from_bit)] | 118 | |
| 155 | // is the same as [(num_bits - 1):0] and repeats all the way down. | 119 | void ResultEmplaceBack(EncodingData val) { |
| 156 | uint Replicate(uint val, uint num_bits, uint to_bit) { | 120 | if (result_index >= result_vector_max_index) { |
| 157 | const uint v = val & uint((1 << num_bits) - 1); | 121 | // Alert callers to avoid decoding more than needed by this phase |
| 158 | uint res = v; | 122 | result_limit_reached = true; |
| 159 | uint reslen = num_bits; | 123 | return; |
| 160 | while (reslen < to_bit) { | ||
| 161 | uint comp = 0; | ||
| 162 | if (num_bits > to_bit - reslen) { | ||
| 163 | uint newshift = to_bit - reslen; | ||
| 164 | comp = num_bits - newshift; | ||
| 165 | num_bits = newshift; | ||
| 166 | } | ||
| 167 | res = uint(res << num_bits); | ||
| 168 | res = uint(res | (v >> comp)); | ||
| 169 | reslen += num_bits; | ||
| 170 | } | 124 | } |
| 171 | return res; | 125 | result_vector[result_index] = val.data; |
| 126 | ++result_index; | ||
| 172 | } | 127 | } |
| 173 | 128 | ||
| 174 | uvec4 ReplicateByteTo16(uvec4 value) { | 129 | uvec4 ReplicateByteTo16(uvec4 value) { |
| @@ -176,64 +131,40 @@ uvec4 ReplicateByteTo16(uvec4 value) { | |||
| 176 | } | 131 | } |
| 177 | 132 | ||
| 178 | uint ReplicateBitTo7(uint value) { | 133 | uint ReplicateBitTo7(uint value) { |
| 179 | return REPLICATE_BIT_TO_7_TABLE[value]; | 134 | return value * 127; |
| 180 | } | 135 | } |
| 181 | 136 | ||
| 182 | uint ReplicateBitTo9(uint value) { | 137 | uint ReplicateBitTo9(uint value) { |
| 183 | return REPLICATE_1_BIT_TO_9_TABLE[value]; | 138 | return value * 511; |
| 184 | } | 139 | } |
| 185 | 140 | ||
| 186 | uint FastReplicate(uint value, uint num_bits, uint to_bit) { | 141 | uint ReplicateBits(uint value, uint num_bits, uint to_bit) { |
| 187 | if (num_bits == 0) { | 142 | if (value == 0 || num_bits == 0) { |
| 188 | return 0; | 143 | return 0; |
| 189 | } | 144 | } |
| 190 | if (num_bits == to_bit) { | 145 | if (num_bits >= to_bit) { |
| 191 | return value; | 146 | return value; |
| 192 | } | 147 | } |
| 193 | if (to_bit == 6) { | 148 | const uint v = value & uint((1 << num_bits) - 1); |
| 194 | switch (num_bits) { | 149 | uint res = v; |
| 195 | case 1: | 150 | uint reslen = num_bits; |
| 196 | return REPLICATE_1_BIT_TO_6_TABLE[value]; | 151 | while (reslen < to_bit) { |
| 197 | case 2: | 152 | const uint num_dst_bits_to_shift_up = min(num_bits, to_bit - reslen); |
| 198 | return REPLICATE_2_BIT_TO_6_TABLE[value]; | 153 | const uint num_src_bits_to_shift_down = num_bits - num_dst_bits_to_shift_up; |
| 199 | case 3: | 154 | |
| 200 | return REPLICATE_3_BIT_TO_6_TABLE[value]; | 155 | res <<= num_dst_bits_to_shift_up; |
| 201 | case 4: | 156 | res |= (v >> num_src_bits_to_shift_down); |
| 202 | return REPLICATE_4_BIT_TO_6_TABLE[value]; | 157 | reslen += num_bits; |
| 203 | case 5: | ||
| 204 | return REPLICATE_5_BIT_TO_6_TABLE[value]; | ||
| 205 | default: | ||
| 206 | break; | ||
| 207 | } | ||
| 208 | } else { /* if (to_bit == 8) */ | ||
| 209 | switch (num_bits) { | ||
| 210 | case 1: | ||
| 211 | return REPLICATE_1_BIT_TO_8_TABLE[value]; | ||
| 212 | case 2: | ||
| 213 | return REPLICATE_2_BIT_TO_8_TABLE[value]; | ||
| 214 | case 3: | ||
| 215 | return REPLICATE_3_BIT_TO_8_TABLE[value]; | ||
| 216 | case 4: | ||
| 217 | return REPLICATE_4_BIT_TO_8_TABLE[value]; | ||
| 218 | case 5: | ||
| 219 | return REPLICATE_5_BIT_TO_8_TABLE[value]; | ||
| 220 | case 6: | ||
| 221 | return REPLICATE_6_BIT_TO_8_TABLE[value]; | ||
| 222 | case 7: | ||
| 223 | return REPLICATE_7_BIT_TO_8_TABLE[value]; | ||
| 224 | default: | ||
| 225 | break; | ||
| 226 | } | ||
| 227 | } | 158 | } |
| 228 | return Replicate(value, num_bits, to_bit); | 159 | return res; |
| 229 | } | 160 | } |
| 230 | 161 | ||
| 231 | uint FastReplicateTo8(uint value, uint num_bits) { | 162 | uint FastReplicateTo8(uint value, uint num_bits) { |
| 232 | return FastReplicate(value, num_bits, 8); | 163 | return ReplicateBits(value, num_bits, 8); |
| 233 | } | 164 | } |
| 234 | 165 | ||
| 235 | uint FastReplicateTo6(uint value, uint num_bits) { | 166 | uint FastReplicateTo6(uint value, uint num_bits) { |
| 236 | return FastReplicate(value, num_bits, 6); | 167 | return ReplicateBits(value, num_bits, 6); |
| 237 | } | 168 | } |
| 238 | 169 | ||
| 239 | uint Div3Floor(uint v) { | 170 | uint Div3Floor(uint v) { |
| @@ -266,15 +197,15 @@ uint Hash52(uint p) { | |||
| 266 | return p; | 197 | return p; |
| 267 | } | 198 | } |
| 268 | 199 | ||
| 269 | uint Select2DPartition(uint seed, uint x, uint y, uint partition_count, bool small_block) { | 200 | uint Select2DPartition(uint seed, uint x, uint y, uint partition_count) { |
| 270 | if (small_block) { | 201 | if ((block_dims.y * block_dims.x) < 32) { |
| 271 | x <<= 1; | 202 | x <<= 1; |
| 272 | y <<= 1; | 203 | y <<= 1; |
| 273 | } | 204 | } |
| 274 | 205 | ||
| 275 | seed += (partition_count - 1) * 1024; | 206 | seed += (partition_count - 1) * 1024; |
| 276 | 207 | ||
| 277 | uint rnum = Hash52(uint(seed)); | 208 | const uint rnum = Hash52(uint(seed)); |
| 278 | uint seed1 = uint(rnum & 0xF); | 209 | uint seed1 = uint(rnum & 0xF); |
| 279 | uint seed2 = uint((rnum >> 4) & 0xF); | 210 | uint seed2 = uint((rnum >> 4) & 0xF); |
| 280 | uint seed3 = uint((rnum >> 8) & 0xF); | 211 | uint seed3 = uint((rnum >> 8) & 0xF); |
| @@ -342,53 +273,52 @@ uint ExtractBits(uvec4 payload, int offset, int bits) { | |||
| 342 | if (bits <= 0) { | 273 | if (bits <= 0) { |
| 343 | return 0; | 274 | return 0; |
| 344 | } | 275 | } |
| 345 | int last_offset = offset + bits - 1; | 276 | if (bits > 32) { |
| 346 | int shifted_offset = offset >> 5; | 277 | return 0; |
| 278 | } | ||
| 279 | const int last_offset = offset + bits - 1; | ||
| 280 | const int shifted_offset = offset >> 5; | ||
| 347 | if ((last_offset >> 5) == shifted_offset) { | 281 | if ((last_offset >> 5) == shifted_offset) { |
| 348 | return bitfieldExtract(payload[shifted_offset], offset & 31, bits); | 282 | return bitfieldExtract(payload[shifted_offset], offset & 31, bits); |
| 349 | } | 283 | } |
| 350 | int first_bits = 32 - (offset & 31); | 284 | const int first_bits = 32 - (offset & 31); |
| 351 | int result_first = int(bitfieldExtract(payload[shifted_offset], offset & 31, first_bits)); | 285 | const int result_first = int(bitfieldExtract(payload[shifted_offset], offset & 31, first_bits)); |
| 352 | int result_second = int(bitfieldExtract(payload[shifted_offset + 1], 0, bits - first_bits)); | 286 | const int result_second = int(bitfieldExtract(payload[shifted_offset + 1], 0, bits - first_bits)); |
| 353 | return result_first | (result_second << first_bits); | 287 | return result_first | (result_second << first_bits); |
| 354 | } | 288 | } |
| 355 | 289 | ||
| 356 | uint StreamBits(uint num_bits) { | 290 | uint StreamBits(uint num_bits) { |
| 357 | int int_bits = int(num_bits); | 291 | const int int_bits = int(num_bits); |
| 358 | uint ret = ExtractBits(local_buff, total_bitsread, int_bits); | 292 | const uint ret = ExtractBits(local_buff, total_bitsread, int_bits); |
| 359 | total_bitsread += int_bits; | 293 | total_bitsread += int_bits; |
| 360 | return ret; | 294 | return ret; |
| 361 | } | 295 | } |
| 362 | 296 | ||
| 297 | void SkipBits(uint num_bits) { | ||
| 298 | const int int_bits = int(num_bits); | ||
| 299 | total_bitsread += int_bits; | ||
| 300 | } | ||
| 301 | |||
| 363 | uint StreamColorBits(uint num_bits) { | 302 | uint StreamColorBits(uint num_bits) { |
| 364 | uint ret = 0; | 303 | const int int_bits = int(num_bits); |
| 365 | int int_bits = int(num_bits); | 304 | const uint ret = ExtractBits(color_endpoint_data, color_bitsread, int_bits); |
| 366 | if (texel_flag) { | 305 | color_bitsread += int_bits; |
| 367 | ret = ExtractBits(texel_weight_data, texel_bitsread, int_bits); | ||
| 368 | texel_bitsread += int_bits; | ||
| 369 | } else { | ||
| 370 | ret = ExtractBits(color_endpoint_data, color_bitsread, int_bits); | ||
| 371 | color_bitsread += int_bits; | ||
| 372 | } | ||
| 373 | return ret; | 306 | return ret; |
| 374 | } | 307 | } |
| 375 | 308 | ||
| 376 | void ResultEmplaceBack(EncodingData val) { | 309 | EncodingData GetEncodingFromVector(uint index) { |
| 377 | if (texel_flag) { | 310 | const uint data = result_vector[index]; |
| 378 | texel_vector[texel_vector_index] = val; | 311 | return EncodingData(data); |
| 379 | ++texel_vector_index; | ||
| 380 | } else { | ||
| 381 | result_vector[result_index] = val; | ||
| 382 | ++result_index; | ||
| 383 | } | ||
| 384 | } | 312 | } |
| 385 | 313 | ||
| 386 | // Returns the number of bits required to encode n_vals values. | 314 | // Returns the number of bits required to encode n_vals values. |
| 387 | uint GetBitLength(uint n_vals, uint encoding_index) { | 315 | uint GetBitLength(uint n_vals, uint encoding_index) { |
| 388 | uint total_bits = encoding_values[encoding_index].num_bits * n_vals; | 316 | const EncodingData encoding_value = EncodingData(encoding_values[encoding_index]); |
| 389 | if (encoding_values[encoding_index].encoding == TRIT) { | 317 | const uint encoding = Encoding(encoding_value); |
| 318 | uint total_bits = NumBits(encoding_value) * n_vals; | ||
| 319 | if (encoding == TRIT) { | ||
| 390 | total_bits += Div5Ceil(n_vals * 8); | 320 | total_bits += Div5Ceil(n_vals * 8); |
| 391 | } else if (encoding_values[encoding_index].encoding == QUINT) { | 321 | } else if (encoding == QUINT) { |
| 392 | total_bits += Div3Ceil(n_vals * 7); | 322 | total_bits += Div3Ceil(n_vals * 7); |
| 393 | } | 323 | } |
| 394 | return total_bits; | 324 | return total_bits; |
| @@ -403,7 +333,7 @@ uint GetNumWeightValues(uvec2 size, bool dual_plane) { | |||
| 403 | } | 333 | } |
| 404 | 334 | ||
| 405 | uint GetPackedBitSize(uvec2 size, bool dual_plane, uint max_weight) { | 335 | uint GetPackedBitSize(uvec2 size, bool dual_plane, uint max_weight) { |
| 406 | uint n_vals = GetNumWeightValues(size, dual_plane); | 336 | const uint n_vals = GetNumWeightValues(size, dual_plane); |
| 407 | return GetBitLength(n_vals, max_weight); | 337 | return GetBitLength(n_vals, max_weight); |
| 408 | } | 338 | } |
| 409 | 339 | ||
| @@ -412,87 +342,74 @@ uint BitsBracket(uint bits, uint pos) { | |||
| 412 | } | 342 | } |
| 413 | 343 | ||
| 414 | uint BitsOp(uint bits, uint start, uint end) { | 344 | uint BitsOp(uint bits, uint start, uint end) { |
| 415 | if (start == end) { | 345 | const uint mask = (1 << (end - start + 1)) - 1; |
| 416 | return BitsBracket(bits, start); | ||
| 417 | } else if (start > end) { | ||
| 418 | uint t = start; | ||
| 419 | start = end; | ||
| 420 | end = t; | ||
| 421 | } | ||
| 422 | |||
| 423 | uint mask = (1 << (end - start + 1)) - 1; | ||
| 424 | return ((bits >> start) & mask); | 346 | return ((bits >> start) & mask); |
| 425 | } | 347 | } |
| 426 | 348 | ||
| 427 | void DecodeQuintBlock(uint num_bits) { | 349 | void DecodeQuintBlock(uint num_bits) { |
| 428 | uint m[3]; | 350 | uvec3 m; |
| 429 | uint q[3]; | 351 | uvec4 qQ; |
| 430 | uint Q; | ||
| 431 | m[0] = StreamColorBits(num_bits); | 352 | m[0] = StreamColorBits(num_bits); |
| 432 | Q = StreamColorBits(3); | 353 | qQ.w = StreamColorBits(3); |
| 433 | m[1] = StreamColorBits(num_bits); | 354 | m[1] = StreamColorBits(num_bits); |
| 434 | Q |= StreamColorBits(2) << 3; | 355 | qQ.w |= StreamColorBits(2) << 3; |
| 435 | m[2] = StreamColorBits(num_bits); | 356 | m[2] = StreamColorBits(num_bits); |
| 436 | Q |= StreamColorBits(2) << 5; | 357 | qQ.w |= StreamColorBits(2) << 5; |
| 437 | if (BitsOp(Q, 1, 2) == 3 && BitsOp(Q, 5, 6) == 0) { | 358 | if (BitsOp(qQ.w, 1, 2) == 3 && BitsOp(qQ.w, 5, 6) == 0) { |
| 438 | q[0] = 4; | 359 | qQ.x = 4; |
| 439 | q[1] = 4; | 360 | qQ.y = 4; |
| 440 | q[2] = (BitsBracket(Q, 0) << 2) | ((BitsBracket(Q, 4) & ~BitsBracket(Q, 0)) << 1) | | 361 | qQ.z = (BitsBracket(qQ.w, 0) << 2) | ((BitsBracket(qQ.w, 4) & ~BitsBracket(qQ.w, 0)) << 1) | |
| 441 | (BitsBracket(Q, 3) & ~BitsBracket(Q, 0)); | 362 | (BitsBracket(qQ.w, 3) & ~BitsBracket(qQ.w, 0)); |
| 442 | } else { | 363 | } else { |
| 443 | uint C = 0; | 364 | uint C = 0; |
| 444 | if (BitsOp(Q, 1, 2) == 3) { | 365 | if (BitsOp(qQ.w, 1, 2) == 3) { |
| 445 | q[2] = 4; | 366 | qQ.z = 4; |
| 446 | C = (BitsOp(Q, 3, 4) << 3) | ((~BitsOp(Q, 5, 6) & 3) << 1) | BitsBracket(Q, 0); | 367 | C = (BitsOp(qQ.w, 3, 4) << 3) | ((~BitsOp(qQ.w, 5, 6) & 3) << 1) | BitsBracket(qQ.w, 0); |
| 447 | } else { | 368 | } else { |
| 448 | q[2] = BitsOp(Q, 5, 6); | 369 | qQ.z = BitsOp(qQ.w, 5, 6); |
| 449 | C = BitsOp(Q, 0, 4); | 370 | C = BitsOp(qQ.w, 0, 4); |
| 450 | } | 371 | } |
| 451 | if (BitsOp(C, 0, 2) == 5) { | 372 | if (BitsOp(C, 0, 2) == 5) { |
| 452 | q[1] = 4; | 373 | qQ.y = 4; |
| 453 | q[0] = BitsOp(C, 3, 4); | 374 | qQ.x = BitsOp(C, 3, 4); |
| 454 | } else { | 375 | } else { |
| 455 | q[1] = BitsOp(C, 3, 4); | 376 | qQ.y = BitsOp(C, 3, 4); |
| 456 | q[0] = BitsOp(C, 0, 2); | 377 | qQ.x = BitsOp(C, 0, 2); |
| 457 | } | 378 | } |
| 458 | } | 379 | } |
| 459 | for (uint i = 0; i < 3; i++) { | 380 | for (uint i = 0; i < 3; i++) { |
| 460 | EncodingData val; | 381 | const EncodingData val = CreateEncodingData(QUINT, num_bits, m[i], qQ[i]); |
| 461 | val.encoding = QUINT; | ||
| 462 | val.num_bits = num_bits; | ||
| 463 | val.bit_value = m[i]; | ||
| 464 | val.quint_trit_value = q[i]; | ||
| 465 | ResultEmplaceBack(val); | 382 | ResultEmplaceBack(val); |
| 466 | } | 383 | } |
| 467 | } | 384 | } |
| 468 | 385 | ||
| 469 | void DecodeTritBlock(uint num_bits) { | 386 | void DecodeTritBlock(uint num_bits) { |
| 470 | uint m[5]; | 387 | uvec4 m; |
| 471 | uint t[5]; | 388 | uvec4 t; |
| 472 | uint T; | 389 | uvec3 Tm5t5; |
| 473 | m[0] = StreamColorBits(num_bits); | 390 | m[0] = StreamColorBits(num_bits); |
| 474 | T = StreamColorBits(2); | 391 | Tm5t5.x = StreamColorBits(2); |
| 475 | m[1] = StreamColorBits(num_bits); | 392 | m[1] = StreamColorBits(num_bits); |
| 476 | T |= StreamColorBits(2) << 2; | 393 | Tm5t5.x |= StreamColorBits(2) << 2; |
| 477 | m[2] = StreamColorBits(num_bits); | 394 | m[2] = StreamColorBits(num_bits); |
| 478 | T |= StreamColorBits(1) << 4; | 395 | Tm5t5.x |= StreamColorBits(1) << 4; |
| 479 | m[3] = StreamColorBits(num_bits); | 396 | m[3] = StreamColorBits(num_bits); |
| 480 | T |= StreamColorBits(2) << 5; | 397 | Tm5t5.x |= StreamColorBits(2) << 5; |
| 481 | m[4] = StreamColorBits(num_bits); | 398 | Tm5t5.y = StreamColorBits(num_bits); |
| 482 | T |= StreamColorBits(1) << 7; | 399 | Tm5t5.x |= StreamColorBits(1) << 7; |
| 483 | uint C = 0; | 400 | uint C = 0; |
| 484 | if (BitsOp(T, 2, 4) == 7) { | 401 | if (BitsOp(Tm5t5.x, 2, 4) == 7) { |
| 485 | C = (BitsOp(T, 5, 7) << 2) | BitsOp(T, 0, 1); | 402 | C = (BitsOp(Tm5t5.x, 5, 7) << 2) | BitsOp(Tm5t5.x, 0, 1); |
| 486 | t[4] = 2; | 403 | Tm5t5.z = 2; |
| 487 | t[3] = 2; | 404 | t[3] = 2; |
| 488 | } else { | 405 | } else { |
| 489 | C = BitsOp(T, 0, 4); | 406 | C = BitsOp(Tm5t5.x, 0, 4); |
| 490 | if (BitsOp(T, 5, 6) == 3) { | 407 | if (BitsOp(Tm5t5.x, 5, 6) == 3) { |
| 491 | t[4] = 2; | 408 | Tm5t5.z = 2; |
| 492 | t[3] = BitsBracket(T, 7); | 409 | t[3] = BitsBracket(Tm5t5.x, 7); |
| 493 | } else { | 410 | } else { |
| 494 | t[4] = BitsBracket(T, 7); | 411 | Tm5t5.z = BitsBracket(Tm5t5.x, 7); |
| 495 | t[3] = BitsOp(T, 5, 6); | 412 | t[3] = BitsOp(Tm5t5.x, 5, 6); |
| 496 | } | 413 | } |
| 497 | } | 414 | } |
| 498 | if (BitsOp(C, 0, 1) == 3) { | 415 | if (BitsOp(C, 0, 1) == 3) { |
| @@ -508,31 +425,31 @@ void DecodeTritBlock(uint num_bits) { | |||
| 508 | t[1] = BitsOp(C, 2, 3); | 425 | t[1] = BitsOp(C, 2, 3); |
| 509 | t[0] = (BitsBracket(C, 1) << 1) | (BitsBracket(C, 0) & ~BitsBracket(C, 1)); | 426 | t[0] = (BitsBracket(C, 1) << 1) | (BitsBracket(C, 0) & ~BitsBracket(C, 1)); |
| 510 | } | 427 | } |
| 511 | for (uint i = 0; i < 5; i++) { | 428 | for (uint i = 0; i < 4; i++) { |
| 512 | EncodingData val; | 429 | const EncodingData val = CreateEncodingData(TRIT, num_bits, m[i], t[i]); |
| 513 | val.encoding = TRIT; | ||
| 514 | val.num_bits = num_bits; | ||
| 515 | val.bit_value = m[i]; | ||
| 516 | val.quint_trit_value = t[i]; | ||
| 517 | ResultEmplaceBack(val); | 430 | ResultEmplaceBack(val); |
| 518 | } | 431 | } |
| 432 | const EncodingData val = CreateEncodingData(TRIT, num_bits, Tm5t5.y, Tm5t5.z); | ||
| 433 | ResultEmplaceBack(val); | ||
| 519 | } | 434 | } |
| 520 | 435 | ||
| 521 | void DecodeIntegerSequence(uint max_range, uint num_values) { | 436 | void DecodeIntegerSequence(uint max_range, uint num_values) { |
| 522 | EncodingData val = encoding_values[max_range]; | 437 | EncodingData val = EncodingData(encoding_values[max_range]); |
| 438 | const uint encoding = Encoding(val); | ||
| 439 | const uint num_bits = NumBits(val); | ||
| 523 | uint vals_decoded = 0; | 440 | uint vals_decoded = 0; |
| 524 | while (vals_decoded < num_values) { | 441 | while (vals_decoded < num_values && !result_limit_reached) { |
| 525 | switch (val.encoding) { | 442 | switch (encoding) { |
| 526 | case QUINT: | 443 | case QUINT: |
| 527 | DecodeQuintBlock(val.num_bits); | 444 | DecodeQuintBlock(num_bits); |
| 528 | vals_decoded += 3; | 445 | vals_decoded += 3; |
| 529 | break; | 446 | break; |
| 530 | case TRIT: | 447 | case TRIT: |
| 531 | DecodeTritBlock(val.num_bits); | 448 | DecodeTritBlock(num_bits); |
| 532 | vals_decoded += 5; | 449 | vals_decoded += 5; |
| 533 | break; | 450 | break; |
| 534 | case JUST_BITS: | 451 | case JUST_BITS: |
| 535 | val.bit_value = StreamColorBits(val.num_bits); | 452 | BitValue(val, StreamColorBits(num_bits)); |
| 536 | ResultEmplaceBack(val); | 453 | ResultEmplaceBack(val); |
| 537 | vals_decoded++; | 454 | vals_decoded++; |
| 538 | break; | 455 | break; |
| @@ -540,7 +457,7 @@ void DecodeIntegerSequence(uint max_range, uint num_values) { | |||
| 540 | } | 457 | } |
| 541 | } | 458 | } |
| 542 | 459 | ||
| 543 | void DecodeColorValues(uvec4 modes, uint num_partitions, uint color_data_bits) { | 460 | void DecodeColorValues(uvec4 modes, uint num_partitions, uint color_data_bits, out uint color_values[32]) { |
| 544 | uint num_values = 0; | 461 | uint num_values = 0; |
| 545 | for (uint i = 0; i < num_partitions; i++) { | 462 | for (uint i = 0; i < num_partitions; i++) { |
| 546 | num_values += ((modes[i] >> 2) + 1) << 1; | 463 | num_values += ((modes[i] >> 2) + 1) << 1; |
| @@ -549,7 +466,7 @@ void DecodeColorValues(uvec4 modes, uint num_partitions, uint color_data_bits) { | |||
| 549 | // TODO(ameerj): profile with binary search | 466 | // TODO(ameerj): profile with binary search |
| 550 | int range = 0; | 467 | int range = 0; |
| 551 | while (++range < encoding_values.length()) { | 468 | while (++range < encoding_values.length()) { |
| 552 | uint bit_length = GetBitLength(num_values, range); | 469 | const uint bit_length = GetBitLength(num_values, range); |
| 553 | if (bit_length > color_data_bits) { | 470 | if (bit_length > color_data_bits) { |
| 554 | break; | 471 | break; |
| 555 | } | 472 | } |
| @@ -560,48 +477,49 @@ void DecodeColorValues(uvec4 modes, uint num_partitions, uint color_data_bits) { | |||
| 560 | if (out_index >= num_values) { | 477 | if (out_index >= num_values) { |
| 561 | break; | 478 | break; |
| 562 | } | 479 | } |
| 563 | EncodingData val = result_vector[itr]; | 480 | const EncodingData val = GetEncodingFromVector(itr); |
| 564 | uint bitlen = val.num_bits; | 481 | const uint encoding = Encoding(val); |
| 565 | uint bitval = val.bit_value; | 482 | const uint bitlen = NumBits(val); |
| 483 | const uint bitval = BitValue(val); | ||
| 566 | uint A = 0, B = 0, C = 0, D = 0; | 484 | uint A = 0, B = 0, C = 0, D = 0; |
| 567 | A = ReplicateBitTo9((bitval & 1)); | 485 | A = ReplicateBitTo9((bitval & 1)); |
| 568 | switch (val.encoding) { | 486 | switch (encoding) { |
| 569 | case JUST_BITS: | 487 | case JUST_BITS: |
| 570 | color_values[out_index++] = FastReplicateTo8(bitval, bitlen); | 488 | color_values[++out_index] = FastReplicateTo8(bitval, bitlen); |
| 571 | break; | 489 | break; |
| 572 | case TRIT: { | 490 | case TRIT: { |
| 573 | D = val.quint_trit_value; | 491 | D = QuintTritValue(val); |
| 574 | switch (bitlen) { | 492 | switch (bitlen) { |
| 575 | case 1: | 493 | case 1: |
| 576 | C = 204; | 494 | C = 204; |
| 577 | break; | 495 | break; |
| 578 | case 2: { | 496 | case 2: { |
| 579 | C = 93; | 497 | C = 93; |
| 580 | uint b = (bitval >> 1) & 1; | 498 | const uint b = (bitval >> 1) & 1; |
| 581 | B = (b << 8) | (b << 4) | (b << 2) | (b << 1); | 499 | B = (b << 8) | (b << 4) | (b << 2) | (b << 1); |
| 582 | break; | 500 | break; |
| 583 | } | 501 | } |
| 584 | case 3: { | 502 | case 3: { |
| 585 | C = 44; | 503 | C = 44; |
| 586 | uint cb = (bitval >> 1) & 3; | 504 | const uint cb = (bitval >> 1) & 3; |
| 587 | B = (cb << 7) | (cb << 2) | cb; | 505 | B = (cb << 7) | (cb << 2) | cb; |
| 588 | break; | 506 | break; |
| 589 | } | 507 | } |
| 590 | case 4: { | 508 | case 4: { |
| 591 | C = 22; | 509 | C = 22; |
| 592 | uint dcb = (bitval >> 1) & 7; | 510 | const uint dcb = (bitval >> 1) & 7; |
| 593 | B = (dcb << 6) | dcb; | 511 | B = (dcb << 6) | dcb; |
| 594 | break; | 512 | break; |
| 595 | } | 513 | } |
| 596 | case 5: { | 514 | case 5: { |
| 597 | C = 11; | 515 | C = 11; |
| 598 | uint edcb = (bitval >> 1) & 0xF; | 516 | const uint edcb = (bitval >> 1) & 0xF; |
| 599 | B = (edcb << 5) | (edcb >> 2); | 517 | B = (edcb << 5) | (edcb >> 2); |
| 600 | break; | 518 | break; |
| 601 | } | 519 | } |
| 602 | case 6: { | 520 | case 6: { |
| 603 | C = 5; | 521 | C = 5; |
| 604 | uint fedcb = (bitval >> 1) & 0x1F; | 522 | const uint fedcb = (bitval >> 1) & 0x1F; |
| 605 | B = (fedcb << 4) | (fedcb >> 4); | 523 | B = (fedcb << 4) | (fedcb >> 4); |
| 606 | break; | 524 | break; |
| 607 | } | 525 | } |
| @@ -609,32 +527,32 @@ void DecodeColorValues(uvec4 modes, uint num_partitions, uint color_data_bits) { | |||
| 609 | break; | 527 | break; |
| 610 | } | 528 | } |
| 611 | case QUINT: { | 529 | case QUINT: { |
| 612 | D = val.quint_trit_value; | 530 | D = QuintTritValue(val); |
| 613 | switch (bitlen) { | 531 | switch (bitlen) { |
| 614 | case 1: | 532 | case 1: |
| 615 | C = 113; | 533 | C = 113; |
| 616 | break; | 534 | break; |
| 617 | case 2: { | 535 | case 2: { |
| 618 | C = 54; | 536 | C = 54; |
| 619 | uint b = (bitval >> 1) & 1; | 537 | const uint b = (bitval >> 1) & 1; |
| 620 | B = (b << 8) | (b << 3) | (b << 2); | 538 | B = (b << 8) | (b << 3) | (b << 2); |
| 621 | break; | 539 | break; |
| 622 | } | 540 | } |
| 623 | case 3: { | 541 | case 3: { |
| 624 | C = 26; | 542 | C = 26; |
| 625 | uint cb = (bitval >> 1) & 3; | 543 | const uint cb = (bitval >> 1) & 3; |
| 626 | B = (cb << 7) | (cb << 1) | (cb >> 1); | 544 | B = (cb << 7) | (cb << 1) | (cb >> 1); |
| 627 | break; | 545 | break; |
| 628 | } | 546 | } |
| 629 | case 4: { | 547 | case 4: { |
| 630 | C = 13; | 548 | C = 13; |
| 631 | uint dcb = (bitval >> 1) & 7; | 549 | const uint dcb = (bitval >> 1) & 7; |
| 632 | B = (dcb << 6) | (dcb >> 1); | 550 | B = (dcb << 6) | (dcb >> 1); |
| 633 | break; | 551 | break; |
| 634 | } | 552 | } |
| 635 | case 5: { | 553 | case 5: { |
| 636 | C = 6; | 554 | C = 6; |
| 637 | uint edcb = (bitval >> 1) & 0xF; | 555 | const uint edcb = (bitval >> 1) & 0xF; |
| 638 | B = (edcb << 5) | (edcb >> 3); | 556 | B = (edcb << 5) | (edcb >> 3); |
| 639 | break; | 557 | break; |
| 640 | } | 558 | } |
| @@ -642,11 +560,11 @@ void DecodeColorValues(uvec4 modes, uint num_partitions, uint color_data_bits) { | |||
| 642 | break; | 560 | break; |
| 643 | } | 561 | } |
| 644 | } | 562 | } |
| 645 | if (val.encoding != JUST_BITS) { | 563 | if (encoding != JUST_BITS) { |
| 646 | uint T = (D * C) + B; | 564 | uint T = (D * C) + B; |
| 647 | T ^= A; | 565 | T ^= A; |
| 648 | T = (A & 0x80) | (T >> 2); | 566 | T = (A & 0x80) | (T >> 2); |
| 649 | color_values[out_index++] = T; | 567 | color_values[++out_index] = T; |
| 650 | } | 568 | } |
| 651 | } | 569 | } |
| 652 | } | 570 | } |
| @@ -664,139 +582,136 @@ ivec2 BitTransferSigned(int a, int b) { | |||
| 664 | } | 582 | } |
| 665 | 583 | ||
| 666 | uvec4 ClampByte(ivec4 color) { | 584 | uvec4 ClampByte(ivec4 color) { |
| 667 | for (uint i = 0; i < 4; ++i) { | 585 | return uvec4(clamp(color, 0, 255)); |
| 668 | color[i] = (color[i] < 0) ? 0 : ((color[i] > 255) ? 255 : color[i]); | ||
| 669 | } | ||
| 670 | return uvec4(color); | ||
| 671 | } | 586 | } |
| 672 | 587 | ||
| 673 | ivec4 BlueContract(int a, int r, int g, int b) { | 588 | ivec4 BlueContract(int a, int r, int g, int b) { |
| 674 | return ivec4(a, (r + b) >> 1, (g + b) >> 1, b); | 589 | return ivec4(a, (r + b) >> 1, (g + b) >> 1, b); |
| 675 | } | 590 | } |
| 676 | 591 | ||
| 677 | void ComputeEndpoints(out uvec4 ep1, out uvec4 ep2, uint color_endpoint_mode) { | 592 | void ComputeEndpoints(out uvec4 ep1, out uvec4 ep2, uint color_endpoint_mode, uint color_values[32], |
| 593 | inout uint colvals_index) { | ||
| 678 | #define READ_UINT_VALUES(N) \ | 594 | #define READ_UINT_VALUES(N) \ |
| 679 | uint v[N]; \ | 595 | uvec4 V[2]; \ |
| 680 | for (uint i = 0; i < N; i++) { \ | 596 | for (uint i = 0; i < N; i++) { \ |
| 681 | v[i] = color_values[colvals_index++]; \ | 597 | V[i / 4][i % 4] = color_values[++colvals_index]; \ |
| 682 | } | 598 | } |
| 683 | |||
| 684 | #define READ_INT_VALUES(N) \ | 599 | #define READ_INT_VALUES(N) \ |
| 685 | int v[N]; \ | 600 | ivec4 V[2]; \ |
| 686 | for (uint i = 0; i < N; i++) { \ | 601 | for (uint i = 0; i < N; i++) { \ |
| 687 | v[i] = int(color_values[colvals_index++]); \ | 602 | V[i / 4][i % 4] = int(color_values[++colvals_index]); \ |
| 688 | } | 603 | } |
| 689 | 604 | ||
| 690 | switch (color_endpoint_mode) { | 605 | switch (color_endpoint_mode) { |
| 691 | case 0: { | 606 | case 0: { |
| 692 | READ_UINT_VALUES(2) | 607 | READ_UINT_VALUES(2) |
| 693 | ep1 = uvec4(0xFF, v[0], v[0], v[0]); | 608 | ep1 = uvec4(0xFF, V[0].x, V[0].x, V[0].x); |
| 694 | ep2 = uvec4(0xFF, v[1], v[1], v[1]); | 609 | ep2 = uvec4(0xFF, V[0].y, V[0].y, V[0].y); |
| 695 | break; | 610 | break; |
| 696 | } | 611 | } |
| 697 | case 1: { | 612 | case 1: { |
| 698 | READ_UINT_VALUES(2) | 613 | READ_UINT_VALUES(2) |
| 699 | uint L0 = (v[0] >> 2) | (v[1] & 0xC0); | 614 | const uint L0 = (V[0].x >> 2) | (V[0].y & 0xC0); |
| 700 | uint L1 = min(L0 + (v[1] & 0x3F), 0xFFU); | 615 | const uint L1 = min(L0 + (V[0].y & 0x3F), 0xFFU); |
| 701 | ep1 = uvec4(0xFF, L0, L0, L0); | 616 | ep1 = uvec4(0xFF, L0, L0, L0); |
| 702 | ep2 = uvec4(0xFF, L1, L1, L1); | 617 | ep2 = uvec4(0xFF, L1, L1, L1); |
| 703 | break; | 618 | break; |
| 704 | } | 619 | } |
| 705 | case 4: { | 620 | case 4: { |
| 706 | READ_UINT_VALUES(4) | 621 | READ_UINT_VALUES(4) |
| 707 | ep1 = uvec4(v[2], v[0], v[0], v[0]); | 622 | ep1 = uvec4(V[0].z, V[0].x, V[0].x, V[0].x); |
| 708 | ep2 = uvec4(v[3], v[1], v[1], v[1]); | 623 | ep2 = uvec4(V[0].w, V[0].y, V[0].y, V[0].y); |
| 709 | break; | 624 | break; |
| 710 | } | 625 | } |
| 711 | case 5: { | 626 | case 5: { |
| 712 | READ_INT_VALUES(4) | 627 | READ_INT_VALUES(4) |
| 713 | ivec2 transferred = BitTransferSigned(v[1], v[0]); | 628 | ivec2 transferred = BitTransferSigned(V[0].y, V[0].x); |
| 714 | v[1] = transferred.x; | 629 | V[0].y = transferred.x; |
| 715 | v[0] = transferred.y; | 630 | V[0].x = transferred.y; |
| 716 | transferred = BitTransferSigned(v[3], v[2]); | 631 | transferred = BitTransferSigned(V[0].w, V[0].z); |
| 717 | v[3] = transferred.x; | 632 | V[0].w = transferred.x; |
| 718 | v[2] = transferred.y; | 633 | V[0].z = transferred.y; |
| 719 | ep1 = ClampByte(ivec4(v[2], v[0], v[0], v[0])); | 634 | ep1 = ClampByte(ivec4(V[0].z, V[0].x, V[0].x, V[0].x)); |
| 720 | ep2 = ClampByte(ivec4(v[2] + v[3], v[0] + v[1], v[0] + v[1], v[0] + v[1])); | 635 | ep2 = ClampByte(ivec4(V[0].z + V[0].w, V[0].x + V[0].y, V[0].x + V[0].y, V[0].x + V[0].y)); |
| 721 | break; | 636 | break; |
| 722 | } | 637 | } |
| 723 | case 6: { | 638 | case 6: { |
| 724 | READ_UINT_VALUES(4) | 639 | READ_UINT_VALUES(4) |
| 725 | ep1 = uvec4(0xFF, (v[0] * v[3]) >> 8, (v[1] * v[3]) >> 8, (v[2] * v[3]) >> 8); | 640 | ep1 = uvec4(0xFF, (V[0].x * V[0].w) >> 8, (V[0].y * V[0].w) >> 8, (V[0].z * V[0].w) >> 8); |
| 726 | ep2 = uvec4(0xFF, v[0], v[1], v[2]); | 641 | ep2 = uvec4(0xFF, V[0].x, V[0].y, V[0].z); |
| 727 | break; | 642 | break; |
| 728 | } | 643 | } |
| 729 | case 8: { | 644 | case 8: { |
| 730 | READ_UINT_VALUES(6) | 645 | READ_UINT_VALUES(6) |
| 731 | if ((v[1] + v[3] + v[5]) >= (v[0] + v[2] + v[4])) { | 646 | if ((V[0].y + V[0].w + V[1].y) >= (V[0].x + V[0].z + V[1].x)) { |
| 732 | ep1 = uvec4(0xFF, v[0], v[2], v[4]); | 647 | ep1 = uvec4(0xFF, V[0].x, V[0].z, V[1].x); |
| 733 | ep2 = uvec4(0xFF, v[1], v[3], v[5]); | 648 | ep2 = uvec4(0xFF, V[0].y, V[0].w, V[1].y); |
| 734 | } else { | 649 | } else { |
| 735 | ep1 = uvec4(BlueContract(0xFF, int(v[1]), int(v[3]), int(v[5]))); | 650 | ep1 = uvec4(BlueContract(0xFF, int(V[0].y), int(V[0].w), int(V[1].y))); |
| 736 | ep2 = uvec4(BlueContract(0xFF, int(v[0]), int(v[2]), int(v[4]))); | 651 | ep2 = uvec4(BlueContract(0xFF, int(V[0].x), int(V[0].z), int(V[1].x))); |
| 737 | } | 652 | } |
| 738 | break; | 653 | break; |
| 739 | } | 654 | } |
| 740 | case 9: { | 655 | case 9: { |
| 741 | READ_INT_VALUES(6) | 656 | READ_INT_VALUES(6) |
| 742 | ivec2 transferred = BitTransferSigned(v[1], v[0]); | 657 | ivec2 transferred = BitTransferSigned(V[0].y, V[0].x); |
| 743 | v[1] = transferred.x; | 658 | V[0].y = transferred.x; |
| 744 | v[0] = transferred.y; | 659 | V[0].x = transferred.y; |
| 745 | transferred = BitTransferSigned(v[3], v[2]); | 660 | transferred = BitTransferSigned(V[0].w, V[0].z); |
| 746 | v[3] = transferred.x; | 661 | V[0].w = transferred.x; |
| 747 | v[2] = transferred.y; | 662 | V[0].z = transferred.y; |
| 748 | transferred = BitTransferSigned(v[5], v[4]); | 663 | transferred = BitTransferSigned(V[1].y, V[1].x); |
| 749 | v[5] = transferred.x; | 664 | V[1].y = transferred.x; |
| 750 | v[4] = transferred.y; | 665 | V[1].x = transferred.y; |
| 751 | if ((v[1] + v[3] + v[5]) >= 0) { | 666 | if ((V[0].y + V[0].w + V[1].y) >= 0) { |
| 752 | ep1 = ClampByte(ivec4(0xFF, v[0], v[2], v[4])); | 667 | ep1 = ClampByte(ivec4(0xFF, V[0].x, V[0].z, V[1].x)); |
| 753 | ep2 = ClampByte(ivec4(0xFF, v[0] + v[1], v[2] + v[3], v[4] + v[5])); | 668 | ep2 = ClampByte(ivec4(0xFF, V[0].x + V[0].y, V[0].z + V[0].w, V[1].x + V[1].y)); |
| 754 | } else { | 669 | } else { |
| 755 | ep1 = ClampByte(BlueContract(0xFF, v[0] + v[1], v[2] + v[3], v[4] + v[5])); | 670 | ep1 = ClampByte(BlueContract(0xFF, V[0].x + V[0].y, V[0].z + V[0].w, V[1].x + V[1].y)); |
| 756 | ep2 = ClampByte(BlueContract(0xFF, v[0], v[2], v[4])); | 671 | ep2 = ClampByte(BlueContract(0xFF, V[0].x, V[0].z, V[1].x)); |
| 757 | } | 672 | } |
| 758 | break; | 673 | break; |
| 759 | } | 674 | } |
| 760 | case 10: { | 675 | case 10: { |
| 761 | READ_UINT_VALUES(6) | 676 | READ_UINT_VALUES(6) |
| 762 | ep1 = uvec4(v[4], (v[0] * v[3]) >> 8, (v[1] * v[3]) >> 8, (v[2] * v[3]) >> 8); | 677 | ep1 = uvec4(V[1].x, (V[0].x * V[0].w) >> 8, (V[0].y * V[0].w) >> 8, (V[0].z * V[0].w) >> 8); |
| 763 | ep2 = uvec4(v[5], v[0], v[1], v[2]); | 678 | ep2 = uvec4(V[1].y, V[0].x, V[0].y, V[0].z); |
| 764 | break; | 679 | break; |
| 765 | } | 680 | } |
| 766 | case 12: { | 681 | case 12: { |
| 767 | READ_UINT_VALUES(8) | 682 | READ_UINT_VALUES(8) |
| 768 | if ((v[1] + v[3] + v[5]) >= (v[0] + v[2] + v[4])) { | 683 | if ((V[0].y + V[0].w + V[1].y) >= (V[0].x + V[0].z + V[1].x)) { |
| 769 | ep1 = uvec4(v[6], v[0], v[2], v[4]); | 684 | ep1 = uvec4(V[1].z, V[0].x, V[0].z, V[1].x); |
| 770 | ep2 = uvec4(v[7], v[1], v[3], v[5]); | 685 | ep2 = uvec4(V[1].w, V[0].y, V[0].w, V[1].y); |
| 771 | } else { | 686 | } else { |
| 772 | ep1 = uvec4(BlueContract(int(v[7]), int(v[1]), int(v[3]), int(v[5]))); | 687 | ep1 = uvec4(BlueContract(int(V[1].w), int(V[0].y), int(V[0].w), int(V[1].y))); |
| 773 | ep2 = uvec4(BlueContract(int(v[6]), int(v[0]), int(v[2]), int(v[4]))); | 688 | ep2 = uvec4(BlueContract(int(V[1].z), int(V[0].x), int(V[0].z), int(V[1].x))); |
| 774 | } | 689 | } |
| 775 | break; | 690 | break; |
| 776 | } | 691 | } |
| 777 | case 13: { | 692 | case 13: { |
| 778 | READ_INT_VALUES(8) | 693 | READ_INT_VALUES(8) |
| 779 | ivec2 transferred = BitTransferSigned(v[1], v[0]); | 694 | ivec2 transferred = BitTransferSigned(V[0].y, V[0].x); |
| 780 | v[1] = transferred.x; | 695 | V[0].y = transferred.x; |
| 781 | v[0] = transferred.y; | 696 | V[0].x = transferred.y; |
| 782 | transferred = BitTransferSigned(v[3], v[2]); | 697 | transferred = BitTransferSigned(V[0].w, V[0].z); |
| 783 | v[3] = transferred.x; | 698 | V[0].w = transferred.x; |
| 784 | v[2] = transferred.y; | 699 | V[0].z = transferred.y; |
| 785 | 700 | ||
| 786 | transferred = BitTransferSigned(v[5], v[4]); | 701 | transferred = BitTransferSigned(V[1].y, V[1].x); |
| 787 | v[5] = transferred.x; | 702 | V[1].y = transferred.x; |
| 788 | v[4] = transferred.y; | 703 | V[1].x = transferred.y; |
| 789 | 704 | ||
| 790 | transferred = BitTransferSigned(v[7], v[6]); | 705 | transferred = BitTransferSigned(V[1].w, V[1].z); |
| 791 | v[7] = transferred.x; | 706 | V[1].w = transferred.x; |
| 792 | v[6] = transferred.y; | 707 | V[1].z = transferred.y; |
| 793 | 708 | ||
| 794 | if ((v[1] + v[3] + v[5]) >= 0) { | 709 | if ((V[0].y + V[0].w + V[1].y) >= 0) { |
| 795 | ep1 = ClampByte(ivec4(v[6], v[0], v[2], v[4])); | 710 | ep1 = ClampByte(ivec4(V[1].z, V[0].x, V[0].z, V[1].x)); |
| 796 | ep2 = ClampByte(ivec4(v[7] + v[6], v[0] + v[1], v[2] + v[3], v[4] + v[5])); | 711 | ep2 = ClampByte(ivec4(V[1].w + V[1].z, V[0].x + V[0].y, V[0].z + V[0].w, V[1].x + V[1].y)); |
| 797 | } else { | 712 | } else { |
| 798 | ep1 = ClampByte(BlueContract(v[6] + v[7], v[0] + v[1], v[2] + v[3], v[4] + v[5])); | 713 | ep1 = ClampByte(BlueContract(V[1].z + V[1].w, V[0].x + V[0].y, V[0].z + V[0].w, V[1].x + V[1].y)); |
| 799 | ep2 = ClampByte(BlueContract(v[6], v[0], v[2], v[4])); | 714 | ep2 = ClampByte(BlueContract(V[1].z, V[0].x, V[0].z, V[1].x)); |
| 800 | } | 715 | } |
| 801 | break; | 716 | break; |
| 802 | } | 717 | } |
| @@ -812,36 +727,34 @@ void ComputeEndpoints(out uvec4 ep1, out uvec4 ep2, uint color_endpoint_mode) { | |||
| 812 | } | 727 | } |
| 813 | 728 | ||
| 814 | uint UnquantizeTexelWeight(EncodingData val) { | 729 | uint UnquantizeTexelWeight(EncodingData val) { |
| 815 | uint bitval = val.bit_value; | 730 | const uint encoding = Encoding(val); |
| 816 | uint bitlen = val.num_bits; | 731 | const uint bitlen = NumBits(val); |
| 817 | uint A = ReplicateBitTo7((bitval & 1)); | 732 | const uint bitval = BitValue(val); |
| 733 | const uint A = ReplicateBitTo7((bitval & 1)); | ||
| 818 | uint B = 0, C = 0, D = 0; | 734 | uint B = 0, C = 0, D = 0; |
| 819 | uint result = 0; | 735 | uint result = 0; |
| 820 | switch (val.encoding) { | 736 | const uint bitlen_0_results[5] = {0, 16, 32, 48, 64}; |
| 737 | switch (encoding) { | ||
| 821 | case JUST_BITS: | 738 | case JUST_BITS: |
| 822 | result = FastReplicateTo6(bitval, bitlen); | 739 | return FastReplicateTo6(bitval, bitlen); |
| 823 | break; | ||
| 824 | case TRIT: { | 740 | case TRIT: { |
| 825 | D = val.quint_trit_value; | 741 | D = QuintTritValue(val); |
| 826 | switch (bitlen) { | 742 | switch (bitlen) { |
| 827 | case 0: { | 743 | case 0: |
| 828 | uint results[3] = {0, 32, 63}; | 744 | return bitlen_0_results[D * 2]; |
| 829 | result = results[D]; | ||
| 830 | break; | ||
| 831 | } | ||
| 832 | case 1: { | 745 | case 1: { |
| 833 | C = 50; | 746 | C = 50; |
| 834 | break; | 747 | break; |
| 835 | } | 748 | } |
| 836 | case 2: { | 749 | case 2: { |
| 837 | C = 23; | 750 | C = 23; |
| 838 | uint b = (bitval >> 1) & 1; | 751 | const uint b = (bitval >> 1) & 1; |
| 839 | B = (b << 6) | (b << 2) | b; | 752 | B = (b << 6) | (b << 2) | b; |
| 840 | break; | 753 | break; |
| 841 | } | 754 | } |
| 842 | case 3: { | 755 | case 3: { |
| 843 | C = 11; | 756 | C = 11; |
| 844 | uint cb = (bitval >> 1) & 3; | 757 | const uint cb = (bitval >> 1) & 3; |
| 845 | B = (cb << 5) | cb; | 758 | B = (cb << 5) | cb; |
| 846 | break; | 759 | break; |
| 847 | } | 760 | } |
| @@ -851,20 +764,17 @@ uint UnquantizeTexelWeight(EncodingData val) { | |||
| 851 | break; | 764 | break; |
| 852 | } | 765 | } |
| 853 | case QUINT: { | 766 | case QUINT: { |
| 854 | D = val.quint_trit_value; | 767 | D = QuintTritValue(val); |
| 855 | switch (bitlen) { | 768 | switch (bitlen) { |
| 856 | case 0: { | 769 | case 0: |
| 857 | uint results[5] = {0, 16, 32, 47, 63}; | 770 | return bitlen_0_results[D]; |
| 858 | result = results[D]; | ||
| 859 | break; | ||
| 860 | } | ||
| 861 | case 1: { | 771 | case 1: { |
| 862 | C = 28; | 772 | C = 28; |
| 863 | break; | 773 | break; |
| 864 | } | 774 | } |
| 865 | case 2: { | 775 | case 2: { |
| 866 | C = 13; | 776 | C = 13; |
| 867 | uint b = (bitval >> 1) & 1; | 777 | const uint b = (bitval >> 1) & 1; |
| 868 | B = (b << 6) | (b << 1); | 778 | B = (b << 6) | (b << 1); |
| 869 | break; | 779 | break; |
| 870 | } | 780 | } |
| @@ -872,7 +782,7 @@ uint UnquantizeTexelWeight(EncodingData val) { | |||
| 872 | break; | 782 | break; |
| 873 | } | 783 | } |
| 874 | } | 784 | } |
| 875 | if (val.encoding != JUST_BITS && bitlen > 0) { | 785 | if (encoding != JUST_BITS && bitlen > 0) { |
| 876 | result = D * C + B; | 786 | result = D * C + B; |
| 877 | result ^= A; | 787 | result ^= A; |
| 878 | result = (A & 0x20) | (result >> 2); | 788 | result = (A & 0x20) | (result >> 2); |
| @@ -883,61 +793,77 @@ uint UnquantizeTexelWeight(EncodingData val) { | |||
| 883 | return result; | 793 | return result; |
| 884 | } | 794 | } |
| 885 | 795 | ||
| 886 | void UnquantizeTexelWeights(bool dual_plane, uvec2 size) { | 796 | void UnquantizeTexelWeights(uvec2 size, bool is_dual_plane) { |
| 887 | uint weight_idx = 0; | 797 | const uint num_planes = is_dual_plane ? 2 : 1; |
| 888 | uint unquantized[2][144]; | 798 | const uint area = size.x * size.y; |
| 889 | uint area = size.x * size.y; | 799 | const uint loop_count = min(result_index, area * num_planes); |
| 890 | for (uint itr = 0; itr < texel_vector_index; itr++) { | 800 | for (uint itr = 0; itr < loop_count; ++itr) { |
| 891 | unquantized[0][weight_idx] = UnquantizeTexelWeight(texel_vector[itr]); | 801 | result_vector[itr] = |
| 892 | if (dual_plane) { | 802 | UnquantizeTexelWeight(GetEncodingFromVector(itr)); |
| 893 | ++itr; | ||
| 894 | unquantized[1][weight_idx] = UnquantizeTexelWeight(texel_vector[itr]); | ||
| 895 | if (itr == texel_vector_index) { | ||
| 896 | break; | ||
| 897 | } | ||
| 898 | } | ||
| 899 | if (++weight_idx >= (area)) | ||
| 900 | break; | ||
| 901 | } | 803 | } |
| 804 | } | ||
| 805 | |||
| 806 | uint GetUnquantizedTexelWieght(uint offset_base, uint plane, bool is_dual_plane) { | ||
| 807 | const uint offset = is_dual_plane ? 2 * offset_base + plane : offset_base; | ||
| 808 | return result_vector[offset]; | ||
| 809 | } | ||
| 902 | 810 | ||
| 811 | uvec4 GetUnquantizedWeightVector(uint t, uint s, uvec2 size, uint plane_index, bool is_dual_plane) { | ||
| 903 | const uint Ds = uint((block_dims.x * 0.5f + 1024) / (block_dims.x - 1)); | 812 | const uint Ds = uint((block_dims.x * 0.5f + 1024) / (block_dims.x - 1)); |
| 904 | const uint Dt = uint((block_dims.y * 0.5f + 1024) / (block_dims.y - 1)); | 813 | const uint Dt = uint((block_dims.y * 0.5f + 1024) / (block_dims.y - 1)); |
| 905 | const uint k_plane_scale = dual_plane ? 2 : 1; | 814 | const uint area = size.x * size.y; |
| 906 | for (uint plane = 0; plane < k_plane_scale; plane++) { | 815 | |
| 907 | for (uint t = 0; t < block_dims.y; t++) { | 816 | const uint cs = Ds * s; |
| 908 | for (uint s = 0; s < block_dims.x; s++) { | 817 | const uint ct = Dt * t; |
| 909 | uint cs = Ds * s; | 818 | const uint gs = (cs * (size.x - 1) + 32) >> 6; |
| 910 | uint ct = Dt * t; | 819 | const uint gt = (ct * (size.y - 1) + 32) >> 6; |
| 911 | uint gs = (cs * (size.x - 1) + 32) >> 6; | 820 | const uint js = gs >> 4; |
| 912 | uint gt = (ct * (size.y - 1) + 32) >> 6; | 821 | const uint fs = gs & 0xF; |
| 913 | uint js = gs >> 4; | 822 | const uint jt = gt >> 4; |
| 914 | uint fs = gs & 0xF; | 823 | const uint ft = gt & 0x0F; |
| 915 | uint jt = gt >> 4; | 824 | const uint w11 = (fs * ft + 8) >> 4; |
| 916 | uint ft = gt & 0x0F; | 825 | const uint w10 = ft - w11; |
| 917 | uint w11 = (fs * ft + 8) >> 4; | 826 | const uint w01 = fs - w11; |
| 918 | uint w10 = ft - w11; | 827 | const uint w00 = 16 - fs - ft + w11; |
| 919 | uint w01 = fs - w11; | 828 | const uvec4 w = uvec4(w00, w01, w10, w11); |
| 920 | uint w00 = 16 - fs - ft + w11; | 829 | const uint v0 = jt * size.x + js; |
| 921 | uvec4 w = uvec4(w00, w01, w10, w11); | 830 | |
| 922 | uint v0 = jt * size.x + js; | 831 | uvec4 p0 = uvec4(0); |
| 923 | 832 | uvec4 p1 = uvec4(0); | |
| 924 | uvec4 p = uvec4(0); | 833 | |
| 925 | if (v0 < area) { | 834 | if (v0 < area) { |
| 926 | p.x = unquantized[plane][v0]; | 835 | const uint offset_base = v0; |
| 927 | } | 836 | p0.x = GetUnquantizedTexelWieght(offset_base, 0, is_dual_plane); |
| 928 | if ((v0 + 1) < (area)) { | 837 | p1.x = GetUnquantizedTexelWieght(offset_base, 1, is_dual_plane); |
| 929 | p.y = unquantized[plane][v0 + 1]; | 838 | } |
| 930 | } | 839 | if ((v0 + 1) < (area)) { |
| 931 | if ((v0 + size.x) < (area)) { | 840 | const uint offset_base = v0 + 1; |
| 932 | p.z = unquantized[plane][(v0 + size.x)]; | 841 | p0.y = GetUnquantizedTexelWieght(offset_base, 0, is_dual_plane); |
| 933 | } | 842 | p1.y = GetUnquantizedTexelWieght(offset_base, 1, is_dual_plane); |
| 934 | if ((v0 + size.x + 1) < (area)) { | 843 | } |
| 935 | p.w = unquantized[plane][(v0 + size.x + 1)]; | 844 | if ((v0 + size.x) < (area)) { |
| 936 | } | 845 | const uint offset_base = v0 + size.x; |
| 937 | unquantized_texel_weights[plane][t * block_dims.x + s] = (uint(dot(p, w)) + 8) >> 4; | 846 | p0.z = GetUnquantizedTexelWieght(offset_base, 0, is_dual_plane); |
| 938 | } | 847 | p1.z = GetUnquantizedTexelWieght(offset_base, 1, is_dual_plane); |
| 848 | } | ||
| 849 | if ((v0 + size.x + 1) < (area)) { | ||
| 850 | const uint offset_base = v0 + size.x + 1; | ||
| 851 | p0.w = GetUnquantizedTexelWieght(offset_base, 0, is_dual_plane); | ||
| 852 | p1.w = GetUnquantizedTexelWieght(offset_base, 1, is_dual_plane); | ||
| 853 | } | ||
| 854 | |||
| 855 | const uint primary_weight = (uint(dot(p0, w)) + 8) >> 4; | ||
| 856 | |||
| 857 | uvec4 weight_vec = uvec4(primary_weight); | ||
| 858 | |||
| 859 | if (is_dual_plane) { | ||
| 860 | const uint secondary_weight = (uint(dot(p1, w)) + 8) >> 4; | ||
| 861 | for (uint c = 0; c < 4; c++) { | ||
| 862 | const bool is_secondary = ((plane_index + 1u) & 3u) == c; | ||
| 863 | weight_vec[c] = is_secondary ? secondary_weight : primary_weight; | ||
| 939 | } | 864 | } |
| 940 | } | 865 | } |
| 866 | return weight_vec; | ||
| 941 | } | 867 | } |
| 942 | 868 | ||
| 943 | int FindLayout(uint mode) { | 869 | int FindLayout(uint mode) { |
| @@ -971,80 +897,96 @@ int FindLayout(uint mode) { | |||
| 971 | return 5; | 897 | return 5; |
| 972 | } | 898 | } |
| 973 | 899 | ||
| 974 | TexelWeightParams DecodeBlockInfo() { | 900 | |
| 975 | TexelWeightParams params = TexelWeightParams(uvec2(0), 0, false, false, false, false); | 901 | void FillError(ivec3 coord) { |
| 976 | uint mode = StreamBits(11); | 902 | for (uint j = 0; j < block_dims.y; j++) { |
| 903 | for (uint i = 0; i < block_dims.x; i++) { | ||
| 904 | imageStore(dest_image, coord + ivec3(i, j, 0), vec4(0.0, 0.0, 0.0, 0.0)); | ||
| 905 | } | ||
| 906 | } | ||
| 907 | } | ||
| 908 | |||
| 909 | void FillVoidExtentLDR(ivec3 coord) { | ||
| 910 | SkipBits(52); | ||
| 911 | const uint r_u = StreamBits(16); | ||
| 912 | const uint g_u = StreamBits(16); | ||
| 913 | const uint b_u = StreamBits(16); | ||
| 914 | const uint a_u = StreamBits(16); | ||
| 915 | const float a = float(a_u) / 65535.0f; | ||
| 916 | const float r = float(r_u) / 65535.0f; | ||
| 917 | const float g = float(g_u) / 65535.0f; | ||
| 918 | const float b = float(b_u) / 65535.0f; | ||
| 919 | for (uint j = 0; j < block_dims.y; j++) { | ||
| 920 | for (uint i = 0; i < block_dims.x; i++) { | ||
| 921 | imageStore(dest_image, coord + ivec3(i, j, 0), vec4(r, g, b, a)); | ||
| 922 | } | ||
| 923 | } | ||
| 924 | } | ||
| 925 | |||
| 926 | bool IsError(uint mode) { | ||
| 977 | if ((mode & 0x1ff) == 0x1fc) { | 927 | if ((mode & 0x1ff) == 0x1fc) { |
| 978 | if ((mode & 0x200) != 0) { | 928 | if ((mode & 0x200) != 0) { |
| 979 | params.void_extent_hdr = true; | 929 | // params.void_extent_hdr = true; |
| 980 | } else { | 930 | return true; |
| 981 | params.void_extent_ldr = true; | ||
| 982 | } | 931 | } |
| 983 | if ((mode & 0x400) == 0 || StreamBits(1) == 0) { | 932 | if ((mode & 0x400) == 0 || StreamBits(1) == 0) { |
| 984 | params.error_state = true; | 933 | return true; |
| 985 | } | 934 | } |
| 986 | return params; | 935 | return false; |
| 987 | } | 936 | } |
| 988 | if ((mode & 0xf) == 0) { | 937 | if ((mode & 0xf) == 0) { |
| 989 | params.error_state = true; | 938 | return true; |
| 990 | return params; | ||
| 991 | } | 939 | } |
| 992 | if ((mode & 3) == 0 && (mode & 0x1c0) == 0x1c0) { | 940 | if ((mode & 3) == 0 && (mode & 0x1c0) == 0x1c0) { |
| 993 | params.error_state = true; | 941 | return true; |
| 994 | return params; | ||
| 995 | } | 942 | } |
| 943 | return false; | ||
| 944 | } | ||
| 945 | |||
| 946 | uvec2 DecodeBlockSize(uint mode) { | ||
| 996 | uint A, B; | 947 | uint A, B; |
| 997 | uint mode_layout = FindLayout(mode); | 948 | switch (FindLayout(mode)) { |
| 998 | switch (mode_layout) { | ||
| 999 | case 0: | 949 | case 0: |
| 1000 | A = (mode >> 5) & 0x3; | 950 | A = (mode >> 5) & 0x3; |
| 1001 | B = (mode >> 7) & 0x3; | 951 | B = (mode >> 7) & 0x3; |
| 1002 | params.size = uvec2(B + 4, A + 2); | 952 | return uvec2(B + 4, A + 2); |
| 1003 | break; | ||
| 1004 | case 1: | 953 | case 1: |
| 1005 | A = (mode >> 5) & 0x3; | 954 | A = (mode >> 5) & 0x3; |
| 1006 | B = (mode >> 7) & 0x3; | 955 | B = (mode >> 7) & 0x3; |
| 1007 | params.size = uvec2(B + 8, A + 2); | 956 | return uvec2(B + 8, A + 2); |
| 1008 | break; | ||
| 1009 | case 2: | 957 | case 2: |
| 1010 | A = (mode >> 5) & 0x3; | 958 | A = (mode >> 5) & 0x3; |
| 1011 | B = (mode >> 7) & 0x3; | 959 | B = (mode >> 7) & 0x3; |
| 1012 | params.size = uvec2(A + 2, B + 8); | 960 | return uvec2(A + 2, B + 8); |
| 1013 | break; | ||
| 1014 | case 3: | 961 | case 3: |
| 1015 | A = (mode >> 5) & 0x3; | 962 | A = (mode >> 5) & 0x3; |
| 1016 | B = (mode >> 7) & 0x1; | 963 | B = (mode >> 7) & 0x1; |
| 1017 | params.size = uvec2(A + 2, B + 6); | 964 | return uvec2(A + 2, B + 6); |
| 1018 | break; | ||
| 1019 | case 4: | 965 | case 4: |
| 1020 | A = (mode >> 5) & 0x3; | 966 | A = (mode >> 5) & 0x3; |
| 1021 | B = (mode >> 7) & 0x1; | 967 | B = (mode >> 7) & 0x1; |
| 1022 | params.size = uvec2(B + 2, A + 2); | 968 | return uvec2(B + 2, A + 2); |
| 1023 | break; | ||
| 1024 | case 5: | 969 | case 5: |
| 1025 | A = (mode >> 5) & 0x3; | 970 | A = (mode >> 5) & 0x3; |
| 1026 | params.size = uvec2(12, A + 2); | 971 | return uvec2(12, A + 2); |
| 1027 | break; | ||
| 1028 | case 6: | 972 | case 6: |
| 1029 | A = (mode >> 5) & 0x3; | 973 | A = (mode >> 5) & 0x3; |
| 1030 | params.size = uvec2(A + 2, 12); | 974 | return uvec2(A + 2, 12); |
| 1031 | break; | ||
| 1032 | case 7: | 975 | case 7: |
| 1033 | params.size = uvec2(6, 10); | 976 | return uvec2(6, 10); |
| 1034 | break; | ||
| 1035 | case 8: | 977 | case 8: |
| 1036 | params.size = uvec2(10, 6); | 978 | return uvec2(10, 6); |
| 1037 | break; | ||
| 1038 | case 9: | 979 | case 9: |
| 1039 | A = (mode >> 5) & 0x3; | 980 | A = (mode >> 5) & 0x3; |
| 1040 | B = (mode >> 9) & 0x3; | 981 | B = (mode >> 9) & 0x3; |
| 1041 | params.size = uvec2(A + 6, B + 6); | 982 | return uvec2(A + 6, B + 6); |
| 1042 | break; | ||
| 1043 | default: | 983 | default: |
| 1044 | params.error_state = true; | 984 | return uvec2(0); |
| 1045 | break; | ||
| 1046 | } | 985 | } |
| 1047 | params.dual_plane = (mode_layout != 9) && ((mode & 0x400) != 0); | 986 | } |
| 987 | |||
| 988 | uint DecodeMaxWeight(uint mode) { | ||
| 989 | const uint mode_layout = FindLayout(mode); | ||
| 1048 | uint weight_index = (mode & 0x10) != 0 ? 1 : 0; | 990 | uint weight_index = (mode & 0x10) != 0 ? 1 : 0; |
| 1049 | if (mode_layout < 5) { | 991 | if (mode_layout < 5) { |
| 1050 | weight_index |= (mode & 0x3) << 1; | 992 | weight_index |= (mode & 0x3) << 1; |
| @@ -1053,64 +995,34 @@ TexelWeightParams DecodeBlockInfo() { | |||
| 1053 | } | 995 | } |
| 1054 | weight_index -= 2; | 996 | weight_index -= 2; |
| 1055 | if ((mode_layout != 9) && ((mode & 0x200) != 0)) { | 997 | if ((mode_layout != 9) && ((mode & 0x200) != 0)) { |
| 1056 | const int max_weights[6] = int[6](7, 8, 9, 10, 11, 12); | 998 | weight_index += 6; |
| 1057 | params.max_weight = max_weights[weight_index]; | ||
| 1058 | } else { | ||
| 1059 | const int max_weights[6] = int[6](1, 2, 3, 4, 5, 6); | ||
| 1060 | params.max_weight = max_weights[weight_index]; | ||
| 1061 | } | ||
| 1062 | return params; | ||
| 1063 | } | ||
| 1064 | |||
| 1065 | void FillError(ivec3 coord) { | ||
| 1066 | for (uint j = 0; j < block_dims.y; j++) { | ||
| 1067 | for (uint i = 0; i < block_dims.x; i++) { | ||
| 1068 | imageStore(dest_image, coord + ivec3(i, j, 0), vec4(0.0, 0.0, 0.0, 0.0)); | ||
| 1069 | } | ||
| 1070 | } | ||
| 1071 | } | ||
| 1072 | |||
| 1073 | void FillVoidExtentLDR(ivec3 coord) { | ||
| 1074 | StreamBits(52); | ||
| 1075 | uint r_u = StreamBits(16); | ||
| 1076 | uint g_u = StreamBits(16); | ||
| 1077 | uint b_u = StreamBits(16); | ||
| 1078 | uint a_u = StreamBits(16); | ||
| 1079 | float a = float(a_u) / 65535.0f; | ||
| 1080 | float r = float(r_u) / 65535.0f; | ||
| 1081 | float g = float(g_u) / 65535.0f; | ||
| 1082 | float b = float(b_u) / 65535.0f; | ||
| 1083 | for (uint j = 0; j < block_dims.y; j++) { | ||
| 1084 | for (uint i = 0; i < block_dims.x; i++) { | ||
| 1085 | imageStore(dest_image, coord + ivec3(i, j, 0), vec4(r, g, b, a)); | ||
| 1086 | } | ||
| 1087 | } | 999 | } |
| 1000 | return weight_index + 1; | ||
| 1088 | } | 1001 | } |
| 1089 | 1002 | ||
| 1090 | void DecompressBlock(ivec3 coord) { | 1003 | void DecompressBlock(ivec3 coord) { |
| 1091 | TexelWeightParams params = DecodeBlockInfo(); | 1004 | uint mode = StreamBits(11); |
| 1092 | if (params.error_state) { | 1005 | if (IsError(mode)) { |
| 1093 | FillError(coord); | ||
| 1094 | return; | ||
| 1095 | } | ||
| 1096 | if (params.void_extent_hdr) { | ||
| 1097 | FillError(coord); | 1006 | FillError(coord); |
| 1098 | return; | 1007 | return; |
| 1099 | } | 1008 | } |
| 1100 | if (params.void_extent_ldr) { | 1009 | if ((mode & 0x1ff) == 0x1fc) { |
| 1010 | // params.void_extent_ldr = true; | ||
| 1101 | FillVoidExtentLDR(coord); | 1011 | FillVoidExtentLDR(coord); |
| 1102 | return; | 1012 | return; |
| 1103 | } | 1013 | } |
| 1104 | if ((params.size.x > block_dims.x) || (params.size.y > block_dims.y)) { | 1014 | const uvec2 size_params = DecodeBlockSize(mode); |
| 1015 | if ((size_params.x > block_dims.x) || (size_params.y > block_dims.y)) { | ||
| 1105 | FillError(coord); | 1016 | FillError(coord); |
| 1106 | return; | 1017 | return; |
| 1107 | } | 1018 | } |
| 1108 | uint num_partitions = StreamBits(2) + 1; | 1019 | const uint num_partitions = StreamBits(2) + 1; |
| 1109 | if (num_partitions > 4 || (num_partitions == 4 && params.dual_plane)) { | 1020 | const uint mode_layout = FindLayout(mode); |
| 1021 | const bool dual_plane = (mode_layout != 9) && ((mode & 0x400) != 0); | ||
| 1022 | if (num_partitions > 4 || (num_partitions == 4 && dual_plane)) { | ||
| 1110 | FillError(coord); | 1023 | FillError(coord); |
| 1111 | return; | 1024 | return; |
| 1112 | } | 1025 | } |
| 1113 | int plane_index = -1; | ||
| 1114 | uint partition_index = 1; | 1026 | uint partition_index = 1; |
| 1115 | uvec4 color_endpoint_mode = uvec4(0); | 1027 | uvec4 color_endpoint_mode = uvec4(0); |
| 1116 | uint ced_pointer = 0; | 1028 | uint ced_pointer = 0; |
| @@ -1122,8 +1034,9 @@ void DecompressBlock(ivec3 coord) { | |||
| 1122 | partition_index = StreamBits(10); | 1034 | partition_index = StreamBits(10); |
| 1123 | base_cem = StreamBits(6); | 1035 | base_cem = StreamBits(6); |
| 1124 | } | 1036 | } |
| 1125 | uint base_mode = base_cem & 3; | 1037 | const uint base_mode = base_cem & 3; |
| 1126 | uint weight_bits = GetPackedBitSize(params.size, params.dual_plane, params.max_weight); | 1038 | const uint max_weight = DecodeMaxWeight(mode); |
| 1039 | const uint weight_bits = GetPackedBitSize(size_params, dual_plane, max_weight); | ||
| 1127 | uint remaining_bits = 128 - weight_bits - total_bitsread; | 1040 | uint remaining_bits = 128 - weight_bits - total_bitsread; |
| 1128 | uint extra_cem_bits = 0; | 1041 | uint extra_cem_bits = 0; |
| 1129 | if (base_mode > 0) { | 1042 | if (base_mode > 0) { |
| @@ -1142,10 +1055,7 @@ void DecompressBlock(ivec3 coord) { | |||
| 1142 | } | 1055 | } |
| 1143 | } | 1056 | } |
| 1144 | remaining_bits -= extra_cem_bits; | 1057 | remaining_bits -= extra_cem_bits; |
| 1145 | uint plane_selector_bits = 0; | 1058 | const uint plane_selector_bits = dual_plane ? 2 : 0; |
| 1146 | if (params.dual_plane) { | ||
| 1147 | plane_selector_bits = 2; | ||
| 1148 | } | ||
| 1149 | remaining_bits -= plane_selector_bits; | 1059 | remaining_bits -= plane_selector_bits; |
| 1150 | if (remaining_bits > 128) { | 1060 | if (remaining_bits > 128) { |
| 1151 | // Bad data, more remaining bits than 4 bytes | 1061 | // Bad data, more remaining bits than 4 bytes |
| @@ -1153,17 +1063,17 @@ void DecompressBlock(ivec3 coord) { | |||
| 1153 | return; | 1063 | return; |
| 1154 | } | 1064 | } |
| 1155 | // Read color data... | 1065 | // Read color data... |
| 1156 | uint color_data_bits = remaining_bits; | 1066 | const uint color_data_bits = remaining_bits; |
| 1157 | while (remaining_bits > 0) { | 1067 | while (remaining_bits > 0) { |
| 1158 | int nb = int(min(remaining_bits, 32U)); | 1068 | const int nb = int(min(remaining_bits, 32U)); |
| 1159 | uint b = StreamBits(nb); | 1069 | const uint b = StreamBits(nb); |
| 1160 | color_endpoint_data[ced_pointer] = uint(bitfieldExtract(b, 0, nb)); | 1070 | color_endpoint_data[ced_pointer] = uint(bitfieldExtract(b, 0, nb)); |
| 1161 | ++ced_pointer; | 1071 | ++ced_pointer; |
| 1162 | remaining_bits -= nb; | 1072 | remaining_bits -= nb; |
| 1163 | } | 1073 | } |
| 1164 | plane_index = int(StreamBits(plane_selector_bits)); | 1074 | const uint plane_index = uint(StreamBits(plane_selector_bits)); |
| 1165 | if (base_mode > 0) { | 1075 | if (base_mode > 0) { |
| 1166 | uint extra_cem = StreamBits(extra_cem_bits); | 1076 | const uint extra_cem = StreamBits(extra_cem_bits); |
| 1167 | uint cem = (extra_cem << 6) | base_cem; | 1077 | uint cem = (extra_cem << 6) | base_cem; |
| 1168 | cem >>= 2; | 1078 | cem >>= 2; |
| 1169 | uvec4 C = uvec4(0); | 1079 | uvec4 C = uvec4(0); |
| @@ -1185,70 +1095,80 @@ void DecompressBlock(ivec3 coord) { | |||
| 1185 | color_endpoint_mode[i] |= M[i]; | 1095 | color_endpoint_mode[i] |= M[i]; |
| 1186 | } | 1096 | } |
| 1187 | } else if (num_partitions > 1) { | 1097 | } else if (num_partitions > 1) { |
| 1188 | uint cem = base_cem >> 2; | 1098 | const uint cem = base_cem >> 2; |
| 1189 | for (uint i = 0; i < num_partitions; i++) { | 1099 | for (uint i = 0; i < num_partitions; i++) { |
| 1190 | color_endpoint_mode[i] = cem; | 1100 | color_endpoint_mode[i] = cem; |
| 1191 | } | 1101 | } |
| 1192 | } | 1102 | } |
| 1193 | DecodeColorValues(color_endpoint_mode, num_partitions, color_data_bits); | ||
| 1194 | 1103 | ||
| 1195 | uvec4 endpoints[4][2]; | 1104 | uvec4 endpoints0[4]; |
| 1196 | for (uint i = 0; i < num_partitions; i++) { | 1105 | uvec4 endpoints1[4]; |
| 1197 | ComputeEndpoints(endpoints[i][0], endpoints[i][1], color_endpoint_mode[i]); | 1106 | { |
| 1107 | // This decode phase should at most push 32 elements into the vector | ||
| 1108 | result_vector_max_index = 32; | ||
| 1109 | uint color_values[32]; | ||
| 1110 | uint colvals_index = 0; | ||
| 1111 | DecodeColorValues(color_endpoint_mode, num_partitions, color_data_bits, color_values); | ||
| 1112 | for (uint i = 0; i < num_partitions; i++) { | ||
| 1113 | ComputeEndpoints(endpoints0[i], endpoints1[i], color_endpoint_mode[i], color_values, | ||
| 1114 | colvals_index); | ||
| 1115 | } | ||
| 1198 | } | 1116 | } |
| 1117 | color_endpoint_data = local_buff; | ||
| 1118 | color_endpoint_data = bitfieldReverse(color_endpoint_data).wzyx; | ||
| 1119 | const uint clear_byte_start = (weight_bits >> 3) + 1; | ||
| 1199 | 1120 | ||
| 1200 | texel_weight_data = local_buff; | 1121 | const uint byte_insert = ExtractBits(color_endpoint_data, int(clear_byte_start - 1) * 8, 8) & |
| 1201 | texel_weight_data = bitfieldReverse(texel_weight_data).wzyx; | 1122 | uint(((1 << (weight_bits % 8)) - 1)); |
| 1202 | uint clear_byte_start = | 1123 | const uint vec_index = (clear_byte_start - 1) >> 2; |
| 1203 | (GetPackedBitSize(params.size, params.dual_plane, params.max_weight) >> 3) + 1; | 1124 | color_endpoint_data[vec_index] = bitfieldInsert(color_endpoint_data[vec_index], byte_insert, |
| 1204 | 1125 | int((clear_byte_start - 1) % 4) * 8, 8); | |
| 1205 | uint byte_insert = ExtractBits(texel_weight_data, int(clear_byte_start - 1) * 8, 8) & | ||
| 1206 | uint( | ||
| 1207 | ((1 << (GetPackedBitSize(params.size, params.dual_plane, params.max_weight) % 8)) - 1)); | ||
| 1208 | uint vec_index = (clear_byte_start - 1) >> 2; | ||
| 1209 | texel_weight_data[vec_index] = | ||
| 1210 | bitfieldInsert(texel_weight_data[vec_index], byte_insert, int((clear_byte_start - 1) % 4) * 8, 8); | ||
| 1211 | for (uint i = clear_byte_start; i < 16; ++i) { | 1126 | for (uint i = clear_byte_start; i < 16; ++i) { |
| 1212 | uint idx = i >> 2; | 1127 | const uint idx = i >> 2; |
| 1213 | texel_weight_data[idx] = bitfieldInsert(texel_weight_data[idx], 0, int(i % 4) * 8, 8); | 1128 | color_endpoint_data[idx] = bitfieldInsert(color_endpoint_data[idx], 0, int(i % 4) * 8, 8); |
| 1214 | } | 1129 | } |
| 1215 | texel_flag = true; // use texel "vector" and bit stream in integer decoding | ||
| 1216 | DecodeIntegerSequence(params.max_weight, GetNumWeightValues(params.size, params.dual_plane)); | ||
| 1217 | 1130 | ||
| 1218 | UnquantizeTexelWeights(params.dual_plane, params.size); | 1131 | // Re-init vector variables for next decode phase |
| 1132 | result_index = 0; | ||
| 1133 | color_bitsread = 0; | ||
| 1134 | result_limit_reached = false; | ||
| 1219 | 1135 | ||
| 1136 | // The limit for the Unquantize phase, avoids decoding more data than needed. | ||
| 1137 | result_vector_max_index = size_params.x * size_params.y; | ||
| 1138 | if (dual_plane) { | ||
| 1139 | result_vector_max_index *= 2; | ||
| 1140 | } | ||
| 1141 | DecodeIntegerSequence(max_weight, GetNumWeightValues(size_params, dual_plane)); | ||
| 1142 | |||
| 1143 | UnquantizeTexelWeights(size_params, dual_plane); | ||
| 1220 | for (uint j = 0; j < block_dims.y; j++) { | 1144 | for (uint j = 0; j < block_dims.y; j++) { |
| 1221 | for (uint i = 0; i < block_dims.x; i++) { | 1145 | for (uint i = 0; i < block_dims.x; i++) { |
| 1222 | uint local_partition = 0; | 1146 | uint local_partition = 0; |
| 1223 | if (num_partitions > 1) { | 1147 | if (num_partitions > 1) { |
| 1224 | local_partition = Select2DPartition(partition_index, i, j, num_partitions, | 1148 | local_partition = Select2DPartition(partition_index, i, j, num_partitions); |
| 1225 | (block_dims.y * block_dims.x) < 32); | ||
| 1226 | } | ||
| 1227 | vec4 p; | ||
| 1228 | uvec4 C0 = ReplicateByteTo16(endpoints[local_partition][0]); | ||
| 1229 | uvec4 C1 = ReplicateByteTo16(endpoints[local_partition][1]); | ||
| 1230 | uvec4 plane_vec = uvec4(0); | ||
| 1231 | uvec4 weight_vec = uvec4(0); | ||
| 1232 | for (uint c = 0; c < 4; c++) { | ||
| 1233 | if (params.dual_plane && (((plane_index + 1) & 3) == c)) { | ||
| 1234 | plane_vec[c] = 1; | ||
| 1235 | } | ||
| 1236 | weight_vec[c] = unquantized_texel_weights[plane_vec[c]][j * block_dims.x + i]; | ||
| 1237 | } | 1149 | } |
| 1238 | vec4 Cf = vec4((C0 * (uvec4(64) - weight_vec) + C1 * weight_vec + uvec4(32)) / 64); | 1150 | const uvec4 C0 = ReplicateByteTo16(endpoints0[local_partition]); |
| 1239 | p = (Cf / 65535.0); | 1151 | const uvec4 C1 = ReplicateByteTo16(endpoints1[local_partition]); |
| 1152 | const uvec4 weight_vec = GetUnquantizedWeightVector(j, i, size_params, plane_index, dual_plane); | ||
| 1153 | const vec4 Cf = | ||
| 1154 | vec4((C0 * (uvec4(64) - weight_vec) + C1 * weight_vec + uvec4(32)) / 64); | ||
| 1155 | const vec4 p = (Cf / 65535.0f); | ||
| 1240 | imageStore(dest_image, coord + ivec3(i, j, 0), p.gbar); | 1156 | imageStore(dest_image, coord + ivec3(i, j, 0), p.gbar); |
| 1241 | } | 1157 | } |
| 1242 | } | 1158 | } |
| 1243 | } | 1159 | } |
| 1244 | 1160 | ||
| 1161 | uint SwizzleOffset(uvec2 pos) { | ||
| 1162 | const uint x = pos.x; | ||
| 1163 | const uint y = pos.y; | ||
| 1164 | return ((x % 64) / 32) * 256 + ((y % 8) / 2) * 64 + | ||
| 1165 | ((x % 32) / 16) * 32 + (y % 2) * 16 + (x % 16); | ||
| 1166 | } | ||
| 1167 | |||
| 1245 | void main() { | 1168 | void main() { |
| 1246 | uvec3 pos = gl_GlobalInvocationID; | 1169 | uvec3 pos = gl_GlobalInvocationID; |
| 1247 | pos.x <<= BYTES_PER_BLOCK_LOG2; | 1170 | pos.x <<= BYTES_PER_BLOCK_LOG2; |
| 1248 | |||
| 1249 | // Read as soon as possible due to its latency | ||
| 1250 | const uint swizzle = SwizzleOffset(pos.xy); | 1171 | const uint swizzle = SwizzleOffset(pos.xy); |
| 1251 | |||
| 1252 | const uint block_y = pos.y >> GOB_SIZE_Y_SHIFT; | 1172 | const uint block_y = pos.y >> GOB_SIZE_Y_SHIFT; |
| 1253 | 1173 | ||
| 1254 | uint offset = 0; | 1174 | uint offset = 0; |
| @@ -1262,8 +1182,6 @@ void main() { | |||
| 1262 | if (any(greaterThanEqual(coord, imageSize(dest_image)))) { | 1182 | if (any(greaterThanEqual(coord, imageSize(dest_image)))) { |
| 1263 | return; | 1183 | return; |
| 1264 | } | 1184 | } |
| 1265 | current_index = 0; | ||
| 1266 | bitsread = 0; | ||
| 1267 | local_buff = astc_data[offset / 16]; | 1185 | local_buff = astc_data[offset / 16]; |
| 1268 | DecompressBlock(coord); | 1186 | DecompressBlock(coord); |
| 1269 | } | 1187 | } |
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 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #version 460 core | ||
| 5 | |||
| 6 | layout (push_constant) uniform PushConstants { | ||
| 7 | vec4 clear_depth; | ||
| 8 | }; | ||
| 9 | |||
| 10 | void main() { | ||
| 11 | gl_FragDepth = clear_depth.x; | ||
| 12 | } | ||
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, | |||
| 1335 | } | 1335 | } |
| 1336 | const u32 buffer_size = static_cast<u32>(buffer_operand.pitch * buffer_operand.height); | 1336 | const u32 buffer_size = static_cast<u32>(buffer_operand.pitch * buffer_operand.height); |
| 1337 | static constexpr auto sync_info = VideoCommon::ObtainBufferSynchronize::FullSynchronize; | 1337 | static constexpr auto sync_info = VideoCommon::ObtainBufferSynchronize::FullSynchronize; |
| 1338 | const auto post_op = VideoCommon::ObtainBufferOperation::DoNothing; | 1338 | const auto post_op = IS_IMAGE_UPLOAD ? VideoCommon::ObtainBufferOperation::DoNothing |
| 1339 | : VideoCommon::ObtainBufferOperation::MarkAsWritten; | ||
| 1339 | const auto [buffer, offset] = | 1340 | const auto [buffer, offset] = |
| 1340 | buffer_cache.ObtainBuffer(buffer_operand.address, buffer_size, sync_info, post_op); | 1341 | buffer_cache.ObtainBuffer(buffer_operand.address, buffer_size, sync_info, post_op); |
| 1341 | 1342 | ||
| @@ -1344,8 +1345,12 @@ bool AccelerateDMA::DmaBufferImageCopy(const Tegra::DMA::ImageCopy& copy_info, | |||
| 1344 | const std::span copy_span{©, 1}; | 1345 | const std::span copy_span{©, 1}; |
| 1345 | 1346 | ||
| 1346 | if constexpr (IS_IMAGE_UPLOAD) { | 1347 | if constexpr (IS_IMAGE_UPLOAD) { |
| 1348 | texture_cache.PrepareImage(image_id, true, false); | ||
| 1347 | image->UploadMemory(buffer->Handle(), offset, copy_span); | 1349 | image->UploadMemory(buffer->Handle(), offset, copy_span); |
| 1348 | } else { | 1350 | } else { |
| 1351 | if (offset % BytesPerBlock(image->info.format)) { | ||
| 1352 | return false; | ||
| 1353 | } | ||
| 1349 | texture_cache.DownloadImageIntoBuffer(image, buffer->Handle(), offset, copy_span, | 1354 | texture_cache.DownloadImageIntoBuffer(image, buffer->Handle(), offset, copy_span, |
| 1350 | buffer_operand.address, buffer_size); | 1355 | buffer_operand.address, buffer_size); |
| 1351 | } | 1356 | } |
diff --git a/src/video_core/renderer_opengl/util_shaders.cpp b/src/video_core/renderer_opengl/util_shaders.cpp index 544982d18..c437013e6 100644 --- a/src/video_core/renderer_opengl/util_shaders.cpp +++ b/src/video_core/renderer_opengl/util_shaders.cpp | |||
| @@ -68,6 +68,7 @@ void UtilShaders::ASTCDecode(Image& image, const StagingBufferMap& map, | |||
| 68 | std::span<const VideoCommon::SwizzleParameters> swizzles) { | 68 | std::span<const VideoCommon::SwizzleParameters> swizzles) { |
| 69 | static constexpr GLuint BINDING_INPUT_BUFFER = 0; | 69 | static constexpr GLuint BINDING_INPUT_BUFFER = 0; |
| 70 | static constexpr GLuint BINDING_OUTPUT_IMAGE = 0; | 70 | static constexpr GLuint BINDING_OUTPUT_IMAGE = 0; |
| 71 | program_manager.LocalMemoryWarmup(); | ||
| 71 | 72 | ||
| 72 | const Extent2D tile_size{ | 73 | const Extent2D tile_size{ |
| 73 | .width = VideoCore::Surface::DefaultBlockWidth(image.info.format), | 74 | .width = VideoCore::Surface::DefaultBlockWidth(image.info.format), |
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 @@ | |||
| 16 | #include "video_core/host_shaders/vulkan_blit_depth_stencil_frag_spv.h" | 16 | #include "video_core/host_shaders/vulkan_blit_depth_stencil_frag_spv.h" |
| 17 | #include "video_core/host_shaders/vulkan_color_clear_frag_spv.h" | 17 | #include "video_core/host_shaders/vulkan_color_clear_frag_spv.h" |
| 18 | #include "video_core/host_shaders/vulkan_color_clear_vert_spv.h" | 18 | #include "video_core/host_shaders/vulkan_color_clear_vert_spv.h" |
| 19 | #include "video_core/host_shaders/vulkan_depthstencil_clear_frag_spv.h" | ||
| 19 | #include "video_core/renderer_vulkan/blit_image.h" | 20 | #include "video_core/renderer_vulkan/blit_image.h" |
| 20 | #include "video_core/renderer_vulkan/maxwell_to_vk.h" | 21 | #include "video_core/renderer_vulkan/maxwell_to_vk.h" |
| 21 | #include "video_core/renderer_vulkan/vk_scheduler.h" | 22 | #include "video_core/renderer_vulkan/vk_scheduler.h" |
| @@ -428,6 +429,7 @@ BlitImageHelper::BlitImageHelper(const Device& device_, Scheduler& scheduler_, | |||
| 428 | blit_depth_stencil_frag(BuildShader(device, VULKAN_BLIT_DEPTH_STENCIL_FRAG_SPV)), | 429 | blit_depth_stencil_frag(BuildShader(device, VULKAN_BLIT_DEPTH_STENCIL_FRAG_SPV)), |
| 429 | clear_color_vert(BuildShader(device, VULKAN_COLOR_CLEAR_VERT_SPV)), | 430 | clear_color_vert(BuildShader(device, VULKAN_COLOR_CLEAR_VERT_SPV)), |
| 430 | clear_color_frag(BuildShader(device, VULKAN_COLOR_CLEAR_FRAG_SPV)), | 431 | clear_color_frag(BuildShader(device, VULKAN_COLOR_CLEAR_FRAG_SPV)), |
| 432 | clear_stencil_frag(BuildShader(device, VULKAN_DEPTHSTENCIL_CLEAR_FRAG_SPV)), | ||
| 431 | convert_depth_to_float_frag(BuildShader(device, CONVERT_DEPTH_TO_FLOAT_FRAG_SPV)), | 433 | convert_depth_to_float_frag(BuildShader(device, CONVERT_DEPTH_TO_FLOAT_FRAG_SPV)), |
| 432 | convert_float_to_depth_frag(BuildShader(device, CONVERT_FLOAT_TO_DEPTH_FRAG_SPV)), | 434 | convert_float_to_depth_frag(BuildShader(device, CONVERT_FLOAT_TO_DEPTH_FRAG_SPV)), |
| 433 | convert_abgr8_to_d24s8_frag(BuildShader(device, CONVERT_ABGR8_TO_D24S8_FRAG_SPV)), | 435 | 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 | |||
| 593 | scheduler.InvalidateState(); | 595 | scheduler.InvalidateState(); |
| 594 | } | 596 | } |
| 595 | 597 | ||
| 598 | void BlitImageHelper::ClearDepthStencil(const Framebuffer* dst_framebuffer, bool depth_clear, | ||
| 599 | f32 clear_depth, u8 stencil_mask, u32 stencil_ref, | ||
| 600 | u32 stencil_compare_mask, const Region2D& dst_region) { | ||
| 601 | const BlitDepthStencilPipelineKey key{ | ||
| 602 | .renderpass = dst_framebuffer->RenderPass(), | ||
| 603 | .depth_clear = depth_clear, | ||
| 604 | .stencil_mask = stencil_mask, | ||
| 605 | .stencil_compare_mask = stencil_compare_mask, | ||
| 606 | .stencil_ref = stencil_ref, | ||
| 607 | }; | ||
| 608 | const VkPipeline pipeline = FindOrEmplaceClearStencilPipeline(key); | ||
| 609 | const VkPipelineLayout layout = *clear_color_pipeline_layout; | ||
| 610 | scheduler.RequestRenderpass(dst_framebuffer); | ||
| 611 | scheduler.Record([pipeline, layout, clear_depth, dst_region](vk::CommandBuffer cmdbuf) { | ||
| 612 | cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); | ||
| 613 | BindBlitState(cmdbuf, dst_region); | ||
| 614 | cmdbuf.PushConstants(layout, VK_SHADER_STAGE_FRAGMENT_BIT, clear_depth); | ||
| 615 | cmdbuf.Draw(3, 1, 0, 0); | ||
| 616 | }); | ||
| 617 | scheduler.InvalidateState(); | ||
| 618 | } | ||
| 619 | |||
| 596 | void BlitImageHelper::Convert(VkPipeline pipeline, const Framebuffer* dst_framebuffer, | 620 | void BlitImageHelper::Convert(VkPipeline pipeline, const Framebuffer* dst_framebuffer, |
| 597 | const ImageView& src_image_view) { | 621 | const ImageView& src_image_view) { |
| 598 | const VkPipelineLayout layout = *one_texture_pipeline_layout; | 622 | const VkPipelineLayout layout = *one_texture_pipeline_layout; |
| @@ -820,6 +844,61 @@ VkPipeline BlitImageHelper::FindOrEmplaceClearColorPipeline(const BlitImagePipel | |||
| 820 | return *clear_color_pipelines.back(); | 844 | return *clear_color_pipelines.back(); |
| 821 | } | 845 | } |
| 822 | 846 | ||
| 847 | VkPipeline BlitImageHelper::FindOrEmplaceClearStencilPipeline( | ||
| 848 | const BlitDepthStencilPipelineKey& key) { | ||
| 849 | const auto it = std::ranges::find(clear_stencil_keys, key); | ||
| 850 | if (it != clear_stencil_keys.end()) { | ||
| 851 | return *clear_stencil_pipelines[std::distance(clear_stencil_keys.begin(), it)]; | ||
| 852 | } | ||
| 853 | clear_stencil_keys.push_back(key); | ||
| 854 | const std::array stages = MakeStages(*clear_color_vert, *clear_stencil_frag); | ||
| 855 | const auto stencil = VkStencilOpState{ | ||
| 856 | .failOp = VK_STENCIL_OP_KEEP, | ||
| 857 | .passOp = VK_STENCIL_OP_REPLACE, | ||
| 858 | .depthFailOp = VK_STENCIL_OP_KEEP, | ||
| 859 | .compareOp = VK_COMPARE_OP_ALWAYS, | ||
| 860 | .compareMask = key.stencil_compare_mask, | ||
| 861 | .writeMask = key.stencil_mask, | ||
| 862 | .reference = key.stencil_ref, | ||
| 863 | }; | ||
| 864 | const VkPipelineDepthStencilStateCreateInfo depth_stencil_ci{ | ||
| 865 | .sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO, | ||
| 866 | .pNext = nullptr, | ||
| 867 | .flags = 0, | ||
| 868 | .depthTestEnable = VK_FALSE, | ||
| 869 | .depthWriteEnable = key.depth_clear, | ||
| 870 | .depthCompareOp = VK_COMPARE_OP_ALWAYS, | ||
| 871 | .depthBoundsTestEnable = VK_FALSE, | ||
| 872 | .stencilTestEnable = VK_TRUE, | ||
| 873 | .front = stencil, | ||
| 874 | .back = stencil, | ||
| 875 | .minDepthBounds = 0.0f, | ||
| 876 | .maxDepthBounds = 0.0f, | ||
| 877 | }; | ||
| 878 | clear_stencil_pipelines.push_back(device.GetLogical().CreateGraphicsPipeline({ | ||
| 879 | .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, | ||
| 880 | .pNext = nullptr, | ||
| 881 | .flags = 0, | ||
| 882 | .stageCount = static_cast<u32>(stages.size()), | ||
| 883 | .pStages = stages.data(), | ||
| 884 | .pVertexInputState = &PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, | ||
| 885 | .pInputAssemblyState = &PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, | ||
| 886 | .pTessellationState = nullptr, | ||
| 887 | .pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO, | ||
| 888 | .pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO, | ||
| 889 | .pMultisampleState = &PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, | ||
| 890 | .pDepthStencilState = &depth_stencil_ci, | ||
| 891 | .pColorBlendState = &PIPELINE_COLOR_BLEND_STATE_GENERIC_CREATE_INFO, | ||
| 892 | .pDynamicState = &PIPELINE_DYNAMIC_STATE_CREATE_INFO, | ||
| 893 | .layout = *clear_color_pipeline_layout, | ||
| 894 | .renderPass = key.renderpass, | ||
| 895 | .subpass = 0, | ||
| 896 | .basePipelineHandle = VK_NULL_HANDLE, | ||
| 897 | .basePipelineIndex = 0, | ||
| 898 | })); | ||
| 899 | return *clear_stencil_pipelines.back(); | ||
| 900 | } | ||
| 901 | |||
| 823 | void BlitImageHelper::ConvertPipeline(vk::Pipeline& pipeline, VkRenderPass renderpass, | 902 | void BlitImageHelper::ConvertPipeline(vk::Pipeline& pipeline, VkRenderPass renderpass, |
| 824 | bool is_target_depth) { | 903 | bool is_target_depth) { |
| 825 | if (pipeline) { | 904 | 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 { | |||
| 27 | Tegra::Engines::Fermi2D::Operation operation; | 27 | Tegra::Engines::Fermi2D::Operation operation; |
| 28 | }; | 28 | }; |
| 29 | 29 | ||
| 30 | struct BlitDepthStencilPipelineKey { | ||
| 31 | constexpr auto operator<=>(const BlitDepthStencilPipelineKey&) const noexcept = default; | ||
| 32 | |||
| 33 | VkRenderPass renderpass; | ||
| 34 | bool depth_clear; | ||
| 35 | u8 stencil_mask; | ||
| 36 | u32 stencil_compare_mask; | ||
| 37 | u32 stencil_ref; | ||
| 38 | }; | ||
| 39 | |||
| 30 | class BlitImageHelper { | 40 | class BlitImageHelper { |
| 31 | public: | 41 | public: |
| 32 | explicit BlitImageHelper(const Device& device, Scheduler& scheduler, | 42 | explicit BlitImageHelper(const Device& device, Scheduler& scheduler, |
| @@ -64,6 +74,10 @@ public: | |||
| 64 | void ClearColor(const Framebuffer* dst_framebuffer, u8 color_mask, | 74 | void ClearColor(const Framebuffer* dst_framebuffer, u8 color_mask, |
| 65 | const std::array<f32, 4>& clear_color, const Region2D& dst_region); | 75 | const std::array<f32, 4>& clear_color, const Region2D& dst_region); |
| 66 | 76 | ||
| 77 | void ClearDepthStencil(const Framebuffer* dst_framebuffer, bool depth_clear, f32 clear_depth, | ||
| 78 | u8 stencil_mask, u32 stencil_ref, u32 stencil_compare_mask, | ||
| 79 | const Region2D& dst_region); | ||
| 80 | |||
| 67 | private: | 81 | private: |
| 68 | void Convert(VkPipeline pipeline, const Framebuffer* dst_framebuffer, | 82 | void Convert(VkPipeline pipeline, const Framebuffer* dst_framebuffer, |
| 69 | const ImageView& src_image_view); | 83 | const ImageView& src_image_view); |
| @@ -76,6 +90,8 @@ private: | |||
| 76 | [[nodiscard]] VkPipeline FindOrEmplaceDepthStencilPipeline(const BlitImagePipelineKey& key); | 90 | [[nodiscard]] VkPipeline FindOrEmplaceDepthStencilPipeline(const BlitImagePipelineKey& key); |
| 77 | 91 | ||
| 78 | [[nodiscard]] VkPipeline FindOrEmplaceClearColorPipeline(const BlitImagePipelineKey& key); | 92 | [[nodiscard]] VkPipeline FindOrEmplaceClearColorPipeline(const BlitImagePipelineKey& key); |
| 93 | [[nodiscard]] VkPipeline FindOrEmplaceClearStencilPipeline( | ||
| 94 | const BlitDepthStencilPipelineKey& key); | ||
| 79 | 95 | ||
| 80 | void ConvertPipeline(vk::Pipeline& pipeline, VkRenderPass renderpass, bool is_target_depth); | 96 | void ConvertPipeline(vk::Pipeline& pipeline, VkRenderPass renderpass, bool is_target_depth); |
| 81 | 97 | ||
| @@ -108,6 +124,7 @@ private: | |||
| 108 | vk::ShaderModule blit_depth_stencil_frag; | 124 | vk::ShaderModule blit_depth_stencil_frag; |
| 109 | vk::ShaderModule clear_color_vert; | 125 | vk::ShaderModule clear_color_vert; |
| 110 | vk::ShaderModule clear_color_frag; | 126 | vk::ShaderModule clear_color_frag; |
| 127 | vk::ShaderModule clear_stencil_frag; | ||
| 111 | vk::ShaderModule convert_depth_to_float_frag; | 128 | vk::ShaderModule convert_depth_to_float_frag; |
| 112 | vk::ShaderModule convert_float_to_depth_frag; | 129 | vk::ShaderModule convert_float_to_depth_frag; |
| 113 | vk::ShaderModule convert_abgr8_to_d24s8_frag; | 130 | vk::ShaderModule convert_abgr8_to_d24s8_frag; |
| @@ -122,6 +139,8 @@ private: | |||
| 122 | std::vector<vk::Pipeline> blit_depth_stencil_pipelines; | 139 | std::vector<vk::Pipeline> blit_depth_stencil_pipelines; |
| 123 | std::vector<BlitImagePipelineKey> clear_color_keys; | 140 | std::vector<BlitImagePipelineKey> clear_color_keys; |
| 124 | std::vector<vk::Pipeline> clear_color_pipelines; | 141 | std::vector<vk::Pipeline> clear_color_pipelines; |
| 142 | std::vector<BlitDepthStencilPipelineKey> clear_stencil_keys; | ||
| 143 | std::vector<vk::Pipeline> clear_stencil_pipelines; | ||
| 125 | vk::Pipeline convert_d32_to_r32_pipeline; | 144 | vk::Pipeline convert_d32_to_r32_pipeline; |
| 126 | vk::Pipeline convert_r32_to_d32_pipeline; | 145 | vk::Pipeline convert_r32_to_d32_pipeline; |
| 127 | vk::Pipeline convert_d16_to_r16_pipeline; | 146 | 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 { | |||
| 126 | {VK_FORMAT_A1R5G5B5_UNORM_PACK16, Attachable}, // A1R5G5B5_UNORM | 126 | {VK_FORMAT_A1R5G5B5_UNORM_PACK16, Attachable}, // A1R5G5B5_UNORM |
| 127 | {VK_FORMAT_A2B10G10R10_UNORM_PACK32, Attachable | Storage}, // A2B10G10R10_UNORM | 127 | {VK_FORMAT_A2B10G10R10_UNORM_PACK32, Attachable | Storage}, // A2B10G10R10_UNORM |
| 128 | {VK_FORMAT_A2B10G10R10_UINT_PACK32, Attachable | Storage}, // A2B10G10R10_UINT | 128 | {VK_FORMAT_A2B10G10R10_UINT_PACK32, Attachable | Storage}, // A2B10G10R10_UINT |
| 129 | {VK_FORMAT_A2R10G10B10_UNORM_PACK32, Attachable | Storage}, // A2R10G10B10_UNORM | 129 | {VK_FORMAT_A2R10G10B10_UNORM_PACK32, Attachable}, // A2R10G10B10_UNORM |
| 130 | {VK_FORMAT_A1R5G5B5_UNORM_PACK16, Attachable}, // A1B5G5R5_UNORM (flipped with swizzle) | 130 | {VK_FORMAT_A1R5G5B5_UNORM_PACK16, Attachable}, // A1B5G5R5_UNORM (flipped with swizzle) |
| 131 | {VK_FORMAT_R5G5B5A1_UNORM_PACK16}, // A5B5G5R1_UNORM (specially swizzled) | 131 | {VK_FORMAT_R5G5B5A1_UNORM_PACK16}, // A5B5G5R1_UNORM (specially swizzled) |
| 132 | {VK_FORMAT_R8_UNORM, Attachable | Storage}, // R8_UNORM | 132 | {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) { | |||
| 428 | if (aspect_flags == 0) { | 428 | if (aspect_flags == 0) { |
| 429 | return; | 429 | return; |
| 430 | } | 430 | } |
| 431 | scheduler.Record([clear_depth = regs.clear_depth, clear_stencil = regs.clear_stencil, | 431 | |
| 432 | clear_rect, aspect_flags](vk::CommandBuffer cmdbuf) { | 432 | if (use_stencil && regs.stencil_front_mask != 0xFF && regs.stencil_front_mask != 0) { |
| 433 | VkClearAttachment attachment; | 433 | Region2D dst_region = { |
| 434 | attachment.aspectMask = aspect_flags; | 434 | Offset2D{.x = clear_rect.rect.offset.x, .y = clear_rect.rect.offset.y}, |
| 435 | attachment.colorAttachment = 0; | 435 | Offset2D{.x = clear_rect.rect.offset.x + static_cast<s32>(clear_rect.rect.extent.width), |
| 436 | attachment.clearValue.depthStencil.depth = clear_depth; | 436 | .y = clear_rect.rect.offset.y + |
| 437 | attachment.clearValue.depthStencil.stencil = clear_stencil; | 437 | static_cast<s32>(clear_rect.rect.extent.height)}}; |
| 438 | cmdbuf.ClearAttachments(attachment, clear_rect); | 438 | blit_image.ClearDepthStencil(framebuffer, use_depth, regs.clear_depth, |
| 439 | }); | 439 | static_cast<u8>(regs.stencil_front_mask), regs.clear_stencil, |
| 440 | regs.stencil_front_func_mask, dst_region); | ||
| 441 | } else { | ||
| 442 | scheduler.Record([clear_depth = regs.clear_depth, clear_stencil = regs.clear_stencil, | ||
| 443 | clear_rect, aspect_flags](vk::CommandBuffer cmdbuf) { | ||
| 444 | VkClearAttachment attachment; | ||
| 445 | attachment.aspectMask = aspect_flags; | ||
| 446 | attachment.colorAttachment = 0; | ||
| 447 | attachment.clearValue.depthStencil.depth = clear_depth; | ||
| 448 | attachment.clearValue.depthStencil.stencil = clear_stencil; | ||
| 449 | cmdbuf.ClearAttachments(attachment, clear_rect); | ||
| 450 | }); | ||
| 451 | } | ||
| 440 | } | 452 | } |
| 441 | 453 | ||
| 442 | void RasterizerVulkan::DispatchCompute() { | 454 | void RasterizerVulkan::DispatchCompute() { |
| @@ -830,7 +842,8 @@ bool AccelerateDMA::DmaBufferImageCopy(const Tegra::DMA::ImageCopy& copy_info, | |||
| 830 | } | 842 | } |
| 831 | const u32 buffer_size = static_cast<u32>(buffer_operand.pitch * buffer_operand.height); | 843 | const u32 buffer_size = static_cast<u32>(buffer_operand.pitch * buffer_operand.height); |
| 832 | static constexpr auto sync_info = VideoCommon::ObtainBufferSynchronize::FullSynchronize; | 844 | static constexpr auto sync_info = VideoCommon::ObtainBufferSynchronize::FullSynchronize; |
| 833 | const auto post_op = VideoCommon::ObtainBufferOperation::DoNothing; | 845 | const auto post_op = IS_IMAGE_UPLOAD ? VideoCommon::ObtainBufferOperation::DoNothing |
| 846 | : VideoCommon::ObtainBufferOperation::MarkAsWritten; | ||
| 834 | const auto [buffer, offset] = | 847 | const auto [buffer, offset] = |
| 835 | buffer_cache.ObtainBuffer(buffer_operand.address, buffer_size, sync_info, post_op); | 848 | buffer_cache.ObtainBuffer(buffer_operand.address, buffer_size, sync_info, post_op); |
| 836 | 849 | ||
| @@ -839,8 +852,12 @@ bool AccelerateDMA::DmaBufferImageCopy(const Tegra::DMA::ImageCopy& copy_info, | |||
| 839 | const std::span copy_span{©, 1}; | 852 | const std::span copy_span{©, 1}; |
| 840 | 853 | ||
| 841 | if constexpr (IS_IMAGE_UPLOAD) { | 854 | if constexpr (IS_IMAGE_UPLOAD) { |
| 855 | texture_cache.PrepareImage(image_id, true, false); | ||
| 842 | image->UploadMemory(buffer->Handle(), offset, copy_span); | 856 | image->UploadMemory(buffer->Handle(), offset, copy_span); |
| 843 | } else { | 857 | } else { |
| 858 | if (offset % BytesPerBlock(image->info.format)) { | ||
| 859 | return false; | ||
| 860 | } | ||
| 844 | texture_cache.DownloadImageIntoBuffer(image, buffer->Handle(), offset, copy_span, | 861 | texture_cache.DownloadImageIntoBuffer(image, buffer->Handle(), offset, copy_span, |
| 845 | buffer_operand.address, buffer_size); | 862 | buffer_operand.address, buffer_size); |
| 846 | } | 863 | } |
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: | |||
| 243 | /// Create channel state. | 243 | /// Create channel state. |
| 244 | void CreateChannel(Tegra::Control::ChannelState& channel) final override; | 244 | void CreateChannel(Tegra::Control::ChannelState& channel) final override; |
| 245 | 245 | ||
| 246 | /// Prepare an image to be used | ||
| 247 | void PrepareImage(ImageId image_id, bool is_modification, bool invalidate); | ||
| 248 | |||
| 246 | std::recursive_mutex mutex; | 249 | std::recursive_mutex mutex; |
| 247 | 250 | ||
| 248 | private: | 251 | private: |
| @@ -387,9 +390,6 @@ private: | |||
| 387 | /// Synchronize image aliases, copying data if needed | 390 | /// Synchronize image aliases, copying data if needed |
| 388 | void SynchronizeAliases(ImageId image_id); | 391 | void SynchronizeAliases(ImageId image_id); |
| 389 | 392 | ||
| 390 | /// Prepare an image to be used | ||
| 391 | void PrepareImage(ImageId image_id, bool is_modification, bool invalidate); | ||
| 392 | |||
| 393 | /// Prepare an image view to be used | 393 | /// Prepare an image view to be used |
| 394 | void PrepareImageView(ImageViewId image_view_id, bool is_modification, bool invalidate); | 394 | void PrepareImageView(ImageViewId image_view_id, bool is_modification, bool invalidate); |
| 395 | 395 | ||
diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index adde96aa5..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{ | |||
| 71 | VK_FORMAT_UNDEFINED, | 71 | VK_FORMAT_UNDEFINED, |
| 72 | }; | 72 | }; |
| 73 | 73 | ||
| 74 | constexpr std::array VK_FORMAT_R32G32B32_SFLOAT{ | ||
| 75 | VK_FORMAT_R32G32B32A32_SFLOAT, | ||
| 76 | VK_FORMAT_UNDEFINED, | ||
| 77 | }; | ||
| 78 | |||
| 74 | } // namespace Alternatives | 79 | } // namespace Alternatives |
| 75 | 80 | ||
| 76 | enum class NvidiaArchitecture { | 81 | enum class NvidiaArchitecture { |
| @@ -103,6 +108,8 @@ constexpr const VkFormat* GetFormatAlternatives(VkFormat format) { | |||
| 103 | return Alternatives::R16G16B16_SSCALED.data(); | 108 | return Alternatives::R16G16B16_SSCALED.data(); |
| 104 | case VK_FORMAT_R8G8B8_SSCALED: | 109 | case VK_FORMAT_R8G8B8_SSCALED: |
| 105 | return Alternatives::R8G8B8_SSCALED.data(); | 110 | return Alternatives::R8G8B8_SSCALED.data(); |
| 111 | case VK_FORMAT_R32G32B32_SFLOAT: | ||
| 112 | return Alternatives::VK_FORMAT_R32G32B32_SFLOAT.data(); | ||
| 106 | default: | 113 | default: |
| 107 | return nullptr; | 114 | return nullptr; |
| 108 | } | 115 | } |
| @@ -130,6 +137,7 @@ std::unordered_map<VkFormat, VkFormatProperties> GetFormatProperties(vk::Physica | |||
| 130 | VK_FORMAT_A2B10G10R10_UINT_PACK32, | 137 | VK_FORMAT_A2B10G10R10_UINT_PACK32, |
| 131 | VK_FORMAT_A2B10G10R10_UNORM_PACK32, | 138 | VK_FORMAT_A2B10G10R10_UNORM_PACK32, |
| 132 | VK_FORMAT_A2B10G10R10_USCALED_PACK32, | 139 | VK_FORMAT_A2B10G10R10_USCALED_PACK32, |
| 140 | VK_FORMAT_A2R10G10B10_UNORM_PACK32, | ||
| 133 | VK_FORMAT_A8B8G8R8_SINT_PACK32, | 141 | VK_FORMAT_A8B8G8R8_SINT_PACK32, |
| 134 | VK_FORMAT_A8B8G8R8_SNORM_PACK32, | 142 | VK_FORMAT_A8B8G8R8_SNORM_PACK32, |
| 135 | VK_FORMAT_A8B8G8R8_SRGB_PACK32, | 143 | VK_FORMAT_A8B8G8R8_SRGB_PACK32, |
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 | |||
| 2535 | return; | 2535 | return; |
| 2536 | } | 2536 | } |
| 2537 | 2537 | ||
| 2538 | FileSys::VirtualFile file; | 2538 | FileSys::VirtualFile base_romfs; |
| 2539 | if (loader->ReadRomFS(file) != Loader::ResultStatus::Success) { | 2539 | if (loader->ReadRomFS(base_romfs) != Loader::ResultStatus::Success) { |
| 2540 | failed(); | 2540 | failed(); |
| 2541 | return; | 2541 | return; |
| 2542 | } | 2542 | } |
| @@ -2549,6 +2549,14 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa | |||
| 2549 | return; | 2549 | return; |
| 2550 | } | 2550 | } |
| 2551 | 2551 | ||
| 2552 | const auto type = *romfs_title_id == program_id ? FileSys::ContentRecordType::Program | ||
| 2553 | : FileSys::ContentRecordType::Data; | ||
| 2554 | const auto base_nca = installed.GetEntry(*romfs_title_id, type); | ||
| 2555 | if (!base_nca) { | ||
| 2556 | failed(); | ||
| 2557 | return; | ||
| 2558 | } | ||
| 2559 | |||
| 2552 | const auto dump_dir = | 2560 | const auto dump_dir = |
| 2553 | target == DumpRomFSTarget::Normal | 2561 | target == DumpRomFSTarget::Normal |
| 2554 | ? Common::FS::GetYuzuPath(Common::FS::YuzuPath::DumpDir) | 2562 | ? Common::FS::GetYuzuPath(Common::FS::YuzuPath::DumpDir) |
| @@ -2560,12 +2568,10 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa | |||
| 2560 | FileSys::VirtualFile romfs; | 2568 | FileSys::VirtualFile romfs; |
| 2561 | 2569 | ||
| 2562 | if (*romfs_title_id == program_id) { | 2570 | if (*romfs_title_id == program_id) { |
| 2563 | const u64 ivfc_offset = loader->ReadRomFSIVFCOffset(); | ||
| 2564 | const FileSys::PatchManager pm{program_id, system->GetFileSystemController(), installed}; | 2571 | const FileSys::PatchManager pm{program_id, system->GetFileSystemController(), installed}; |
| 2565 | romfs = | 2572 | romfs = pm.PatchRomFS(base_nca.get(), base_romfs, type, nullptr, false); |
| 2566 | pm.PatchRomFS(file, ivfc_offset, FileSys::ContentRecordType::Program, nullptr, false); | ||
| 2567 | } else { | 2573 | } else { |
| 2568 | romfs = installed.GetEntry(*romfs_title_id, FileSys::ContentRecordType::Data)->GetRomFS(); | 2574 | romfs = installed.GetEntry(*romfs_title_id, type)->GetRomFS(); |
| 2569 | } | 2575 | } |
| 2570 | 2576 | ||
| 2571 | const auto extracted = FileSys::ExtractRomFS(romfs, FileSys::RomFSExtractionType::Full); | 2577 | const auto extracted = FileSys::ExtractRomFS(romfs, FileSys::RomFSExtractionType::Full); |