summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/android/app/build.gradle.kts5
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt24
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt4
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt12
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeSetting.kt6
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt19
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt1
-rw-r--r--src/android/app/src/main/res/layout/card_home_option.xml17
-rw-r--r--src/common/alignment.h37
-rw-r--r--src/common/lz4_compression.cpp6
-rw-r--r--src/common/lz4_compression.h2
-rw-r--r--src/core/CMakeLists.txt45
-rw-r--r--src/core/file_sys/card_image.cpp54
-rw-r--r--src/core/file_sys/card_image.h1
-rw-r--r--src/core/file_sys/content_archive.cpp585
-rw-r--r--src/core/file_sys/content_archive.h66
-rw-r--r--src/core/file_sys/errors.h70
-rw-r--r--src/core/file_sys/fssystem/fs_i_storage.h58
-rw-r--r--src/core/file_sys/fssystem/fs_types.h46
-rw-r--r--src/core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.cpp251
-rw-r--r--src/core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.h114
-rw-r--r--src/core/file_sys/fssystem/fssystem_aes_ctr_storage.cpp129
-rw-r--r--src/core/file_sys/fssystem/fssystem_aes_ctr_storage.h43
-rw-r--r--src/core/file_sys/fssystem/fssystem_aes_xts_storage.cpp112
-rw-r--r--src/core/file_sys/fssystem/fssystem_aes_xts_storage.h42
-rw-r--r--src/core/file_sys/fssystem/fssystem_alignment_matching_storage.h146
-rw-r--r--src/core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.cpp204
-rw-r--r--src/core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.h21
-rw-r--r--src/core/file_sys/fssystem/fssystem_bucket_tree.cpp598
-rw-r--r--src/core/file_sys/fssystem/fssystem_bucket_tree.h416
-rw-r--r--src/core/file_sys/fssystem/fssystem_bucket_tree_template_impl.h170
-rw-r--r--src/core/file_sys/fssystem/fssystem_bucket_tree_utils.h110
-rw-r--r--src/core/file_sys/fssystem/fssystem_compressed_storage.h963
-rw-r--r--src/core/file_sys/fssystem/fssystem_compression_common.h43
-rw-r--r--src/core/file_sys/fssystem/fssystem_compression_configuration.cpp36
-rw-r--r--src/core/file_sys/fssystem/fssystem_compression_configuration.h12
-rw-r--r--src/core/file_sys/fssystem/fssystem_crypto_configuration.cpp65
-rw-r--r--src/core/file_sys/fssystem/fssystem_crypto_configuration.h12
-rw-r--r--src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.cpp127
-rw-r--r--src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h164
-rw-r--r--src/core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.cpp80
-rw-r--r--src/core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.h44
-rw-r--r--src/core/file_sys/fssystem/fssystem_indirect_storage.cpp119
-rw-r--r--src/core/file_sys/fssystem/fssystem_indirect_storage.h294
-rw-r--r--src/core/file_sys/fssystem/fssystem_integrity_romfs_storage.cpp30
-rw-r--r--src/core/file_sys/fssystem/fssystem_integrity_romfs_storage.h42
-rw-r--r--src/core/file_sys/fssystem/fssystem_integrity_verification_storage.cpp91
-rw-r--r--src/core/file_sys/fssystem/fssystem_integrity_verification_storage.h65
-rw-r--r--src/core/file_sys/fssystem/fssystem_memory_resource_buffer_hold_storage.h61
-rw-r--r--src/core/file_sys/fssystem/fssystem_nca_file_system_driver.cpp1351
-rw-r--r--src/core/file_sys/fssystem/fssystem_nca_file_system_driver.h364
-rw-r--r--src/core/file_sys/fssystem/fssystem_nca_header.cpp20
-rw-r--r--src/core/file_sys/fssystem/fssystem_nca_header.h338
-rw-r--r--src/core/file_sys/fssystem/fssystem_nca_reader.cpp531
-rw-r--r--src/core/file_sys/fssystem/fssystem_pooled_buffer.cpp61
-rw-r--r--src/core/file_sys/fssystem/fssystem_pooled_buffer.h95
-rw-r--r--src/core/file_sys/fssystem/fssystem_sparse_storage.cpp39
-rw-r--r--src/core/file_sys/fssystem/fssystem_sparse_storage.h72
-rw-r--r--src/core/file_sys/fssystem/fssystem_switch_storage.h80
-rw-r--r--src/core/file_sys/fssystem/fssystem_utility.cpp27
-rw-r--r--src/core/file_sys/fssystem/fssystem_utility.h12
-rw-r--r--src/core/file_sys/nca_patch.cpp217
-rw-r--r--src/core/file_sys/nca_patch.h145
-rw-r--r--src/core/file_sys/patch_manager.cpp48
-rw-r--r--src/core/file_sys/patch_manager.h4
-rw-r--r--src/core/file_sys/registered_cache.cpp8
-rw-r--r--src/core/file_sys/romfs_factory.cpp9
-rw-r--r--src/core/file_sys/romfs_factory.h11
-rw-r--r--src/core/file_sys/submission_package.cpp4
-rw-r--r--src/core/hle/service/am/applets/applet_web_browser.cpp2
-rw-r--r--src/core/hle/service/filesystem/filesystem.cpp5
-rw-r--r--src/core/hle/service/filesystem/filesystem.h3
-rw-r--r--src/core/hle/service/filesystem/fsp_srv.cpp3
-rw-r--r--src/core/loader/loader.cpp4
-rw-r--r--src/core/loader/loader.h12
-rw-r--r--src/core/loader/nax.cpp4
-rw-r--r--src/core/loader/nax.h1
-rw-r--r--src/core/loader/nca.cpp28
-rw-r--r--src/core/loader/nca.h1
-rw-r--r--src/core/loader/nsp.cpp4
-rw-r--r--src/core/loader/nsp.h1
-rw-r--r--src/core/loader/xci.cpp4
-rw-r--r--src/core/loader/xci.h1
-rw-r--r--src/video_core/host_shaders/CMakeLists.txt1
-rw-r--r--src/video_core/host_shaders/astc_decoder.comp988
-rw-r--r--src/video_core/host_shaders/vulkan_depthstencil_clear.frag12
-rw-r--r--src/video_core/renderer_opengl/util_shaders.cpp1
-rw-r--r--src/video_core/renderer_vulkan/blit_image.cpp79
-rw-r--r--src/video_core/renderer_vulkan/blit_image.h19
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.cpp30
-rw-r--r--src/yuzu/main.cpp18
91 files changed, 8735 insertions, 1584 deletions
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
163tasks.create<Delete>("ktlintReset") {
164 delete(File(buildDir.path + File.separator + "intermediates/ktLint"))
165}
166
167tasks.getByPath("loadKtlintReporters").dependsOn("ktlintReset")
163tasks.getByPath("preBuild").dependsOn("ktlintCheck") 168tasks.getByPath("preBuild").dependsOn("ktlintCheck")
164 169
165ktlint { 170ktlint {
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
4package org.yuzu.yuzu_emu.adapters 4package org.yuzu.yuzu_emu.adapters
5 5
6import android.text.TextUtils
6import android.view.LayoutInflater 7import android.view.LayoutInflater
7import android.view.View 8import android.view.View
8import android.view.ViewGroup 9import android.view.ViewGroup
9import androidx.appcompat.app.AppCompatActivity 10import androidx.appcompat.app.AppCompatActivity
10import androidx.core.content.ContextCompat 11import androidx.core.content.ContextCompat
11import androidx.core.content.res.ResourcesCompat 12import androidx.core.content.res.ResourcesCompat
13import androidx.lifecycle.LifecycleOwner
12import androidx.recyclerview.widget.RecyclerView 14import androidx.recyclerview.widget.RecyclerView
13import org.yuzu.yuzu_emu.R 15import org.yuzu.yuzu_emu.R
14import org.yuzu.yuzu_emu.databinding.CardHomeOptionBinding 16import org.yuzu.yuzu_emu.databinding.CardHomeOptionBinding
15import org.yuzu.yuzu_emu.fragments.MessageDialogFragment 17import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
16import org.yuzu.yuzu_emu.model.HomeSetting 18import org.yuzu.yuzu_emu.model.HomeSetting
17 19
18class HomeSettingAdapter(private val activity: AppCompatActivity, var options: List<HomeSetting>) : 20class 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/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/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
4package org.yuzu.yuzu_emu.model 4package org.yuzu.yuzu_emu.model
5 5
6import androidx.lifecycle.LiveData
7import androidx.lifecycle.MutableLiveData
8
6data class HomeSetting( 9data 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 e13d84c9c..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
4package org.yuzu.yuzu_emu.model 4package org.yuzu.yuzu_emu.model
5 5
6import android.net.Uri
7import androidx.fragment.app.FragmentActivity
6import androidx.lifecycle.LiveData 8import androidx.lifecycle.LiveData
7import androidx.lifecycle.MutableLiveData 9import androidx.lifecycle.MutableLiveData
8import androidx.lifecycle.ViewModel 10import androidx.lifecycle.ViewModel
11import androidx.lifecycle.ViewModelProvider
12import androidx.preference.PreferenceManager
13import org.yuzu.yuzu_emu.YuzuApplication
14import org.yuzu.yuzu_emu.utils.GameHelper
9 15
10class HomeViewModel : ViewModel() { 16class HomeViewModel : ViewModel() {
11 private val _navigationVisible = MutableLiveData<Pair<Boolean, Boolean>>() 17 private val _navigationVisible = MutableLiveData<Pair<Boolean, Boolean>>()
@@ -17,6 +23,14 @@ class HomeViewModel : ViewModel() {
17 private val _shouldPageForward = MutableLiveData(false) 23 private val _shouldPageForward = MutableLiveData(false)
18 val shouldPageForward: LiveData<Boolean> get() = _shouldPageForward 24 val shouldPageForward: LiveData<Boolean> get() = _shouldPageForward
19 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
20 var navigatedToSetup = false 34 var navigatedToSetup = false
21 35
22 init { 36 init {
@@ -40,4 +54,9 @@ class HomeViewModel : ViewModel() {
40 fun setShouldPageForward(pageForward: Boolean) { 54 fun setShouldPageForward(pageForward: Boolean) {
41 _shouldPageForward.value = pageForward 55 _shouldPageForward.value = pageForward
42 } 56 }
57
58 fun setGamesDir(activity: FragmentActivity, dir: String) {
59 ViewModelProvider(activity)[GamesViewModel::class.java].reloadGames(true)
60 _gamesDir.value = dir
61 }
43} 62}
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 f77d06262..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
@@ -290,6 +290,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
290 ).show() 290 ).show()
291 291
292 gamesViewModel.reloadGames(true) 292 gamesViewModel.reloadGames(true)
293 homeViewModel.setGamesDir(this, result.path!!)
293 } 294 }
294 295
295 val getProdKey = 296 val getProdKey =
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/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 @@
10namespace Common { 11namespace Common {
11 12
12template <typename T> 13template <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
26template <typename T> 29template <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
63template <typename T>
64 requires std::is_integral_v<T>
65[[nodiscard]] constexpr T LeastSignificantOneBit(T x) {
66 return x & ~(x - 1);
67}
68
69template <typename T>
70 requires std::is_integral_v<T>
71[[nodiscard]] constexpr T ResetLeastSignificantOneBit(T x) {
72 return x & (x - 1);
73}
74
75template <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
81template <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
58template <typename T, size_t Align = 16> 87template <typename T, size_t Align = 16>
59class AlignmentAllocator { 88class AlignmentAllocator {
60public: 89public:
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
74int 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/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
315Loader::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
319u8 XCI::GetFormatVersion() { 353u8 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
129private: 129private:
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
20namespace 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. 23namespace FileSys {
23constexpr u64 MEDIA_OFFSET_MULTIPLIER = 0x200;
24
25constexpr u64 SECTION_HEADER_SIZE = 0x200;
26constexpr u64 SECTION_HEADER_OFFSET = 0x400;
27
28constexpr u32 IVFC_MAX_LEVEL = 6;
29
30enum class NCASectionFilesystemType : u8 {
31 PFS0 = 0x2,
32 ROMFS = 0x3,
33};
34
35struct IVFCLevel {
36 u64_le offset;
37 u64_le size;
38 u32_le block_size;
39 u32_le reserved;
40};
41static_assert(sizeof(IVFCLevel) == 0x18, "IVFCLevel has incorrect size.");
42
43struct 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};
50static_assert(sizeof(IVFCHeader) == 0xE0, "IVFCHeader has incorrect size.");
51
52struct NCASectionHeaderBlock {
53 INSERT_PADDING_BYTES_NOINIT(3);
54 NCASectionFilesystemType filesystem_type;
55 NCASectionCryptoType crypto_type;
56 INSERT_PADDING_BYTES_NOINIT(3);
57};
58static_assert(sizeof(NCASectionHeaderBlock) == 0x8, "NCASectionHeaderBlock has incorrect size.");
59
60struct NCABucketInfo {
61 u64 table_offset;
62 u64 table_size;
63 std::array<u8, 0x10> table_header;
64};
65static_assert(sizeof(NCABucketInfo) == 0x20, "NCABucketInfo has incorrect size.");
66
67struct NCASparseInfo {
68 NCABucketInfo bucket;
69 u64 physical_offset;
70 u16 generation;
71 INSERT_PADDING_BYTES_NOINIT(0x6);
72};
73static_assert(sizeof(NCASparseInfo) == 0x30, "NCASparseInfo has incorrect size.");
74
75struct NCACompressionInfo {
76 NCABucketInfo bucket;
77 INSERT_PADDING_BYTES_NOINIT(0x8);
78};
79static_assert(sizeof(NCACompressionInfo) == 0x28, "NCACompressionInfo has incorrect size.");
80
81struct 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};
89static_assert(sizeof(NCASectionRaw) == 0x200, "NCASectionRaw has incorrect size.");
90
91struct 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};
102static_assert(sizeof(PFS0Superblock) == 0x200, "PFS0Superblock has incorrect size.");
103
104struct RomFSSuperblock {
105 NCASectionHeaderBlock header_block;
106 IVFCHeader ivfc;
107 INSERT_PADDING_BYTES_NOINIT(0x118);
108};
109static_assert(sizeof(RomFSSuperblock) == 0x200, "RomFSSuperblock has incorrect size.");
110
111struct 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};
119static_assert(sizeof(BKTRHeader) == 0x20, "BKTRHeader has incorrect size.");
120
121struct 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};
129static_assert(sizeof(BKTRSuperblock) == 0x200, "BKTRSuperblock has incorrect size.");
130
131union NCASectionHeader {
132 NCASectionRaw raw{};
133 PFS0Superblock pfs0;
134 RomFSSuperblock romfs;
135 BKTRSuperblock bktr;
136};
137static_assert(sizeof(NCASectionHeader) == 0x200, "NCASectionHeader has incorrect size.");
138
139static 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
144NCA::NCA(VirtualFile file_, VirtualFile bktr_base_romfs_, u64 bktr_base_ivfc_offset) 25NCA::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
176NCA::~NCA() = default;
177
178bool 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
192bool 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
225std::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);
247bool 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
279bool 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
403bool 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
438u8 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
447std::optional<Core::Crypto::Key128> NCA::GetKeyAreaKey(NCASectionCryptoType type) const { 117NCA::~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
478std::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
506VirtualFile 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
555Loader::ResultStatus NCA::GetStatus() const { 119Loader::ResultStatus NCA::GetStatus() const {
556 return status; 120 return status;
@@ -579,21 +143,24 @@ VirtualDir NCA::GetParentDirectory() const {
579} 143}
580 144
581NCAContentType NCA::GetType() const { 145NCAContentType NCA::GetType() const {
582 return header.content_type; 146 return static_cast<NCAContentType>(reader->GetContentType());
583} 147}
584 148
585u64 NCA::GetTitleId() const { 149u64 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
591std::array<u8, 16> NCA::GetRightsId() const { 156RightsId 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
595u32 NCA::GetSDKVersion() const { 162u32 NCA::GetSDKVersion() const {
596 return header.sdk_version; 163 return reader->GetSdkAddonVersion();
597} 164}
598 165
599bool NCA::IsUpdate() const { 166bool NCA::IsUpdate() const {
@@ -612,10 +179,6 @@ VirtualFile NCA::GetBaseFile() const {
612 return file; 179 return file;
613} 180}
614 181
615u64 NCA::GetBaseIVFCOffset() const {
616 return ivfc_offset;
617}
618
619VirtualDir NCA::GetLogoPartition() const { 182VirtualDir 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
22namespace FileSys { 22namespace FileSys {
23 23
24union NCASectionHeader; 24class NcaReader;
25 25
26/// Describes the type of content within an NCA archive. 26/// Describes the type of content within an NCA archive.
27enum class NCAContentType : u8 { 27enum class NCAContentType : u8 {
@@ -45,41 +45,7 @@ enum class NCAContentType : u8 {
45 PublicData = 5, 45 PublicData = 5,
46}; 46};
47 47
48enum class NCASectionCryptoType : u8 { 48using RightsId = std::array<u8, 0x10>;
49 NONE = 1,
50 XTS = 2,
51 CTR = 3,
52 BKTR = 4,
53};
54
55struct NCASectionTableEntry {
56 u32_le media_offset;
57 u32_le media_end_offset;
58 INSERT_PADDING_BYTES(0x8);
59};
60static_assert(sizeof(NCASectionTableEntry) == 0x10, "NCASectionTableEntry has incorrect size.");
61
62struct 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};
82static_assert(sizeof(NCAHeader) == 0x400, "NCAHeader has incorrect size.");
83 49
84inline bool IsDirectoryExeFS(const VirtualDir& pfs) { 50inline 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.
98class NCA : public ReadOnlyVfsDirectory { 64class NCA : public ReadOnlyVfsDirectory {
99public: 65public:
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
127private: 89private:
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};
17constexpr Result ERROR_INVALID_OFFSET{ErrorModule::FS, 6061}; 17constexpr Result ERROR_INVALID_OFFSET{ErrorModule::FS, 6061};
18constexpr Result ERROR_INVALID_SIZE{ErrorModule::FS, 6062}; 18constexpr Result ERROR_INVALID_SIZE{ErrorModule::FS, 6062};
19 19
20constexpr Result ResultUnsupportedSdkVersion{ErrorModule::FS, 50};
21constexpr Result ResultPartitionNotFound{ErrorModule::FS, 1001};
22constexpr Result ResultUnsupportedVersion{ErrorModule::FS, 3002};
23constexpr Result ResultOutOfRange{ErrorModule::FS, 3005};
24constexpr Result ResultAllocationMemoryFailedInFileSystemBuddyHeapA{ErrorModule::FS, 3294};
25constexpr Result ResultAllocationMemoryFailedInNcaFileSystemDriverI{ErrorModule::FS, 3341};
26constexpr Result ResultAllocationMemoryFailedInNcaReaderA{ErrorModule::FS, 3363};
27constexpr Result ResultAllocationMemoryFailedInAesCtrCounterExtendedStorageA{ErrorModule::FS, 3399};
28constexpr Result ResultAllocationMemoryFailedInIntegrityRomFsStorageA{ErrorModule::FS, 3412};
29constexpr Result ResultAllocationMemoryFailedMakeUnique{ErrorModule::FS, 3422};
30constexpr Result ResultAllocationMemoryFailedAllocateShared{ErrorModule::FS, 3423};
31constexpr Result ResultInvalidAesCtrCounterExtendedEntryOffset{ErrorModule::FS, 4012};
32constexpr Result ResultIndirectStorageCorrupted{ErrorModule::FS, 4021};
33constexpr Result ResultInvalidIndirectEntryOffset{ErrorModule::FS, 4022};
34constexpr Result ResultInvalidIndirectEntryStorageIndex{ErrorModule::FS, 4023};
35constexpr Result ResultInvalidIndirectStorageSize{ErrorModule::FS, 4024};
36constexpr Result ResultInvalidBucketTreeSignature{ErrorModule::FS, 4032};
37constexpr Result ResultInvalidBucketTreeEntryCount{ErrorModule::FS, 4033};
38constexpr Result ResultInvalidBucketTreeNodeEntryCount{ErrorModule::FS, 4034};
39constexpr Result ResultInvalidBucketTreeNodeOffset{ErrorModule::FS, 4035};
40constexpr Result ResultInvalidBucketTreeEntryOffset{ErrorModule::FS, 4036};
41constexpr Result ResultInvalidBucketTreeEntrySetOffset{ErrorModule::FS, 4037};
42constexpr Result ResultInvalidBucketTreeNodeIndex{ErrorModule::FS, 4038};
43constexpr Result ResultInvalidBucketTreeVirtualOffset{ErrorModule::FS, 4039};
44constexpr Result ResultRomNcaInvalidPatchMetaDataHashType{ErrorModule::FS, 4084};
45constexpr Result ResultRomNcaInvalidIntegrityLayerInfoOffset{ErrorModule::FS, 4085};
46constexpr Result ResultRomNcaInvalidPatchMetaDataHashDataSize{ErrorModule::FS, 4086};
47constexpr Result ResultRomNcaInvalidPatchMetaDataHashDataOffset{ErrorModule::FS, 4087};
48constexpr Result ResultRomNcaInvalidPatchMetaDataHashDataHash{ErrorModule::FS, 4088};
49constexpr Result ResultRomNcaInvalidSparseMetaDataHashType{ErrorModule::FS, 4089};
50constexpr Result ResultRomNcaInvalidSparseMetaDataHashDataSize{ErrorModule::FS, 4090};
51constexpr Result ResultRomNcaInvalidSparseMetaDataHashDataOffset{ErrorModule::FS, 4091};
52constexpr Result ResultRomNcaInvalidSparseMetaDataHashDataHash{ErrorModule::FS, 4091};
53constexpr Result ResultNcaBaseStorageOutOfRangeB{ErrorModule::FS, 4509};
54constexpr Result ResultNcaBaseStorageOutOfRangeC{ErrorModule::FS, 4510};
55constexpr Result ResultNcaBaseStorageOutOfRangeD{ErrorModule::FS, 4511};
56constexpr Result ResultInvalidNcaSignature{ErrorModule::FS, 4517};
57constexpr Result ResultNcaFsHeaderHashVerificationFailed{ErrorModule::FS, 4520};
58constexpr Result ResultInvalidNcaKeyIndex{ErrorModule::FS, 4521};
59constexpr Result ResultInvalidNcaFsHeaderHashType{ErrorModule::FS, 4522};
60constexpr Result ResultInvalidNcaFsHeaderEncryptionType{ErrorModule::FS, 4523};
61constexpr Result ResultInvalidNcaPatchInfoIndirectSize{ErrorModule::FS, 4524};
62constexpr Result ResultInvalidNcaPatchInfoAesCtrExSize{ErrorModule::FS, 4525};
63constexpr Result ResultInvalidNcaPatchInfoAesCtrExOffset{ErrorModule::FS, 4526};
64constexpr Result ResultInvalidNcaHeader{ErrorModule::FS, 4528};
65constexpr Result ResultInvalidNcaFsHeader{ErrorModule::FS, 4529};
66constexpr Result ResultNcaBaseStorageOutOfRangeE{ErrorModule::FS, 4530};
67constexpr Result ResultInvalidHierarchicalSha256BlockSize{ErrorModule::FS, 4532};
68constexpr Result ResultInvalidHierarchicalSha256LayerCount{ErrorModule::FS, 4533};
69constexpr Result ResultHierarchicalSha256BaseStorageTooLarge{ErrorModule::FS, 4534};
70constexpr Result ResultHierarchicalSha256HashVerificationFailed{ErrorModule::FS, 4535};
71constexpr Result ResultInvalidNcaHierarchicalIntegrityVerificationLayerCount{ErrorModule::FS, 4541};
72constexpr Result ResultInvalidNcaIndirectStorageOutOfRange{ErrorModule::FS, 4542};
73constexpr Result ResultInvalidNcaHeader1SignatureKeyGeneration{ErrorModule::FS, 4543};
74constexpr Result ResultInvalidCompressedStorageSize{ErrorModule::FS, 4547};
75constexpr Result ResultInvalidNcaMetaDataHashDataSize{ErrorModule::FS, 4548};
76constexpr Result ResultInvalidNcaMetaDataHashDataHash{ErrorModule::FS, 4549};
77constexpr Result ResultUnexpectedInCompressedStorageA{ErrorModule::FS, 5324};
78constexpr Result ResultUnexpectedInCompressedStorageB{ErrorModule::FS, 5325};
79constexpr Result ResultUnexpectedInCompressedStorageC{ErrorModule::FS, 5326};
80constexpr Result ResultUnexpectedInCompressedStorageD{ErrorModule::FS, 5327};
81constexpr Result ResultInvalidArgument{ErrorModule::FS, 6001};
82constexpr Result ResultInvalidOffset{ErrorModule::FS, 6061};
83constexpr Result ResultInvalidSize{ErrorModule::FS, 6062};
84constexpr Result ResultNullptrArgument{ErrorModule::FS, 6063};
85constexpr Result ResultUnsupportedSetSizeForIndirectStorage{ErrorModule::FS, 6325};
86constexpr Result ResultUnsupportedWriteForCompressedStorage{ErrorModule::FS, 6387};
87constexpr Result ResultUnsupportedOperateRangeForCompressedStorage{ErrorModule::FS, 6388};
88constexpr 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
10namespace FileSys {
11
12class IStorage : public VfsFile {
13public:
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
47class IReadOnlyStorage : public IStorage {
48public:
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
8namespace FileSys {
9
10struct 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
33struct HashSalt {
34 static constexpr size_t Size = 32;
35
36 std::array<u8, Size> value;
37};
38static_assert(std::is_trivial_v<HashSalt>);
39static_assert(sizeof(HashSalt) == HashSalt::Size);
40
41constexpr inline size_t IntegrityMinLayerCount = 2;
42constexpr inline size_t IntegrityMaxLayerCount = 7;
43constexpr inline size_t IntegrityLayerCountSave = 5;
44constexpr 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
9namespace FileSys {
10
11namespace {
12
13class SoftwareDecryptor final : public AesCtrCounterExtendedStorage::IDecryptor {
14public:
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
22Result 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
29Result 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
55Result 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
84void AesCtrCounterExtendedStorage::Finalize() {
85 if (this->IsInitialized()) {
86 m_table.Finalize();
87 m_data_storage = VirtualFile();
88 }
89}
90
91Result 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
153size_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
242void 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
12namespace FileSys {
13
14using namespace Common::Literals;
15
16class AesCtrCounterExtendedStorage : public IReadOnlyStorage {
17 YUZU_NON_COPYABLE(AesCtrCounterExtendedStorage);
18 YUZU_NON_MOVEABLE(AesCtrCounterExtendedStorage);
19
20public:
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
58public:
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
73public:
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
101private:
102 Result Initialize(const void* key, size_t key_size, u32 secure_value, VirtualFile data_storage,
103 VirtualFile table_storage);
104
105private:
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
10namespace FileSys {
11
12void 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
23AesCtrStorage::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
38size_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
66size_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
125size_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
14namespace FileSys {
15
16class AesCtrStorage : public IStorage {
17 YUZU_NON_COPYABLE(AesCtrStorage);
18 YUZU_NON_MOVEABLE(AesCtrStorage);
19
20public:
21 static constexpr size_t BlockSize = 0x10;
22 static constexpr size_t KeySize = 0x10;
23 static constexpr size_t IvSize = 0x10;
24
25public:
26 static void MakeIv(void* dst, size_t dst_size, u64 upper, s64 offset);
27
28public:
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
36private:
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
11namespace FileSys {
12
13void 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
23AesXtsStorage::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
41size_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
108size_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
12namespace FileSys {
13
14class AesXtsStorage : public IReadOnlyStorage {
15 YUZU_NON_COPYABLE(AesXtsStorage);
16 YUZU_NON_MOVEABLE(AesXtsStorage);
17
18public:
19 static constexpr size_t AesBlockSize = 0x10;
20 static constexpr size_t KeySize = 0x20;
21 static constexpr size_t IvSize = 0x10;
22
23public:
24 static void MakeAesXtsIv(void* dst, size_t dst_size, s64 offset, size_t block_size);
25
26public:
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
33private:
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
12namespace FileSys {
13
14template <size_t DataAlign_, size_t BufferAlign_>
15class AlignmentMatchingStorage : public IStorage {
16 YUZU_NON_COPYABLE(AlignmentMatchingStorage);
17 YUZU_NON_MOVEABLE(AlignmentMatchingStorage);
18
19public:
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
28private:
29 VirtualFile m_base_storage;
30 s64 m_base_storage_size;
31
32public:
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
78template <size_t BufferAlign_>
79class AlignmentMatchingStoragePooledBuffer : public IStorage {
80 YUZU_NON_COPYABLE(AlignmentMatchingStoragePooledBuffer);
81 YUZU_NON_MOVEABLE(AlignmentMatchingStoragePooledBuffer);
82
83public:
84 static constexpr size_t BufferAlign = BufferAlign_;
85
86 static_assert(Common::IsPowerOfTwo(BufferAlign));
87
88private:
89 VirtualFile m_base_storage;
90 s64 m_base_storage_size;
91 size_t m_data_align;
92
93public:
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
7namespace FileSys {
8
9namespace {
10
11template <typename T>
12constexpr size_t GetRoundDownDifference(T x, size_t align) {
13 return static_cast<size_t>(x - Common::AlignDown(x, align));
14}
15
16template <typename T>
17constexpr size_t GetRoundUpDifference(T x, size_t align) {
18 return static_cast<size_t>(Common::AlignUp(x, align) - x);
19}
20
21template <typename T>
22size_t GetRoundUpDifference(T* x, size_t align) {
23 return GetRoundUpDifference(reinterpret_cast<uintptr_t>(x), align);
24}
25
26} // namespace
27
28size_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
123size_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
9namespace FileSys {
10
11class AlignmentMatchingStorageImpl {
12public:
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
9namespace FileSys {
10
11namespace {
12
13using Node = impl::BucketTreeNode<const s64*>;
14static_assert(sizeof(Node) == sizeof(BucketTree::NodeHeader));
15static_assert(std::is_trivial_v<Node>);
16
17constexpr inline s32 NodeHeaderSize = sizeof(BucketTree::NodeHeader);
18
19class StorageNode {
20private:
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
84private:
85 const Offset m_start;
86 const s32 m_count;
87 s32 m_index;
88
89public:
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
147void 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
156Result 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
163Result 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
175Result 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
233void 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
246void 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
263Result 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
278Result BucketTree::InvalidateCache() {
279 // Reset our offsets.
280 m_offset_cache.is_initialized = false;
281
282 R_SUCCEED();
283}
284
285Result 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
321Result 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
336Result 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
374Result 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
414Result 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
463Result 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
476Result 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
501Result 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
523Result 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
535Result 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
568Result 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
16namespace FileSys {
17
18using namespace Common::Literals;
19
20class BucketTree {
21 YUZU_NON_COPYABLE(BucketTree);
22 YUZU_NON_MOVEABLE(BucketTree);
23
24public:
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
31public:
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
124private:
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
192private:
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
221public:
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
257public:
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
291private:
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
302private:
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
320private:
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
332class BucketTree::Visitor {
333 YUZU_NON_COPYABLE(Visitor);
334 YUZU_NON_MOVEABLE(Visitor);
335
336public:
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
379private:
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
393private:
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
11namespace FileSys {
12
13template <typename EntryType>
14Result 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
147template <typename EntryType>
148Result 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
8namespace FileSys::impl {
9
10class SafeValue {
11public:
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
39template <typename IteratorType>
40struct 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
98constexpr 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
104constexpr 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
15namespace FileSys {
16
17using namespace Common::Literals;
18
19class CompressedStorage : public IReadOnlyStorage {
20 YUZU_NON_COPYABLE(CompressedStorage);
21 YUZU_NON_MOVEABLE(CompressedStorage);
22
23public:
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
39public:
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
48private:
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
898public:
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
943public:
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
958private:
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
8namespace FileSys {
9
10enum class CompressionType : u8 {
11 None = 0,
12 Zeros = 1,
13 Two = 2,
14 Lz4 = 3,
15 Unknown = 4,
16};
17
18using DecompressorFunction = Result (*)(void*, size_t, const void*, size_t);
19using GetDecompressorFunction = DecompressorFunction (*)(CompressionType);
20
21constexpr s64 CompressionBlockAlignment = 0x10;
22
23namespace CompressionTypeUtility {
24
25constexpr bool IsBlockAlignmentRequired(CompressionType type) {
26 return type != CompressionType::None && type != CompressionType::Zeros;
27}
28
29constexpr bool IsDataStorageAccessRequired(CompressionType type) {
30 return type != CompressionType::Zeros;
31}
32
33constexpr bool IsRandomAccessible(CompressionType type) {
34 return type == CompressionType::None;
35}
36
37constexpr 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
7namespace FileSys {
8
9namespace {
10
11Result 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
17constexpr 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
28const 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
8namespace FileSys {
9
10const 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
8namespace FileSys {
9
10namespace {
11
12void 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
49const 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
8namespace FileSys {
9
10const 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
7namespace FileSys {
8
9HierarchicalIntegrityVerificationStorage::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
16Result 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
93void 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
106size_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
123size_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
13namespace FileSys {
14
15struct HierarchicalIntegrityVerificationLevelInformation {
16 Int64 offset;
17 Int64 size;
18 s32 block_order;
19 std::array<u8, 4> reserved;
20};
21static_assert(std::is_trivial_v<HierarchicalIntegrityVerificationLevelInformation>);
22static_assert(sizeof(HierarchicalIntegrityVerificationLevelInformation) == 0x18);
23static_assert(alignof(HierarchicalIntegrityVerificationLevelInformation) == 0x4);
24
25struct 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};
42static_assert(std::is_trivial_v<HierarchicalIntegrityVerificationInformation>);
43
44struct HierarchicalIntegrityVerificationMetaInformation {
45 u32 magic;
46 u32 version;
47 u32 master_hash_size;
48 HierarchicalIntegrityVerificationInformation level_hash_info;
49};
50static_assert(std::is_trivial_v<HierarchicalIntegrityVerificationMetaInformation>);
51
52struct HierarchicalIntegrityVerificationSizeSet {
53 s64 control_size;
54 s64 master_hash_size;
55 std::array<s64, IntegrityMaxLayerCount - 2> layered_hash_sizes;
56};
57static_assert(std::is_trivial_v<HierarchicalIntegrityVerificationSizeSet>);
58
59class HierarchicalIntegrityVerificationStorage : public IReadOnlyStorage {
60 YUZU_NON_COPYABLE(HierarchicalIntegrityVerificationStorage);
61 YUZU_NON_MOVEABLE(HierarchicalIntegrityVerificationStorage);
62
63public:
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
110public:
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
138public:
139 static constexpr s8 GetDefaultDataCacheBufferLevel(u32 max_layers) {
140 return static_cast<s8>(16 + max_layers - 2);
141 }
142
143protected:
144 static constexpr s64 HashSize = 256 / 8;
145 static constexpr size_t MaxLayers = IntegrityMaxLayerCount;
146
147private:
148 static GenerateRandomFunction s_generate_random;
149
150 static void SetGenerateRandomFunction(GenerateRandomFunction func) {
151 s_generate_random = func;
152 }
153
154private:
155 friend struct HierarchicalIntegrityVerificationMetaInformation;
156
157private:
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
8namespace FileSys {
9
10namespace {
11
12s32 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
25Result 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
67size_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
12namespace FileSys {
13
14class HierarchicalSha256Storage : public IReadOnlyStorage {
15 YUZU_NON_COPYABLE(HierarchicalSha256Storage);
16 YUZU_NON_MOVEABLE(HierarchicalSha256Storage);
17
18public:
19 static constexpr s32 LayerCount = 3;
20 static constexpr size_t HashSize = 256 / 8;
21
22public:
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
34private:
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
7namespace FileSys {
8
9Result 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
28void 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
37Result 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
99size_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
13namespace FileSys {
14
15class IndirectStorage : public IReadOnlyStorage {
16 YUZU_NON_COPYABLE(IndirectStorage);
17 YUZU_NON_MOVEABLE(IndirectStorage);
18
19public:
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
64public:
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
105public:
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
118protected:
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
131private:
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
151private:
152 mutable BucketTree m_table;
153 std::array<VirtualFile, StorageCount> m_data_storage;
154};
155
156template <bool ContinuousCheck, bool RangeCheck, typename F>
157Result 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
6namespace FileSys {
7
8Result 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
26void 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
10namespace FileSys {
11
12constexpr inline size_t IntegrityLayerCountRomFs = 7;
13constexpr inline size_t IntegrityHashLayerBlockSize = 16_KiB;
14
15class IntegrityRomFsStorage : public IReadOnlyStorage {
16public:
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
36private:
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
7namespace FileSys {
8
9constexpr 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
14void 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
45void IntegrityVerificationStorage::Finalize() {
46 m_hash_storage = VirtualFile();
47 m_data_storage = VirtualFile();
48}
49
50size_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
87size_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
11namespace FileSys {
12
13class IntegrityVerificationStorage : public IReadOnlyStorage {
14 YUZU_NON_COPYABLE(IntegrityVerificationStorage);
15 YUZU_NON_MOVEABLE(IntegrityVerificationStorage);
16
17public:
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
25public:
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
44private:
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
55private:
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
8namespace FileSys {
9
10class MemoryResourceBufferHoldStorage : public IStorage {
11 YUZU_NON_COPYABLE(MemoryResourceBufferHoldStorage);
12 YUZU_NON_MOVEABLE(MemoryResourceBufferHoldStorage);
13
14public:
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
33public:
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
55private:
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
20namespace FileSys {
21
22namespace {
23
24constexpr inline s32 IntegrityDataCacheCount = 24;
25constexpr inline s32 IntegrityHashCacheCount = 8;
26
27constexpr inline s32 IntegrityDataCacheCountForMeta = 16;
28constexpr inline s32 IntegrityHashCacheCountForMeta = 2;
29
30class SharedNcaBodyStorage : public IReadOnlyStorage {
31 YUZU_NON_COPYABLE(SharedNcaBodyStorage);
32 YUZU_NON_MOVEABLE(SharedNcaBodyStorage);
33
34private:
35 VirtualFile m_storage;
36 std::shared_ptr<NcaReader> m_nca_reader;
37
38public:
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
58inline s64 GetFsOffset(const NcaReader& reader, s32 fs_index) {
59 return static_cast<s64>(reader.GetFsOffset(fs_index));
60}
61
62inline s64 GetFsEndOffset(const NcaReader& reader, s32 fs_index) {
63 return static_cast<s64>(reader.GetFsEndOffset(fs_index));
64}
65
66using Sha256DataRegion = NcaFsHeader::Region;
67using IntegrityLevelInfo = NcaFsHeader::HashData::IntegrityMetaInfo::LevelHashInfo;
68using IntegrityDataInfo = IntegrityLevelInfo::HierarchicalIntegrityVerificationLevelInformation;
69
70} // namespace
71
72Result 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
79Result 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
292Result 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
328Result 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
397Result 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
418Result 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
456Result 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
484Result 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
523Result 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
568Result 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
635Result 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
702Result 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
776Result 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
835Result 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
925Result 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
957Result 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
1014Result 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
1096Result 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
1160Result 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
1169Result 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
1208Result 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
1271Result 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
1292Result 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
1300Result 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
10namespace FileSys {
11
12class CompressedStorage;
13class AesCtrCounterExtendedStorage;
14class IndirectStorage;
15class SparseStorage;
16
17struct NcaCryptoConfiguration;
18
19using KeyGenerationFunction = void (*)(void* dst_key, size_t dst_key_size, const void* src_key,
20 size_t src_key_size, s32 key_type);
21using VerifySign1Function = bool (*)(const void* sig, size_t sig_size, const void* data,
22 size_t data_size, u8 generation);
23
24struct 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};
52static_assert(std::is_trivial_v<NcaCryptoConfiguration>);
53
54struct NcaCompressionConfiguration {
55 GetDecompressorFunction get_decompressor;
56};
57static_assert(std::is_trivial_v<NcaCompressionConfiguration>);
58
59constexpr inline s32 KeyAreaEncryptionKeyCount =
60 NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount *
61 NcaCryptoConfiguration::KeyGenerationMax;
62
63enum 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
74constexpr inline bool IsInvalidKeyTypeValue(s32 key_type) {
75 return key_type < 0;
76}
77
78constexpr 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
90class NcaReader {
91 YUZU_NON_COPYABLE(NcaReader);
92 YUZU_NON_MOVEABLE(NcaReader);
93
94public:
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
141private:
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
156class NcaFsHeaderReader {
157 YUZU_NON_COPYABLE(NcaFsHeaderReader);
158 YUZU_NON_MOVEABLE(NcaFsHeaderReader);
159
160public:
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
204private:
205 NcaFsHeader m_data;
206 s32 m_fs_index;
207};
208
209class NcaFileSystemDriver {
210 YUZU_NON_COPYABLE(NcaFileSystemDriver);
211 YUZU_NON_MOVEABLE(NcaFileSystemDriver);
212
213public:
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
237private:
238 enum class AlignmentStorageRequirement {
239 CacheBlockSize = 0,
240 None = 1,
241 };
242
243public:
244 static Result SetupFsHeaderReader(NcaFsHeaderReader* out, const NcaReader& reader,
245 s32 fs_index);
246
247public:
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
269public:
270 Result CreateStorageByRawStorage(VirtualFile* out, const NcaFsHeaderReader* header_reader,
271 VirtualFile raw_storage, StorageContext* ctx);
272
273private:
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
353public:
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
359private:
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
6namespace FileSys {
7
8u8 NcaHeader::GetProperKeyGeneration() const {
9 return std::max(this->key_generation, this->key_generation_2);
10}
11
12bool NcaPatchInfo::HasIndirectTable() const {
13 return this->indirect_size != 0;
14}
15
16bool 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
13namespace FileSys {
14
15using namespace Common::Literals;
16
17struct Hash {
18 static constexpr std::size_t Size = 256 / 8;
19 std::array<u8, Size> value;
20};
21static_assert(sizeof(Hash) == Hash::Size);
22static_assert(std::is_trivial_v<Hash>);
23
24using NcaDigest = Hash;
25
26struct 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};
122static_assert(sizeof(NcaHeader) == NcaHeader::Size);
123static_assert(std::is_trivial_v<NcaHeader>);
124
125struct NcaBucketInfo {
126 static constexpr size_t HeaderSize = 0x10;
127 Int64 offset;
128 Int64 size;
129 std::array<u8, HeaderSize> header;
130};
131static_assert(std::is_trivial_v<NcaBucketInfo>);
132
133struct 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};
147static_assert(std::is_trivial_v<NcaPatchInfo>);
148
149union NcaAesCtrUpperIv {
150 u64 value;
151 struct {
152 u32 generation;
153 u32 secure_value;
154 } part;
155};
156static_assert(std::is_trivial_v<NcaAesCtrUpperIv>);
157
158struct 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};
178static_assert(std::is_trivial_v<NcaSparseInfo>);
179
180struct NcaCompressionInfo {
181 NcaBucketInfo bucket;
182 std::array<u8, 8> resreved;
183};
184static_assert(std::is_trivial_v<NcaCompressionInfo>);
185
186struct NcaMetaDataHashDataInfo {
187 Int64 offset;
188 Int64 size;
189 Hash hash;
190};
191static_assert(std::is_trivial_v<NcaMetaDataHashDataInfo>);
192
193struct 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};
321static_assert(sizeof(NcaFsHeader) == NcaFsHeader::Size);
322static_assert(std::is_trivial_v<NcaFsHeader>);
323static_assert(offsetof(NcaFsHeader, patch_info) == NcaPatchInfo::Offset);
324
325inline constexpr const size_t NcaFsHeader::HashData::HierarchicalSha256Data::MasterHashOffset =
326 offsetof(NcaFsHeader, hash_data.hierarchical_sha256_data.fs_data_master_hash);
327inline constexpr const size_t NcaFsHeader::HashData::IntegrityMetaInfo::MasterHashOffset =
328 offsetof(NcaFsHeader, hash_data.integrity_meta_info.master_hash);
329
330struct NcaMetaDataHashData {
331 s64 layer_info_offset;
332 NcaFsHeader::HashData::IntegrityMetaInfo integrity_meta_info;
333};
334static_assert(sizeof(NcaMetaDataHashData) ==
335 sizeof(NcaFsHeader::HashData::IntegrityMetaInfo) + sizeof(s64));
336static_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
8namespace FileSys {
9
10namespace {
11
12constexpr inline u32 SdkAddonVersionMin = 0x000B0000;
13constexpr inline size_t Aes128KeySize = 0x10;
14constexpr const std::array<u8, Aes128KeySize> ZeroKey{};
15
16constexpr 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
30NcaReader::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
39NcaReader::~NcaReader() {}
40
41Result 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
169VirtualFile NcaReader::GetSharedBodyStorage() {
170 ASSERT(m_body_storage != nullptr);
171 return m_body_storage;
172}
173
174u32 NcaReader::GetMagic() const {
175 ASSERT(m_body_storage != nullptr);
176 return m_header.magic;
177}
178
179NcaHeader::DistributionType NcaReader::GetDistributionType() const {
180 ASSERT(m_body_storage != nullptr);
181 return m_header.distribution_type;
182}
183
184NcaHeader::ContentType NcaReader::GetContentType() const {
185 ASSERT(m_body_storage != nullptr);
186 return m_header.content_type;
187}
188
189u8 NcaReader::GetHeaderSign1KeyGeneration() const {
190 ASSERT(m_body_storage != nullptr);
191 return m_header.header1_signature_key_generation;
192}
193
194u8 NcaReader::GetKeyGeneration() const {
195 ASSERT(m_body_storage != nullptr);
196 return m_header.GetProperKeyGeneration();
197}
198
199u8 NcaReader::GetKeyIndex() const {
200 ASSERT(m_body_storage != nullptr);
201 return m_header.key_index;
202}
203
204u64 NcaReader::GetContentSize() const {
205 ASSERT(m_body_storage != nullptr);
206 return m_header.content_size;
207}
208
209u64 NcaReader::GetProgramId() const {
210 ASSERT(m_body_storage != nullptr);
211 return m_header.program_id;
212}
213
214u32 NcaReader::GetContentIndex() const {
215 ASSERT(m_body_storage != nullptr);
216 return m_header.content_index;
217}
218
219u32 NcaReader::GetSdkAddonVersion() const {
220 ASSERT(m_body_storage != nullptr);
221 return m_header.sdk_addon_version;
222}
223
224void 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
231bool 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
236s32 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
246const 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
252void 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
259void 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
266u64 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
272u64 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
278u64 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
285void 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
293const 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
299bool 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
309bool NcaReader::HasInternalDecryptionKeyForAesHw() const {
310 return std::memcmp(ZeroKey.data(), this->GetDecryptionKey(NcaHeader::DecryptionKey_AesCtrHw),
311 Aes128KeySize) != 0;
312}
313
314bool NcaReader::IsSoftwareAesPrioritized() const {
315 return m_is_software_aes_prioritized;
316}
317
318void NcaReader::PrioritizeSoftwareAes() {
319 m_is_software_aes_prioritized = true;
320}
321
322bool NcaReader::IsAvailableSwKey() const {
323 return m_is_available_sw_key;
324}
325
326bool NcaReader::HasExternalDecryptionKey() const {
327 return std::memcmp(ZeroKey.data(), this->GetExternalDecryptionKey(), Aes128KeySize) != 0;
328}
329
330const void* NcaReader::GetExternalDecryptionKey() const {
331 return m_external_decryption_key.data();
332}
333
334void 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
341void 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
349GetDecompressorFunction NcaReader::GetDecompressor() const {
350 ASSERT(m_get_decompressor != nullptr);
351 return m_get_decompressor;
352}
353
354NcaHeader::EncryptionType NcaReader::GetEncryptionType() const {
355 return m_header_encryption_type;
356}
357
358Result 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
368bool NcaReader::GetHeaderSign1Valid() const {
369 return m_is_header_sign1_signature_valid;
370}
371
372void 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
379Result 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
391void 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
399NcaFsHeader::HashData& NcaFsHeaderReader::GetHashData() {
400 ASSERT(this->IsInitialized());
401 return m_data.hash_data;
402}
403
404const NcaFsHeader::HashData& NcaFsHeaderReader::GetHashData() const {
405 ASSERT(this->IsInitialized());
406 return m_data.hash_data;
407}
408
409u16 NcaFsHeaderReader::GetVersion() const {
410 ASSERT(this->IsInitialized());
411 return m_data.version;
412}
413
414s32 NcaFsHeaderReader::GetFsIndex() const {
415 ASSERT(this->IsInitialized());
416 return m_fs_index;
417}
418
419NcaFsHeader::FsType NcaFsHeaderReader::GetFsType() const {
420 ASSERT(this->IsInitialized());
421 return m_data.fs_type;
422}
423
424NcaFsHeader::HashType NcaFsHeaderReader::GetHashType() const {
425 ASSERT(this->IsInitialized());
426 return m_data.hash_type;
427}
428
429NcaFsHeader::EncryptionType NcaFsHeaderReader::GetEncryptionType() const {
430 ASSERT(this->IsInitialized());
431 return m_data.encryption_type;
432}
433
434NcaPatchInfo& NcaFsHeaderReader::GetPatchInfo() {
435 ASSERT(this->IsInitialized());
436 return m_data.patch_info;
437}
438
439const NcaPatchInfo& NcaFsHeaderReader::GetPatchInfo() const {
440 ASSERT(this->IsInitialized());
441 return m_data.patch_info;
442}
443
444const NcaAesCtrUpperIv NcaFsHeaderReader::GetAesCtrUpperIv() const {
445 ASSERT(this->IsInitialized());
446 return m_data.aes_ctr_upper_iv;
447}
448
449bool NcaFsHeaderReader::IsSkipLayerHashEncryption() const {
450 ASSERT(this->IsInitialized());
451 return m_data.IsSkipLayerHashEncryption();
452}
453
454Result NcaFsHeaderReader::GetHashTargetOffset(s64* out) const {
455 ASSERT(out != nullptr);
456 ASSERT(this->IsInitialized());
457
458 R_RETURN(m_data.GetHashTargetOffset(out));
459}
460
461bool NcaFsHeaderReader::ExistsSparseLayer() const {
462 ASSERT(this->IsInitialized());
463 return m_data.sparse_info.generation != 0;
464}
465
466NcaSparseInfo& NcaFsHeaderReader::GetSparseInfo() {
467 ASSERT(this->IsInitialized());
468 return m_data.sparse_info;
469}
470
471const NcaSparseInfo& NcaFsHeaderReader::GetSparseInfo() const {
472 ASSERT(this->IsInitialized());
473 return m_data.sparse_info;
474}
475
476bool 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
481NcaCompressionInfo& NcaFsHeaderReader::GetCompressionInfo() {
482 ASSERT(this->IsInitialized());
483 return m_data.compression_info;
484}
485
486const NcaCompressionInfo& NcaFsHeaderReader::GetCompressionInfo() const {
487 ASSERT(this->IsInitialized());
488 return m_data.compression_info;
489}
490
491bool NcaFsHeaderReader::ExistsPatchMetaHashLayer() const {
492 ASSERT(this->IsInitialized());
493 return m_data.meta_data_hash_data_info.size != 0 && this->GetPatchInfo().HasIndirectTable();
494}
495
496NcaMetaDataHashDataInfo& NcaFsHeaderReader::GetPatchMetaDataHashDataInfo() {
497 ASSERT(this->IsInitialized());
498 return m_data.meta_data_hash_data_info;
499}
500
501const NcaMetaDataHashDataInfo& NcaFsHeaderReader::GetPatchMetaDataHashDataInfo() const {
502 ASSERT(this->IsInitialized());
503 return m_data.meta_data_hash_data_info;
504}
505
506NcaFsHeader::MetaDataHashType NcaFsHeaderReader::GetPatchMetaHashType() const {
507 ASSERT(this->IsInitialized());
508 return m_data.meta_data_hash_type;
509}
510
511bool NcaFsHeaderReader::ExistsSparseMetaHashLayer() const {
512 ASSERT(this->IsInitialized());
513 return m_data.meta_data_hash_data_info.size != 0 && this->ExistsSparseLayer();
514}
515
516NcaMetaDataHashDataInfo& NcaFsHeaderReader::GetSparseMetaDataHashDataInfo() {
517 ASSERT(this->IsInitialized());
518 return m_data.meta_data_hash_data_info;
519}
520
521const NcaMetaDataHashDataInfo& NcaFsHeaderReader::GetSparseMetaDataHashDataInfo() const {
522 ASSERT(this->IsInitialized());
523 return m_data.meta_data_hash_data_info;
524}
525
526NcaFsHeader::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
7namespace FileSys {
8
9namespace {
10
11constexpr size_t HeapBlockSize = BufferPoolAlignment;
12static_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.
16constexpr s32 HeapOrderMax = 7;
17constexpr s32 HeapOrderMaxForLarge = HeapOrderMax + 3;
18
19constexpr size_t HeapAllocatableSizeMax = HeapBlockSize * (static_cast<size_t>(1) << HeapOrderMax);
20constexpr size_t HeapAllocatableSizeMaxForLarge =
21 HeapBlockSize * (static_cast<size_t>(1) << HeapOrderMaxForLarge);
22
23} // namespace
24
25size_t PooledBuffer::GetAllocatableSizeMaxCore(bool large) {
26 return large ? HeapAllocatableSizeMaxForLarge : HeapAllocatableSizeMax;
27}
28
29void 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
50void 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
11namespace FileSys {
12
13using namespace Common::Literals;
14
15constexpr inline size_t BufferPoolAlignment = 4_KiB;
16constexpr inline size_t BufferPoolWorkSize = 320;
17
18class PooledBuffer {
19 YUZU_NON_COPYABLE(PooledBuffer);
20
21public:
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
71public:
72 static size_t GetAllocatableSizeMax() {
73 return GetAllocatableSizeMaxCore(false);
74 }
75 static size_t GetAllocatableParticularlyLargeSizeMax() {
76 return GetAllocatableSizeMaxCore(true);
77 }
78
79private:
80 static size_t GetAllocatableSizeMaxCore(bool large);
81
82private:
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
90private:
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
6namespace FileSys {
7
8size_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
8namespace FileSys {
9
10class SparseStorage : public IndirectStorage {
11 YUZU_NON_COPYABLE(SparseStorage);
12 YUZU_NON_MOVEABLE(SparseStorage);
13
14private:
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
35public:
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
63private:
64 void SetZeroStorage() {
65 return this->SetStorage(1, m_zero_storage, 0, std::numeric_limits<s64>::max());
66 }
67
68private:
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
8namespace FileSys {
9
10class RegionSwitchStorage : public IReadOnlyStorage {
11 YUZU_NON_COPYABLE(RegionSwitchStorage);
12 YUZU_NON_MOVEABLE(RegionSwitchStorage);
13
14public:
15 struct Region {
16 s64 offset;
17 s64 size;
18 };
19
20public:
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
49private:
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
74private:
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
6namespace FileSys {
7
8void 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
8namespace FileSys {
9
10void 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
13namespace FileSys {
14namespace {
15template <bool Subsection, typename BlockType, typename BucketType>
16std::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
58BKTR::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
81BKTR::~BKTR() = default;
82
83std::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
159RelocationEntry 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
164RelocationEntry 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
172SubsectionEntry 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
177SubsectionEntry 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
185std::string BKTR::GetName() const {
186 return base_romfs->GetName();
187}
188
189std::size_t BKTR::GetSize() const {
190 return relocation.size;
191}
192
193bool BKTR::Resize(std::size_t new_size) {
194 return false;
195}
196
197VirtualDir BKTR::GetContainingDirectory() const {
198 return base_romfs->GetContainingDirectory();
199}
200
201bool BKTR::IsWritable() const {
202 return false;
203}
204
205bool BKTR::IsReadable() const {
206 return true;
207}
208
209std::size_t BKTR::Write(const u8* data, std::size_t length, std::size_t offset) {
210 return 0;
211}
212
213bool 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
15namespace FileSys {
16
17#pragma pack(push, 1)
18struct RelocationEntry {
19 u64_le address_patch;
20 u64_le address_source;
21 u32 from_patch;
22};
23#pragma pack(pop)
24static_assert(sizeof(RelocationEntry) == 0x14, "RelocationEntry has incorrect size.");
25
26struct 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};
33static_assert(sizeof(RelocationBucketRaw) == 0x4000, "RelocationBucketRaw has incorrect size.");
34
35// Vector version of RelocationBucketRaw
36struct RelocationBucket {
37 u32 number_entries;
38 u64 end_offset;
39 std::vector<RelocationEntry> entries;
40};
41
42struct RelocationBlock {
43 INSERT_PADDING_BYTES(4);
44 u32_le number_buckets;
45 u64_le size;
46 std::array<u64, 0x7FE> base_offsets;
47};
48static_assert(sizeof(RelocationBlock) == 0x4000, "RelocationBlock has incorrect size.");
49
50struct SubsectionEntry {
51 u64_le address_patch;
52 INSERT_PADDING_BYTES(0x4);
53 u32_le ctr;
54};
55static_assert(sizeof(SubsectionEntry) == 0x10, "SubsectionEntry has incorrect size.");
56
57struct SubsectionBucketRaw {
58 INSERT_PADDING_BYTES(4);
59 u32_le number_entries;
60 u64_le end_offset;
61 std::array<SubsectionEntry, 0x3FF> subsection_entries;
62};
63static_assert(sizeof(SubsectionBucketRaw) == 0x4000, "SubsectionBucketRaw has incorrect size.");
64
65// Vector version of SubsectionBucketRaw
66struct SubsectionBucket {
67 u32 number_entries;
68 u64 end_offset;
69 std::vector<SubsectionEntry> entries;
70};
71
72struct SubsectionBlock {
73 INSERT_PADDING_BYTES(4);
74 u32_le number_buckets;
75 u64_le size;
76 std::array<u64, 0x7FE> base_offsets;
77};
78static_assert(sizeof(SubsectionBlock) == 0x4000, "SubsectionBlock has incorrect size.");
79
80inline 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
86inline 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
92class BKTR : public VfsFile {
93public:
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
118private:
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
415VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, ContentRecordType type, 421VirtualFile 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
506template <typename T> 506template <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
970std::vector<ContentProviderEntry> ManualContentProvider::ListEntriesFilter( 970std::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
32RomFSFactory::~RomFSFactory() = default; 31RomFSFactory::~RomFSFactory() = default;
33 32
34void RomFSFactory::SetPackedUpdate(VirtualFile update_raw_file) { 33void 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
38VirtualFile RomFSFactory::OpenCurrentProcess(u64 current_process_title_id) const { 37VirtualFile 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
48VirtualFile RomFSFactory::OpenPatchedRomFS(u64 title_id, ContentRecordType type) const { 49VirtualFile 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
60VirtualFile RomFSFactory::OpenPatchedRomFSWithProgramIndex(u64 title_id, u8 program_index, 61VirtualFile 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
50private:
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
52private:
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/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
376std::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
376Result FileSystemController::CreateSaveData(FileSys::VirtualDir* out_save_data, 381Result 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
16namespace FileSys { 16namespace FileSys {
17class BISFactory; 17class BISFactory;
18class NCA;
18class RegisteredCache; 19class RegisteredCache;
19class RegisteredCacheUnion; 20class RegisteredCacheUnion;
20class PlaceholderCache; 21class 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/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
79u64 AppLoader_NAX::ReadRomFSIVFCOffset() const {
80 return nca_loader->ReadRomFSIVFCOffset();
81}
82
83ResultStatus AppLoader_NAX::ReadProgramId(u64& out_program_id) { 79ResultStatus 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
80u64 AppLoader_NCA::ReadRomFSIVFCOffset() const {
81 if (nca == nullptr) {
82 return 0;
83 }
84
85 return nca->GetBaseIVFCOffset();
86}
87
88ResultStatus AppLoader_NCA::ReadProgramId(u64& out_program_id) { 96ResultStatus 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
124u64 AppLoader_NSP::ReadRomFSIVFCOffset() const {
125 return secondary_loader->ReadRomFSIVFCOffset();
126}
127
128ResultStatus AppLoader_NSP::ReadUpdateRaw(FileSys::VirtualFile& out_file) { 124ResultStatus 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
92u64 AppLoader_XCI::ReadRomFSIVFCOffset() const {
93 return nca_loader->ReadRomFSIVFCOffset();
94}
95
96ResultStatus AppLoader_XCI::ReadUpdateRaw(FileSys::VirtualFile& out_file) { 92ResultStatus 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/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;
33END_PUSH_CONSTANTS 33END_PUSH_CONSTANTS
34 34
35struct EncodingData { 35struct EncodingData {
36 uint encoding; 36 uint data;
37 uint num_bits;
38 uint bit_value;
39 uint quint_trit_value;
40}; 37};
41 38
42struct TexelWeightParams { 39layout(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
51layout(binding = BINDING_INPUT_BUFFER, std430) readonly buffer InputBufferU32 {
52 uvec4 astc_data[]; 40 uvec4 astc_data[];
53}; 41};
54 42
55layout(binding = BINDING_OUTPUT_IMAGE, rgba8) uniform writeonly image2DArray dest_image; 43layout(binding = BINDING_OUTPUT_IMAGE, rgba8) uniform writeonly restrict image2DArray dest_image;
56 44
57const uint GOB_SIZE_X_SHIFT = 6; 45const uint GOB_SIZE_X_SHIFT = 6;
58const uint GOB_SIZE_Y_SHIFT = 3; 46const 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
61const uint BYTES_PER_BLOCK_LOG2 = 4; 49const uint BYTES_PER_BLOCK_LOG2 = 4;
62 50
63const int JUST_BITS = 0; 51const uint JUST_BITS = 0u;
64const int QUINT = 1; 52const uint QUINT = 1u;
65const int TRIT = 2; 53const 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)
69EncodingData encoding_values[22] = EncodingData[]( 57const 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_"
85const uint REPLICATE_BIT_TO_7_TABLE[2] = uint[](0, 127);
86const uint REPLICATE_1_BIT_TO_9_TABLE[2] = uint[](0, 511);
87
88const uint REPLICATE_1_BIT_TO_8_TABLE[2] = uint[](0, 255);
89const uint REPLICATE_2_BIT_TO_8_TABLE[4] = uint[](0, 85, 170, 255);
90const uint REPLICATE_3_BIT_TO_8_TABLE[8] = uint[](0, 36, 73, 109, 146, 182, 219, 255);
91const 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);
93const 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);
96const uint REPLICATE_1_BIT_TO_6_TABLE[2] = uint[](0, 63);
97const uint REPLICATE_2_BIT_TO_6_TABLE[4] = uint[](0, 21, 42, 63);
98const uint REPLICATE_3_BIT_TO_6_TABLE[8] = uint[](0, 9, 18, 27, 36, 45, 54, 63);
99const 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);
101const 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);
104const 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);
109const 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
119uint current_index = 0;
120int bitsread = 0;
121int total_bitsread = 0; 66int total_bitsread = 0;
122uvec4 local_buff; 67uvec4 local_buff;
123 68
@@ -125,50 +70,60 @@ uvec4 local_buff;
125uvec4 color_endpoint_data; 70uvec4 color_endpoint_data;
126int color_bitsread = 0; 71int color_bitsread = 0;
127 72
128// Four values, two endpoints, four maximum partitions 73// Global "vector" to be pushed into when decoding
129uint color_values[32]; 74// At most will require BLOCK_WIDTH x BLOCK_HEIGHT in single plane mode
130int 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
133uvec4 texel_weight_data; 78#define ARRAY_NUM_ELEMENTS 144
134int texel_bitsread = 0; 79#define VECTOR_ARRAY_SIZE DIVCEIL(ARRAY_NUM_ELEMENTS * 2, 4)
80uint result_vector[ARRAY_NUM_ELEMENTS * 2];
135 81
136bool texel_flag = false;
137
138// Global "vectors" to be pushed into when decoding
139EncodingData result_vector[144];
140int result_index = 0; 82int result_index = 0;
83uint result_vector_max_index;
84bool result_limit_reached = false;
141 85
142EncodingData texel_vector[144]; 86// EncodingData helpers
143int texel_vector_index = 0; 87uint Encoding(EncodingData val) {
88 return bitfieldExtract(val.data, 0, 8);
89}
90uint NumBits(EncodingData val) {
91 return bitfieldExtract(val.data, 8, 8);
92}
93uint BitValue(EncodingData val) {
94 return bitfieldExtract(val.data, 16, 8);
95}
96uint QuintTritValue(EncodingData val) {
97 return bitfieldExtract(val.data, 24, 8);
98}
144 99
145uint unquantized_texel_weights[2][144]; 100void Encoding(inout EncodingData val, uint v) {
101 val.data = bitfieldInsert(val.data, v, 0, 8);
102}
103void NumBits(inout EncodingData val, uint v) {
104 val.data = bitfieldInsert(val.data, v, 8, 8);
105}
106void BitValue(inout EncodingData val, uint v) {
107 val.data = bitfieldInsert(val.data, v, 16, 8);
108}
109void QuintTritValue(inout EncodingData val, uint v) {
110 val.data = bitfieldInsert(val.data, v, 24, 8);
111}
146 112
147uint SwizzleOffset(uvec2 pos) { 113EncodingData 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. 119void ResultEmplaceBack(EncodingData val) {
156uint 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
174uvec4 ReplicateByteTo16(uvec4 value) { 129uvec4 ReplicateByteTo16(uvec4 value) {
@@ -176,64 +131,40 @@ uvec4 ReplicateByteTo16(uvec4 value) {
176} 131}
177 132
178uint ReplicateBitTo7(uint value) { 133uint ReplicateBitTo7(uint value) {
179 return REPLICATE_BIT_TO_7_TABLE[value]; 134 return value * 127;
180} 135}
181 136
182uint ReplicateBitTo9(uint value) { 137uint ReplicateBitTo9(uint value) {
183 return REPLICATE_1_BIT_TO_9_TABLE[value]; 138 return value * 511;
184} 139}
185 140
186uint FastReplicate(uint value, uint num_bits, uint to_bit) { 141uint 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
231uint FastReplicateTo8(uint value, uint num_bits) { 162uint FastReplicateTo8(uint value, uint num_bits) {
232 return FastReplicate(value, num_bits, 8); 163 return ReplicateBits(value, num_bits, 8);
233} 164}
234 165
235uint FastReplicateTo6(uint value, uint num_bits) { 166uint FastReplicateTo6(uint value, uint num_bits) {
236 return FastReplicate(value, num_bits, 6); 167 return ReplicateBits(value, num_bits, 6);
237} 168}
238 169
239uint Div3Floor(uint v) { 170uint Div3Floor(uint v) {
@@ -266,15 +197,15 @@ uint Hash52(uint p) {
266 return p; 197 return p;
267} 198}
268 199
269uint Select2DPartition(uint seed, uint x, uint y, uint partition_count, bool small_block) { 200uint 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
356uint StreamBits(uint num_bits) { 290uint 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
297void SkipBits(uint num_bits) {
298 const int int_bits = int(num_bits);
299 total_bitsread += int_bits;
300}
301
363uint StreamColorBits(uint num_bits) { 302uint 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
376void ResultEmplaceBack(EncodingData val) { 309EncodingData 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.
387uint GetBitLength(uint n_vals, uint encoding_index) { 315uint 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
405uint GetPackedBitSize(uvec2 size, bool dual_plane, uint max_weight) { 335uint 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
414uint BitsOp(uint bits, uint start, uint end) { 344uint 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
427void DecodeQuintBlock(uint num_bits) { 349void 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
469void DecodeTritBlock(uint num_bits) { 386void 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
521void DecodeIntegerSequence(uint max_range, uint num_values) { 436void 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
543void DecodeColorValues(uvec4 modes, uint num_partitions, uint color_data_bits) { 460void 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
666uvec4 ClampByte(ivec4 color) { 584uvec4 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
673ivec4 BlueContract(int a, int r, int g, int b) { 588ivec4 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
677void ComputeEndpoints(out uvec4 ep1, out uvec4 ep2, uint color_endpoint_mode) { 592void 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
814uint UnquantizeTexelWeight(EncodingData val) { 729uint 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
886void UnquantizeTexelWeights(bool dual_plane, uvec2 size) { 796void 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
806uint 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
811uvec4 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
943int FindLayout(uint mode) { 869int FindLayout(uint mode) {
@@ -971,80 +897,96 @@ int FindLayout(uint mode) {
971 return 5; 897 return 5;
972} 898}
973 899
974TexelWeightParams DecodeBlockInfo() { 900
975 TexelWeightParams params = TexelWeightParams(uvec2(0), 0, false, false, false, false); 901void 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
909void 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
926bool 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
946uvec2 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
988uint 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
1065void 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
1073void 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
1090void DecompressBlock(ivec3 coord) { 1003void 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
1161uint 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
1245void main() { 1168void 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
6layout (push_constant) uniform PushConstants {
7 vec4 clear_depth;
8};
9
10void main() {
11 gl_FragDepth = clear_depth.x;
12}
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
598void 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
596void BlitImageHelper::Convert(VkPipeline pipeline, const Framebuffer* dst_framebuffer, 620void 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
847VkPipeline 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
823void BlitImageHelper::ConvertPipeline(vk::Pipeline& pipeline, VkRenderPass renderpass, 902void 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
30struct 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
30class BlitImageHelper { 40class BlitImageHelper {
31public: 41public:
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
67private: 81private:
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/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
index 89aa243d2..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
442void RasterizerVulkan::DispatchCompute() { 454void RasterizerVulkan::DispatchCompute() {
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);