diff --git a/README.md b/README.md index c2fabd262..154abe224 100755 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ yuzu emulator early access ============= -This is the source code for early-access 4087. +This is the source code for early-access 4088. ## Legal Notice diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt index c408485c6..55abba093 100755 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt @@ -303,6 +303,11 @@ object NativeLibrary { */ external fun getCpuBackend(): String + /** + * Returns the current GPU Driver. + */ + external fun getGpuDriver(): String + external fun applySettings() external fun logSettings() @@ -614,6 +619,11 @@ object NativeLibrary { */ external fun clearFilesystemProvider() + /** + * Checks if all necessary keys are present for decryption + */ + external fun areKeysPresent(): Boolean + /** * Button type for use in onTouchEvent */ diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AbstractDiffAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AbstractDiffAdapter.kt index f006f9e3d..0ab1b46c3 100755 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AbstractDiffAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AbstractDiffAdapter.kt @@ -14,15 +14,20 @@ import androidx.recyclerview.widget.RecyclerView * Generic adapter that implements an [AsyncDifferConfig] and covers some of the basic boilerplate * code used in every [RecyclerView]. * Type assigned to [Model] must inherit from [Object] in order to be compared properly. + * @param exact Decides whether each item will be compared by reference or by their contents */ -abstract class AbstractDiffAdapter> : - ListAdapter(AsyncDifferConfig.Builder(DiffCallback()).build()) { +abstract class AbstractDiffAdapter>( + exact: Boolean = true +) : ListAdapter(AsyncDifferConfig.Builder(DiffCallback(exact)).build()) { override fun onBindViewHolder(holder: Holder, position: Int) = holder.bind(currentList[position]) - private class DiffCallback : DiffUtil.ItemCallback() { + private class DiffCallback(val exact: Boolean) : DiffUtil.ItemCallback() { override fun areItemsTheSame(oldItem: Model & Any, newItem: Model & Any): Boolean { - return oldItem === newItem + if (exact) { + return oldItem === newItem + } + return oldItem == newItem } @SuppressLint("DiffUtilEquals") diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt index b4f4d950f..85c8249e6 100755 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt @@ -30,7 +30,7 @@ import org.yuzu.yuzu_emu.utils.GameIconUtils import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder class GameAdapter(private val activity: AppCompatActivity) : - AbstractDiffAdapter() { + AbstractDiffAdapter(exact = false) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GameViewHolder { CardGameBinding.inflate(LayoutInflater.from(parent.context), parent, false) .also { return GameViewHolder(it) } 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 2a97ae14d..d17e087fe 100755 --- 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 @@ -38,7 +38,6 @@ import androidx.window.layout.WindowLayoutInfo import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.slider.Slider import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch import org.yuzu.yuzu_emu.HomeNavigationDirections @@ -141,7 +140,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { // So this fragment doesn't restart on configuration changes; i.e. rotation. retainInstance = true - emulationState = EmulationState(game.path) + emulationState = EmulationState(game.path) { + return@EmulationState driverViewModel.isInteractionAllowed.value + } } /** @@ -370,6 +371,15 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { } } } + launch { + repeatOnLifecycle(Lifecycle.State.RESUMED) { + driverViewModel.isInteractionAllowed.collect { + if (it) { + startEmulation() + } + } + } + } launch { repeatOnLifecycle(Lifecycle.State.CREATED) { emulationViewModel.emulationStarted.collectLatest { @@ -398,19 +408,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { } } } - launch { - repeatOnLifecycle(Lifecycle.State.RESUMED) { - driverViewModel.isInteractionAllowed.collect { - if (it) { - onEmulationStart() - } - } - } - } } } - private fun onEmulationStart() { + private fun startEmulation() { if (!NativeLibrary.isRunning() && !NativeLibrary.isPaused()) { if (!DirectoryInitialization.areDirectoriesReady) { DirectoryInitialization.start() @@ -485,12 +486,15 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { val FRAMETIME = 2 val SPEED = 3 perfStatsUpdater = { - if (emulationViewModel.emulationStarted.value) { + if (emulationViewModel.emulationStarted.value && + !emulationViewModel.isEmulationStopping.value + ) { val perfStats = NativeLibrary.getPerfStats() val cpuBackend = NativeLibrary.getCpuBackend() + val gpuDriver = NativeLibrary.getGpuDriver() if (_binding != null) { binding.showFpsText.text = - String.format("FPS: %.1f\n%s", perfStats[FPS], cpuBackend) + String.format("FPS: %.1f\n%s/%s", perfStats[FPS], cpuBackend, gpuDriver) } perfStatsUpdateHandler.postDelayed(perfStatsUpdater!!, 800) } @@ -807,7 +811,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { } } - private class EmulationState(private val gamePath: String) { + private class EmulationState( + private val gamePath: String, + private val emulationCanStart: () -> Boolean + ) { private var state: State private var surface: Surface? = null @@ -901,6 +908,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { State.PAUSED -> Log.warning( "[EmulationFragment] Surface cleared while emulation paused." ) + else -> Log.warning( "[EmulationFragment] Surface cleared while emulation stopped." ) @@ -910,6 +918,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { private fun runWithValidSurface() { NativeLibrary.surfaceChanged(surface) + if (!emulationCanStart.invoke()) { + return + } + when (state) { State.STOPPED -> { val emulationThread = Thread({ diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/MessageDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/MessageDialogFragment.kt index 620d8db7c..22b084b9a 100755 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/MessageDialogFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/MessageDialogFragment.kt @@ -26,9 +26,15 @@ class MessageDialogFragment : DialogFragment() { val descriptionId = requireArguments().getInt(DESCRIPTION_ID) val descriptionString = requireArguments().getString(DESCRIPTION_STRING)!! val helpLinkId = requireArguments().getInt(HELP_LINK) + val dismissible = requireArguments().getBoolean(DISMISSIBLE) + val clearPositiveAction = requireArguments().getBoolean(CLEAR_POSITIVE_ACTION) val builder = MaterialAlertDialogBuilder(requireContext()) + if (clearPositiveAction) { + messageDialogViewModel.positiveAction = null + } + if (messageDialogViewModel.positiveAction == null) { builder.setPositiveButton(R.string.close, null) } else { @@ -51,6 +57,8 @@ class MessageDialogFragment : DialogFragment() { } } + isCancelable = dismissible + return builder.show() } @@ -67,6 +75,8 @@ class MessageDialogFragment : DialogFragment() { private const val DESCRIPTION_ID = "DescriptionId" private const val DESCRIPTION_STRING = "DescriptionString" private const val HELP_LINK = "Link" + private const val DISMISSIBLE = "Dismissible" + private const val CLEAR_POSITIVE_ACTION = "ClearPositiveAction" fun newInstance( activity: FragmentActivity? = null, @@ -75,22 +85,28 @@ class MessageDialogFragment : DialogFragment() { descriptionId: Int = 0, descriptionString: String = "", helpLinkId: Int = 0, + dismissible: Boolean = true, positiveAction: (() -> Unit)? = null ): MessageDialogFragment { - val dialog = MessageDialogFragment() - val bundle = Bundle() - bundle.apply { - putInt(TITLE_ID, titleId) - putString(TITLE_STRING, titleString) - putInt(DESCRIPTION_ID, descriptionId) - putString(DESCRIPTION_STRING, descriptionString) - putInt(HELP_LINK, helpLinkId) - } + var clearPositiveAction = false if (activity != null) { ViewModelProvider(activity)[MessageDialogViewModel::class.java].apply { clear() this.positiveAction = positiveAction } + } else { + clearPositiveAction = true + } + + val dialog = MessageDialogFragment() + val bundle = Bundle().apply { + putInt(TITLE_ID, titleId) + putString(TITLE_STRING, titleString) + putInt(DESCRIPTION_ID, descriptionId) + putString(DESCRIPTION_STRING, descriptionString) + putInt(HELP_LINK, helpLinkId) + putBoolean(DISMISSIBLE, dismissible) + putBoolean(CLEAR_POSITIVE_ACTION, clearPositiveAction) } dialog.arguments = bundle return dialog diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt index 064342cdd..ebf41a639 100755 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt @@ -31,6 +31,7 @@ import androidx.preference.PreferenceManager import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback import com.google.android.material.transition.MaterialFadeThrough import kotlinx.coroutines.launch +import org.yuzu.yuzu_emu.NativeLibrary import java.io.File import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.YuzuApplication @@ -162,7 +163,7 @@ class SetupFragment : Fragment() { R.string.install_prod_keys_warning_help, { val file = File(DirectoryInitialization.userDirectory + "/keys/prod.keys") - if (file.exists()) { + if (file.exists() && NativeLibrary.areKeysPresent()) { StepState.COMPLETE } else { StepState.INCOMPLETE @@ -347,7 +348,8 @@ class SetupFragment : Fragment() { val getProdKey = registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> if (result != null) { - if (mainActivity.processKey(result)) { + mainActivity.processKey(result) + if (NativeLibrary.areKeysPresent()) { keyCallback.onStepCompleted() } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt index 15ae3a42b..5ed754c96 100755 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt @@ -144,6 +144,7 @@ class DriverViewModel : ViewModel() { val selectedDriverFile = File(StringSetting.DRIVER_PATH.getString()) val selectedDriverMetadata = GpuDriverHelper.customDriverSettingData if (GpuDriverHelper.installedCustomDriverData == selectedDriverMetadata) { + setDriverReady() return } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt index c8a4a2d17..6859b7780 100755 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt @@ -70,11 +70,19 @@ class Game( } override fun equals(other: Any?): Boolean { - if (other !is Game) { - return false - } + if (this === other) return true + if (javaClass != other?.javaClass) return false - return hashCode() == other.hashCode() + other as Game + + if (title != other.title) return false + if (path != other.path) return false + if (programId != other.programId) return false + if (developer != other.developer) return false + if (version != other.version) return false + if (isHomebrew != other.isHomebrew) return false + + return true } override fun hashCode(): Int { 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 513ac2fc5..cfc777b81 100755 --- 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 @@ -31,6 +31,9 @@ class HomeViewModel : ViewModel() { private val _reloadPropertiesList = MutableStateFlow(false) val reloadPropertiesList get() = _reloadPropertiesList.asStateFlow() + private val _checkKeys = MutableStateFlow(false) + val checkKeys = _checkKeys.asStateFlow() + var navigatedToSetup = false fun setNavigationVisibility(visible: Boolean, animated: Boolean) { @@ -66,4 +69,8 @@ class HomeViewModel : ViewModel() { fun reloadPropertiesList(reload: Boolean) { _reloadPropertiesList.value = reload } + + fun setCheckKeys(value: Boolean) { + _checkKeys.value = value + } } 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 c2cc29961..b3967d294 100755 --- 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 @@ -64,6 +64,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider { override var themeId: Int = 0 + private val CHECKED_DECRYPTION = "CheckedDecryption" + private var checkedDecryption = false + override fun onCreate(savedInstanceState: Bundle?) { val splashScreen = installSplashScreen() splashScreen.setKeepOnScreenCondition { !DirectoryInitialization.areDirectoriesReady } @@ -75,6 +78,18 @@ class MainActivity : AppCompatActivity(), ThemeProvider { binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) + if (savedInstanceState != null) { + checkedDecryption = savedInstanceState.getBoolean(CHECKED_DECRYPTION) + } + if (!checkedDecryption) { + val firstTimeSetup = PreferenceManager.getDefaultSharedPreferences(applicationContext) + .getBoolean(Settings.PREF_FIRST_APP_LAUNCH, true) + if (!firstTimeSetup) { + checkKeys() + } + checkedDecryption = true + } + WindowCompat.setDecorFitsSystemWindows(window, false) window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING) @@ -150,6 +165,16 @@ class MainActivity : AppCompatActivity(), ThemeProvider { } } } + launch { + repeatOnLifecycle(Lifecycle.State.CREATED) { + homeViewModel.checkKeys.collect { + if (it) { + checkKeys() + homeViewModel.setCheckKeys(false) + } + } + } + } } // Dismiss previous notifications (should not happen unless a crash occurred) @@ -158,6 +183,21 @@ class MainActivity : AppCompatActivity(), ThemeProvider { setInsets() } + private fun checkKeys() { + if (!NativeLibrary.areKeysPresent()) { + MessageDialogFragment.newInstance( + titleId = R.string.keys_missing, + descriptionId = R.string.keys_missing_description, + helpLinkId = R.string.keys_missing_help + ).show(supportFragmentManager, MessageDialogFragment.TAG) + } + } + + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + outState.putBoolean(CHECKED_DECRYPTION, checkedDecryption) + } + fun finishSetup(navController: NavController) { navController.navigate(R.id.action_firstTimeSetupFragment_to_gamesFragment) (binding.navigationView as NavigationBarView).setupWithNavController(navController) @@ -349,6 +389,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider { R.string.install_keys_success, Toast.LENGTH_SHORT ).show() + homeViewModel.setCheckKeys(true) gamesViewModel.reloadGames(true) return true } else { @@ -399,6 +440,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider { firmwarePath.deleteRecursively() cacheFirmwareDir.copyRecursively(firmwarePath, true) NativeLibrary.initializeSystem(true) + homeViewModel.setCheckKeys(true) getString(R.string.save_file_imported_success) } } catch (e: Exception) { diff --git a/src/android/app/src/main/jni/game_metadata.cpp b/src/android/app/src/main/jni/game_metadata.cpp index 78f604c70..8f0da1413 100755 --- a/src/android/app/src/main/jni/game_metadata.cpp +++ b/src/android/app/src/main/jni/game_metadata.cpp @@ -1,12 +1,12 @@ // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include -#include -#include -#include -#include +#include "core/core.h" +#include "core/file_sys/fs_filesystem.h" +#include "core/file_sys/patch_manager.h" #include "core/loader/loader.h" +#include "core/loader/nro.h" +#include "jni.h" #include "jni/android_common/android_common.h" #include "native.h" @@ -79,7 +79,7 @@ extern "C" { jboolean Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getIsValid(JNIEnv* env, jobject obj, jstring jpath) { const auto file = EmulationSession::GetInstance().System().GetFilesystem()->OpenFile( - GetJString(env, jpath), FileSys::Mode::Read); + GetJString(env, jpath), FileSys::OpenMode::Read); if (!file) { return false; } diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index 963f57380..3fd9a500c 100755 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -35,9 +35,10 @@ #include "core/crypto/key_manager.h" #include "core/file_sys/card_image.h" #include "core/file_sys/content_archive.h" +#include "core/file_sys/fs_filesystem.h" #include "core/file_sys/submission_package.h" -#include "core/file_sys/vfs.h" -#include "core/file_sys/vfs_real.h" +#include "core/file_sys/vfs/vfs.h" +#include "core/file_sys/vfs/vfs_real.h" #include "core/frontend/applets/cabinet.h" #include "core/frontend/applets/controller.h" #include "core/frontend/applets/error.h" @@ -154,7 +155,7 @@ void EmulationSession::SurfaceChanged() { } void EmulationSession::ConfigureFilesystemProvider(const std::string& filepath) { - const auto file = m_system.GetFilesystem()->OpenFile(filepath, FileSys::Mode::Read); + const auto file = m_system.GetFilesystem()->OpenFile(filepath, FileSys::OpenMode::Read); if (!file) { return; } @@ -247,6 +248,7 @@ Core::SystemResultStatus EmulationSession::InitializeEmulation(const std::string m_system.GetCpuManager().OnGpuReady(); m_system.RegisterExitCallback([&] { HaltEmulation(); }); + OnEmulationStarted(); return Core::SystemResultStatus::Success; } @@ -463,8 +465,8 @@ int Java_org_yuzu_yuzu_1emu_NativeLibrary_installFileToNand(JNIEnv* env, jobject }; return static_cast( - ContentManager::InstallNSP(&EmulationSession::GetInstance().System(), - EmulationSession::GetInstance().System().GetFilesystem().get(), + ContentManager::InstallNSP(EmulationSession::GetInstance().System(), + *EmulationSession::GetInstance().System().GetFilesystem(), GetJString(env, j_file), callback)); } @@ -474,8 +476,8 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_doesUpdateMatchProgram(JNIEnv* en u64 program_id = EmulationSession::GetProgramId(env, jprogramId); std::string updatePath = GetJString(env, jupdatePath); std::shared_ptr nsp = std::make_shared( - EmulationSession::GetInstance().System().GetFilesystem()->OpenFile(updatePath, - FileSys::Mode::Read)); + EmulationSession::GetInstance().System().GetFilesystem()->OpenFile( + updatePath, FileSys::OpenMode::Read)); for (const auto& item : nsp->GetNCAs()) { for (const auto& nca_details : item.second) { if (nca_details.second->GetName().ends_with(".cnmt.nca")) { @@ -674,6 +676,11 @@ jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getCpuBackend(JNIEnv* env, jclass return ToJString(env, "JIT"); } +jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getGpuDriver(JNIEnv* env, jobject jobj) { + return ToJString(env, + EmulationSession::GetInstance().System().GPU().Renderer().GetDeviceVendor()); +} + void Java_org_yuzu_yuzu_1emu_NativeLibrary_applySettings(JNIEnv* env, jobject jobj) { EmulationSession::GetInstance().System().ApplySettings(); } @@ -713,7 +720,7 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeEmptyUserDirectory(JNIEnv* jobject instance) { const auto nand_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir); auto vfs_nand_dir = EmulationSession::GetInstance().System().GetFilesystem()->OpenDirectory( - Common::FS::PathToUTF8String(nand_dir), FileSys::Mode::Read); + Common::FS::PathToUTF8String(nand_dir), FileSys::OpenMode::Read); const auto user_id = EmulationSession::GetInstance().System().GetProfileManager().GetUser( static_cast(0)); @@ -819,7 +826,7 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_removeUpdate(JNIEnv* env, jobject job void Java_org_yuzu_yuzu_1emu_NativeLibrary_removeDLC(JNIEnv* env, jobject jobj, jstring jprogramId) { auto program_id = EmulationSession::GetProgramId(env, jprogramId); - ContentManager::RemoveAllDLC(&EmulationSession::GetInstance().System(), program_id); + ContentManager::RemoveAllDLC(EmulationSession::GetInstance().System(), program_id); } void Java_org_yuzu_yuzu_1emu_NativeLibrary_removeMod(JNIEnv* env, jobject jobj, jstring jprogramId, @@ -829,8 +836,9 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_removeMod(JNIEnv* env, jobject jobj, program_id, GetJString(env, jname)); } -jobject Java_org_yuzu_yuzu_1emu_NativeLibrary_verifyInstalledContents(JNIEnv* env, jobject jobj, - jobject jcallback) { +jobjectArray Java_org_yuzu_yuzu_1emu_NativeLibrary_verifyInstalledContents(JNIEnv* env, + jobject jobj, + jobject jcallback) { auto jlambdaClass = env->GetObjectClass(jcallback); auto jlambdaInvokeMethod = env->GetMethodID( jlambdaClass, "invoke", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); @@ -842,7 +850,7 @@ jobject Java_org_yuzu_yuzu_1emu_NativeLibrary_verifyInstalledContents(JNIEnv* en auto& session = EmulationSession::GetInstance(); std::vector result = ContentManager::VerifyInstalledContents( - &session.System(), session.GetContentProvider(), callback); + session.System(), *session.GetContentProvider(), callback); jobjectArray jresult = env->NewObjectArray(result.size(), IDCache::GetStringClass(), ToJString(env, "")); for (size_t i = 0; i < result.size(); ++i) { @@ -863,7 +871,7 @@ jint Java_org_yuzu_yuzu_1emu_NativeLibrary_verifyGameContents(JNIEnv* env, jobje }; auto& session = EmulationSession::GetInstance(); return static_cast( - ContentManager::VerifyGameContents(&session.System(), GetJString(env, jpath), callback)); + ContentManager::VerifyGameContents(session.System(), GetJString(env, jpath), callback)); } jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getSavePath(JNIEnv* env, jobject jobj, @@ -882,7 +890,7 @@ jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getSavePath(JNIEnv* env, jobject j const auto nandDir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir); auto vfsNandDir = system.GetFilesystem()->OpenDirectory(Common::FS::PathToUTF8String(nandDir), - FileSys::Mode::Read); + FileSys::OpenMode::Read); const auto user_save_data_path = FileSys::SaveDataFactory::GetFullPath( {}, vfsNandDir, FileSys::SaveDataSpaceId::NandUser, FileSys::SaveDataType::SaveData, @@ -912,4 +920,10 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_clearFilesystemProvider(JNIEnv* env, EmulationSession::GetInstance().GetContentProvider()->ClearAllEntries(); } +jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_areKeysPresent(JNIEnv* env, jobject jobj) { + auto& system = EmulationSession::GetInstance().System(); + system.GetFileSystemController().CreateFactories(*system.GetFilesystem()); + return ContentManager::AreKeysPresent(); +} + } // extern "C" diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index 779eb36a8..3cd1586fd 100755 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -144,6 +144,9 @@ No save data found Verify installed content Checks all installed content for corruption + Encryption keys are missing + Firmware and retail games cannot be decrypted + https://yuzu-emu.org/help/quickstart/#dumping-decryption-keys Applet launcher diff --git a/src/common/overflow.h b/src/common/overflow.h index 44d8e7e73..e184ead95 100755 --- a/src/common/overflow.h +++ b/src/common/overflow.h @@ -3,6 +3,7 @@ #pragma once +#include #include #include "bit_cast.h" @@ -19,4 +20,21 @@ inline T WrappingAdd(T lhs, T rhs) { return BitCast(lhs_u + rhs_u); } +template + requires(std::is_integral_v && std::is_signed_v) +inline bool CanAddWithoutOverflow(T lhs, T rhs) { +#ifdef _MSC_VER + if (lhs >= 0 && rhs >= 0) { + return WrappingAdd(lhs, rhs) >= std::max(lhs, rhs); + } else if (lhs < 0 && rhs < 0) { + return WrappingAdd(lhs, rhs) <= std::min(lhs, rhs); + } else { + return true; + } +#else + T res; + return !__builtin_add_overflow(lhs, rhs, &res); +#endif +} + } // namespace Common diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 2cebedb02..ddd3c9704 100755 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -20,28 +20,49 @@ add_library(core STATIC cpu_manager.h crypto/aes_util.cpp crypto/aes_util.h + crypto/ctr_encryption_layer.cpp + crypto/ctr_encryption_layer.h crypto/encryption_layer.cpp crypto/encryption_layer.h crypto/key_manager.cpp crypto/key_manager.h crypto/partition_data_manager.cpp crypto/partition_data_manager.h - crypto/ctr_encryption_layer.cpp - crypto/ctr_encryption_layer.h crypto/xts_encryption_layer.cpp crypto/xts_encryption_layer.h - debugger/debugger_interface.h debugger/debugger.cpp debugger/debugger.h - debugger/gdbstub_arch.cpp - debugger/gdbstub_arch.h + debugger/debugger_interface.h debugger/gdbstub.cpp debugger/gdbstub.h + debugger/gdbstub_arch.cpp + debugger/gdbstub_arch.h device_memory_manager.h device_memory_manager.inc device_memory.cpp device_memory.h + file_sys/bis_factory.cpp + file_sys/bis_factory.h + file_sys/card_image.cpp + file_sys/card_image.h + file_sys/common_funcs.h + file_sys/content_archive.cpp + file_sys/content_archive.h + file_sys/control_metadata.cpp + file_sys/control_metadata.h + file_sys/errors.h + file_sys/fs_directory.h + file_sys/fs_file.h + file_sys/fs_filesystem.h + file_sys/fs_memory_management.h + file_sys/fs_operate_range.h + file_sys/fs_path.h + file_sys/fs_path_utility.h + file_sys/fs_string_util.h + file_sys/fsmitm_romfsbuild.cpp + file_sys/fsmitm_romfsbuild.h file_sys/fssystem/fs_i_storage.h + file_sys/fssystem/fs_types.h file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.cpp file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.h file_sys/fssystem/fssystem_aes_ctr_storage.cpp @@ -83,25 +104,10 @@ add_library(core STATIC file_sys/fssystem/fssystem_switch_storage.h file_sys/fssystem/fssystem_utility.cpp file_sys/fssystem/fssystem_utility.h - file_sys/fssystem/fs_types.h - file_sys/bis_factory.cpp - file_sys/bis_factory.h - file_sys/card_image.cpp - file_sys/card_image.h - file_sys/common_funcs.h - file_sys/content_archive.cpp - file_sys/content_archive.h - file_sys/control_metadata.cpp - file_sys/control_metadata.h - file_sys/directory.h - file_sys/errors.h - file_sys/fsmitm_romfsbuild.cpp - file_sys/fsmitm_romfsbuild.h file_sys/ips_layer.cpp file_sys/ips_layer.h file_sys/kernel_executable.cpp file_sys/kernel_executable.h - file_sys/mode.h file_sys/nca_metadata.cpp file_sys/nca_metadata.h file_sys/partition_filesystem.cpp @@ -146,22 +152,22 @@ add_library(core STATIC file_sys/system_archive/system_version.h file_sys/system_archive/time_zone_binary.cpp file_sys/system_archive/time_zone_binary.h - file_sys/vfs.cpp - file_sys/vfs.h - file_sys/vfs_cached.cpp - file_sys/vfs_cached.h - file_sys/vfs_concat.cpp - file_sys/vfs_concat.h - file_sys/vfs_layered.cpp - file_sys/vfs_layered.h - file_sys/vfs_offset.cpp - file_sys/vfs_offset.h - file_sys/vfs_real.cpp - file_sys/vfs_real.h - file_sys/vfs_static.h - file_sys/vfs_types.h - file_sys/vfs_vector.cpp - file_sys/vfs_vector.h + file_sys/vfs/vfs.cpp + file_sys/vfs/vfs.h + file_sys/vfs/vfs_cached.cpp + file_sys/vfs/vfs_cached.h + file_sys/vfs/vfs_concat.cpp + file_sys/vfs/vfs_concat.h + file_sys/vfs/vfs_layered.cpp + file_sys/vfs/vfs_layered.h + file_sys/vfs/vfs_offset.cpp + file_sys/vfs/vfs_offset.h + file_sys/vfs/vfs_real.cpp + file_sys/vfs/vfs_real.h + file_sys/vfs/vfs_static.h + file_sys/vfs/vfs_types.h + file_sys/vfs/vfs_vector.cpp + file_sys/vfs/vfs_vector.h file_sys/xts_archive.cpp file_sys/xts_archive.h frontend/applets/cabinet.cpp @@ -194,7 +200,6 @@ add_library(core STATIC hle/kernel/board/nintendo/nx/secure_monitor.h hle/kernel/code_set.cpp hle/kernel/code_set.h - hle/kernel/svc_results.h hle/kernel/global_scheduler_context.cpp hle/kernel/global_scheduler_context.h hle/kernel/init/init_slab_setup.cpp @@ -204,11 +209,11 @@ add_library(core STATIC hle/kernel/k_address_arbiter.h hle/kernel/k_address_space_info.cpp hle/kernel/k_address_space_info.h + hle/kernel/k_affinity_mask.h hle/kernel/k_auto_object.cpp hle/kernel/k_auto_object.h hle/kernel/k_auto_object_container.cpp hle/kernel/k_auto_object_container.h - hle/kernel/k_affinity_mask.h hle/kernel/k_capabilities.cpp hle/kernel/k_capabilities.h hle/kernel/k_class_token.cpp @@ -232,9 +237,9 @@ add_library(core STATIC hle/kernel/k_event_info.h hle/kernel/k_handle_table.cpp hle/kernel/k_handle_table.h - hle/kernel/k_hardware_timer_base.h hle/kernel/k_hardware_timer.cpp hle/kernel/k_hardware_timer.h + hle/kernel/k_hardware_timer_base.h hle/kernel/k_interrupt_manager.cpp hle/kernel/k_interrupt_manager.h hle/kernel/k_light_client_session.cpp @@ -261,10 +266,10 @@ add_library(core STATIC hle/kernel/k_page_bitmap.h hle/kernel/k_page_buffer.cpp hle/kernel/k_page_buffer.h - hle/kernel/k_page_heap.cpp - hle/kernel/k_page_heap.h hle/kernel/k_page_group.cpp hle/kernel/k_page_group.h + hle/kernel/k_page_heap.cpp + hle/kernel/k_page_heap.h hle/kernel/k_page_table.h hle/kernel/k_page_table_base.cpp hle/kernel/k_page_table_base.h @@ -329,8 +334,6 @@ add_library(core STATIC hle/kernel/slab_helpers.h hle/kernel/svc.cpp hle/kernel/svc.h - hle/kernel/svc_common.h - hle/kernel/svc_types.h hle/kernel/svc/svc_activity.cpp hle/kernel/svc/svc_address_arbiter.cpp hle/kernel/svc/svc_address_translation.cpp @@ -368,6 +371,9 @@ add_library(core STATIC hle/kernel/svc/svc_thread_profiler.cpp hle/kernel/svc/svc_tick.cpp hle/kernel/svc/svc_transfer_memory.cpp + hle/kernel/svc_common.h + hle/kernel/svc_results.h + hle/kernel/svc_types.h hle/result.h hle/service/acc/acc.cpp hle/service/acc/acc.h @@ -472,6 +478,8 @@ add_library(core STATIC hle/service/caps/caps_types.h hle/service/caps/caps_u.cpp hle/service/caps/caps_u.h + hle/service/cmif_serialization.h + hle/service/cmif_types.h hle/service/erpt/erpt.cpp hle/service/erpt/erpt.h hle/service/es/es.cpp @@ -484,14 +492,25 @@ add_library(core STATIC hle/service/fatal/fatal_p.h hle/service/fatal/fatal_u.cpp hle/service/fatal/fatal_u.h + hle/service/fgm/fgm.cpp + hle/service/fgm/fgm.h hle/service/filesystem/filesystem.cpp hle/service/filesystem/filesystem.h - hle/service/filesystem/fsp_ldr.cpp - hle/service/filesystem/fsp_ldr.h - hle/service/filesystem/fsp_pr.cpp - hle/service/filesystem/fsp_pr.h - hle/service/filesystem/fsp_srv.cpp - hle/service/filesystem/fsp_srv.h + hle/service/filesystem/fsp/fs_i_directory.cpp + hle/service/filesystem/fsp/fs_i_directory.h + hle/service/filesystem/fsp/fs_i_file.cpp + hle/service/filesystem/fsp/fs_i_file.h + hle/service/filesystem/fsp/fs_i_filesystem.cpp + hle/service/filesystem/fsp/fs_i_filesystem.h + hle/service/filesystem/fsp/fs_i_storage.cpp + hle/service/filesystem/fsp/fs_i_storage.h + hle/service/filesystem/fsp/fsp_ldr.cpp + hle/service/filesystem/fsp/fsp_ldr.h + hle/service/filesystem/fsp/fsp_pr.cpp + hle/service/filesystem/fsp/fsp_pr.h + hle/service/filesystem/fsp/fsp_srv.cpp + hle/service/filesystem/fsp/fsp_srv.h + hle/service/filesystem/fsp/fsp_util.h hle/service/filesystem/romfs_controller.cpp hle/service/filesystem/romfs_controller.h hle/service/filesystem/save_data_controller.cpp @@ -549,13 +568,18 @@ add_library(core STATIC hle/service/hid/irs.h hle/service/hid/xcd.cpp hle/service/hid/xcd.h + hle/service/hle_ipc.cpp + hle/service/hle_ipc.h + hle/service/ipc_helpers.h + hle/service/kernel_helpers.cpp + hle/service/kernel_helpers.h hle/service/lbl/lbl.cpp hle/service/lbl/lbl.h hle/service/ldn/lan_discovery.cpp hle/service/ldn/lan_discovery.h - hle/service/ldn/ldn_results.h hle/service/ldn/ldn.cpp hle/service/ldn/ldn.h + hle/service/ldn/ldn_results.h hle/service/ldn/ldn_types.h hle/service/ldr/ldr.cpp hle/service/ldr/ldr.h @@ -563,16 +587,6 @@ add_library(core STATIC hle/service/lm/lm.h hle/service/mig/mig.cpp hle/service/mig/mig.h - hle/service/mii/types/char_info.cpp - hle/service/mii/types/char_info.h - hle/service/mii/types/core_data.cpp - hle/service/mii/types/core_data.h - hle/service/mii/types/raw_data.cpp - hle/service/mii/types/raw_data.h - hle/service/mii/types/store_data.cpp - hle/service/mii/types/store_data.h - hle/service/mii/types/ver3_store_data.cpp - hle/service/mii/types/ver3_store_data.h hle/service/mii/mii.cpp hle/service/mii/mii.h hle/service/mii/mii_database.cpp @@ -584,10 +598,22 @@ add_library(core STATIC hle/service/mii/mii_result.h hle/service/mii/mii_types.h hle/service/mii/mii_util.h + hle/service/mii/types/char_info.cpp + hle/service/mii/types/char_info.h + hle/service/mii/types/core_data.cpp + hle/service/mii/types/core_data.h + hle/service/mii/types/raw_data.cpp + hle/service/mii/types/raw_data.h + hle/service/mii/types/store_data.cpp + hle/service/mii/types/store_data.h + hle/service/mii/types/ver3_store_data.cpp + hle/service/mii/types/ver3_store_data.h hle/service/mm/mm_u.cpp hle/service/mm/mm_u.h hle/service/mnpp/mnpp_app.cpp hle/service/mnpp/mnpp_app.h + hle/service/mutex.cpp + hle/service/mutex.h hle/service/ncm/ncm.cpp hle/service/ncm/ncm.h hle/service/nfc/common/amiibo_crypto.cpp @@ -757,19 +783,12 @@ add_library(core STATIC hle/service/ptm/ptm.h hle/service/ptm/ts.cpp hle/service/ptm/ts.h - hle/service/hle_ipc.cpp - hle/service/hle_ipc.h - hle/service/ipc_helpers.h - hle/service/kernel_helpers.cpp - hle/service/kernel_helpers.h - hle/service/mutex.cpp - hle/service/mutex.h + hle/service/ro/ro.cpp + hle/service/ro/ro.h hle/service/ro/ro_nro_utils.cpp hle/service/ro/ro_nro_utils.h hle/service/ro/ro_results.h hle/service/ro/ro_types.h - hle/service/ro/ro.cpp - hle/service/ro/ro.h hle/service/server_manager.cpp hle/service/server_manager.h hle/service/service.cpp @@ -836,9 +855,9 @@ add_library(core STATIC internal_network/network.h internal_network/network_interface.cpp internal_network/network_interface.h - internal_network/sockets.h internal_network/socket_proxy.cpp internal_network/socket_proxy.h + internal_network/sockets.h loader/deconstructed_rom_directory.cpp loader/deconstructed_rom_directory.h loader/kip.cpp @@ -857,13 +876,13 @@ add_library(core STATIC loader/nsp.h loader/xci.cpp loader/xci.h + memory.cpp + memory.h memory/cheat_engine.cpp memory/cheat_engine.h memory/dmnt_cheat_types.h memory/dmnt_cheat_vm.cpp memory/dmnt_cheat_vm.h - memory.cpp - memory.h perf_stats.cpp perf_stats.h precompiled_headers.h diff --git a/src/core/core.cpp b/src/core/core.cpp index e4b877229..ae6003629 100755 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -21,13 +21,13 @@ #include "core/debugger/debugger.h" #include "core/device_memory.h" #include "core/file_sys/bis_factory.h" -#include "core/file_sys/mode.h" +#include "core/file_sys/fs_filesystem.h" #include "core/file_sys/patch_manager.h" #include "core/file_sys/registered_cache.h" #include "core/file_sys/romfs_factory.h" #include "core/file_sys/savedata_factory.h" -#include "core/file_sys/vfs_concat.h" -#include "core/file_sys/vfs_real.h" +#include "core/file_sys/vfs/vfs_concat.h" +#include "core/file_sys/vfs/vfs_real.h" #include "core/gpu_dirty_memory_manager.h" #include "core/hle/kernel/k_memory_manager.h" #include "core/hle/kernel/k_process.h" @@ -102,7 +102,7 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs, Common::SplitPath(path, &dir_name, &filename, nullptr); if (filename == "00") { - const auto dir = vfs->OpenDirectory(dir_name, FileSys::Mode::Read); + const auto dir = vfs->OpenDirectory(dir_name, FileSys::OpenMode::Read); std::vector concat; for (u32 i = 0; i < 0x10; ++i) { @@ -127,10 +127,10 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs, } if (Common::FS::IsDir(path)) { - return vfs->OpenFile(path + "/main", FileSys::Mode::Read); + return vfs->OpenFile(path + "/main", FileSys::OpenMode::Read); } - return vfs->OpenFile(path, FileSys::Mode::Read); + return vfs->OpenFile(path, FileSys::OpenMode::Read); } struct System::Impl { diff --git a/src/core/core.h b/src/core/core.h index 6634098d4..ad5ac97e5 100755 --- a/src/core/core.h +++ b/src/core/core.h @@ -13,7 +13,7 @@ #include #include "common/common_types.h" -#include "core/file_sys/vfs_types.h" +#include "core/file_sys/vfs/vfs_types.h" namespace Core::Frontend { class EmuWindow; diff --git a/src/core/crypto/aes_util.h b/src/core/crypto/aes_util.h index eaab2f502..cf4cab1f1 100755 --- a/src/core/crypto/aes_util.h +++ b/src/core/crypto/aes_util.h @@ -7,7 +7,7 @@ #include #include #include "common/common_types.h" -#include "core/file_sys/vfs.h" +#include "core/file_sys/vfs/vfs.h" namespace Core::Crypto { diff --git a/src/core/crypto/encryption_layer.h b/src/core/crypto/encryption_layer.h index 2c46e37ea..dc4ec5e50 100755 --- a/src/core/crypto/encryption_layer.h +++ b/src/core/crypto/encryption_layer.h @@ -4,7 +4,7 @@ #pragma once #include "common/common_types.h" -#include "core/file_sys/vfs.h" +#include "core/file_sys/vfs/vfs.h" namespace Core::Crypto { diff --git a/src/core/crypto/partition_data_manager.cpp b/src/core/crypto/partition_data_manager.cpp index 89d2f6675..71512fb92 100755 --- a/src/core/crypto/partition_data_manager.cpp +++ b/src/core/crypto/partition_data_manager.cpp @@ -21,9 +21,9 @@ #include "core/crypto/partition_data_manager.h" #include "core/crypto/xts_encryption_layer.h" #include "core/file_sys/kernel_executable.h" -#include "core/file_sys/vfs.h" -#include "core/file_sys/vfs_offset.h" -#include "core/file_sys/vfs_vector.h" +#include "core/file_sys/vfs/vfs.h" +#include "core/file_sys/vfs/vfs_offset.h" +#include "core/file_sys/vfs/vfs_vector.h" #include "core/loader/loader.h" using Common::AsArray; diff --git a/src/core/crypto/partition_data_manager.h b/src/core/crypto/partition_data_manager.h index 75bf8fbf2..94445b658 100755 --- a/src/core/crypto/partition_data_manager.h +++ b/src/core/crypto/partition_data_manager.h @@ -5,7 +5,7 @@ #include #include "common/common_types.h" -#include "core/file_sys/vfs_types.h" +#include "core/file_sys/vfs/vfs_types.h" namespace Core::Crypto { diff --git a/src/core/file_sys/bis_factory.cpp b/src/core/file_sys/bis_factory.cpp index 82d9171b3..807c29c61 100755 --- a/src/core/file_sys/bis_factory.cpp +++ b/src/core/file_sys/bis_factory.cpp @@ -4,9 +4,8 @@ #include #include "common/fs/path_util.h" #include "core/file_sys/bis_factory.h" -#include "core/file_sys/mode.h" #include "core/file_sys/registered_cache.h" -#include "core/file_sys/vfs.h" +#include "core/file_sys/vfs/vfs.h" namespace FileSys { @@ -84,7 +83,7 @@ VirtualFile BISFactory::OpenPartitionStorage(BisPartitionId id, VirtualFilesystem file_system) const { auto& keys = Core::Crypto::KeyManager::Instance(); Core::Crypto::PartitionDataManager pdm{file_system->OpenDirectory( - Common::FS::GetYuzuPathString(Common::FS::YuzuPath::NANDDir), Mode::Read)}; + Common::FS::GetYuzuPathString(Common::FS::YuzuPath::NANDDir), OpenMode::Read)}; keys.PopulateFromPartitionData(pdm); switch (id) { diff --git a/src/core/file_sys/bis_factory.h b/src/core/file_sys/bis_factory.h index 6e8ea64c5..a6c412cce 100755 --- a/src/core/file_sys/bis_factory.h +++ b/src/core/file_sys/bis_factory.h @@ -6,7 +6,7 @@ #include #include "common/common_types.h" -#include "core/file_sys/vfs_types.h" +#include "core/file_sys/vfs/vfs_types.h" namespace FileSys { diff --git a/src/core/file_sys/card_image.cpp b/src/core/file_sys/card_image.cpp index 248002c7e..e6ed9a9d3 100755 --- a/src/core/file_sys/card_image.cpp +++ b/src/core/file_sys/card_image.cpp @@ -13,8 +13,8 @@ #include "core/file_sys/nca_metadata.h" #include "core/file_sys/partition_filesystem.h" #include "core/file_sys/submission_package.h" -#include "core/file_sys/vfs_offset.h" -#include "core/file_sys/vfs_vector.h" +#include "core/file_sys/vfs/vfs_offset.h" +#include "core/file_sys/vfs/vfs_vector.h" #include "core/loader/loader.h" namespace FileSys { diff --git a/src/core/file_sys/card_image.h b/src/core/file_sys/card_image.h index 2bc6749f7..f4b90cedb 100755 --- a/src/core/file_sys/card_image.h +++ b/src/core/file_sys/card_image.h @@ -8,7 +8,7 @@ #include #include "common/common_types.h" #include "common/swap.h" -#include "core/file_sys/vfs.h" +#include "core/file_sys/vfs/vfs.h" namespace Core::Crypto { class KeyManager; diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp index e7aa8cf87..7e543576e 100755 --- a/src/core/file_sys/content_archive.cpp +++ b/src/core/file_sys/content_archive.cpp @@ -13,7 +13,7 @@ #include "core/crypto/key_manager.h" #include "core/file_sys/content_archive.h" #include "core/file_sys/partition_filesystem.h" -#include "core/file_sys/vfs_offset.h" +#include "core/file_sys/vfs/vfs_offset.h" #include "core/loader/loader.h" #include "core/file_sys/fssystem/fssystem_compression_configuration.h" diff --git a/src/core/file_sys/content_archive.h b/src/core/file_sys/content_archive.h index 430505124..8cc82ccb8 100755 --- a/src/core/file_sys/content_archive.h +++ b/src/core/file_sys/content_archive.h @@ -13,7 +13,7 @@ #include "common/common_types.h" #include "common/swap.h" #include "core/crypto/key_manager.h" -#include "core/file_sys/vfs.h" +#include "core/file_sys/vfs/vfs.h" namespace Loader { enum class ResultStatus : u16; diff --git a/src/core/file_sys/control_metadata.cpp b/src/core/file_sys/control_metadata.cpp index a9b1325a2..eab578ff1 100755 --- a/src/core/file_sys/control_metadata.cpp +++ b/src/core/file_sys/control_metadata.cpp @@ -5,7 +5,7 @@ #include "common/string_util.h" #include "common/swap.h" #include "core/file_sys/control_metadata.h" -#include "core/file_sys/vfs.h" +#include "core/file_sys/vfs/vfs.h" namespace FileSys { diff --git a/src/core/file_sys/control_metadata.h b/src/core/file_sys/control_metadata.h index 43502df6d..c9e6adb42 100755 --- a/src/core/file_sys/control_metadata.h +++ b/src/core/file_sys/control_metadata.h @@ -8,7 +8,7 @@ #include "common/common_funcs.h" #include "common/common_types.h" #include "common/swap.h" -#include "core/file_sys/vfs_types.h" +#include "core/file_sys/vfs/vfs_types.h" namespace FileSys { diff --git a/src/core/file_sys/errors.h b/src/core/file_sys/errors.h index 3ae86fe4b..50f65cc5f 100755 --- a/src/core/file_sys/errors.h +++ b/src/core/file_sys/errors.h @@ -7,18 +7,13 @@ namespace FileSys { -constexpr Result ERROR_PATH_NOT_FOUND{ErrorModule::FS, 1}; -constexpr Result ERROR_PATH_ALREADY_EXISTS{ErrorModule::FS, 2}; -constexpr Result ERROR_ENTITY_NOT_FOUND{ErrorModule::FS, 1002}; -constexpr Result ERROR_SD_CARD_NOT_FOUND{ErrorModule::FS, 2001}; -constexpr Result ERROR_OUT_OF_BOUNDS{ErrorModule::FS, 3005}; -constexpr Result ERROR_FAILED_MOUNT_ARCHIVE{ErrorModule::FS, 3223}; -constexpr Result ERROR_INVALID_ARGUMENT{ErrorModule::FS, 6001}; -constexpr Result ERROR_INVALID_OFFSET{ErrorModule::FS, 6061}; -constexpr Result ERROR_INVALID_SIZE{ErrorModule::FS, 6062}; - +constexpr Result ResultPathNotFound{ErrorModule::FS, 1}; +constexpr Result ResultPathAlreadyExists{ErrorModule::FS, 2}; constexpr Result ResultUnsupportedSdkVersion{ErrorModule::FS, 50}; constexpr Result ResultPartitionNotFound{ErrorModule::FS, 1001}; +constexpr Result ResultTargetNotFound{ErrorModule::FS, 1002}; +constexpr Result ResultPortSdCardNoDevice{ErrorModule::FS, 2001}; +constexpr Result ResultNotImplemented{ErrorModule::FS, 3001}; constexpr Result ResultUnsupportedVersion{ErrorModule::FS, 3002}; constexpr Result ResultOutOfRange{ErrorModule::FS, 3005}; constexpr Result ResultAllocationMemoryFailedInFileSystemBuddyHeapA{ErrorModule::FS, 3294}; @@ -78,10 +73,21 @@ constexpr Result ResultUnexpectedInCompressedStorageA{ErrorModule::FS, 5324}; constexpr Result ResultUnexpectedInCompressedStorageB{ErrorModule::FS, 5325}; constexpr Result ResultUnexpectedInCompressedStorageC{ErrorModule::FS, 5326}; constexpr Result ResultUnexpectedInCompressedStorageD{ErrorModule::FS, 5327}; +constexpr Result ResultUnexpectedInPathA{ErrorModule::FS, 5328}; constexpr Result ResultInvalidArgument{ErrorModule::FS, 6001}; +constexpr Result ResultInvalidPath{ErrorModule::FS, 6002}; +constexpr Result ResultTooLongPath{ErrorModule::FS, 6003}; +constexpr Result ResultInvalidCharacter{ErrorModule::FS, 6004}; +constexpr Result ResultInvalidPathFormat{ErrorModule::FS, 6005}; +constexpr Result ResultDirectoryUnobtainable{ErrorModule::FS, 6006}; +constexpr Result ResultNotNormalized{ErrorModule::FS, 6007}; constexpr Result ResultInvalidOffset{ErrorModule::FS, 6061}; constexpr Result ResultInvalidSize{ErrorModule::FS, 6062}; constexpr Result ResultNullptrArgument{ErrorModule::FS, 6063}; +constexpr Result ResultInvalidOpenMode{ErrorModule::FS, 6072}; +constexpr Result ResultFileExtensionWithoutOpenModeAllowAppend{ErrorModule::FS, 6201}; +constexpr Result ResultReadNotPermitted{ErrorModule::FS, 6202}; +constexpr Result ResultWriteNotPermitted{ErrorModule::FS, 6203}; constexpr Result ResultUnsupportedSetSizeForIndirectStorage{ErrorModule::FS, 6325}; constexpr Result ResultUnsupportedWriteForCompressedStorage{ErrorModule::FS, 6387}; constexpr Result ResultUnsupportedOperateRangeForCompressedStorage{ErrorModule::FS, 6388}; diff --git a/src/core/file_sys/fs_directory.h b/src/core/file_sys/fs_directory.h new file mode 100755 index 000000000..25c9cb18a --- /dev/null +++ b/src/core/file_sys/fs_directory.h @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +namespace FileSys { + +constexpr inline size_t EntryNameLengthMax = 0x300; + +struct DirectoryEntry { + DirectoryEntry(std::string_view view, s8 entry_type, u64 entry_size) + : type{entry_type}, file_size{static_cast(entry_size)} { + const std::size_t copy_size = view.copy(name, std::size(name) - 1); + name[copy_size] = '\0'; + } + + char name[EntryNameLengthMax + 1]; + INSERT_PADDING_BYTES(3); + s8 type; + INSERT_PADDING_BYTES(3); + s64 file_size; +}; + +static_assert(sizeof(DirectoryEntry) == 0x310, + "Directory Entry struct isn't exactly 0x310 bytes long!"); +static_assert(offsetof(DirectoryEntry, type) == 0x304, "Wrong offset for type in Entry."); +static_assert(offsetof(DirectoryEntry, file_size) == 0x308, "Wrong offset for file_size in Entry."); + +struct DirectoryHandle { + void* handle; +}; + +} // namespace FileSys diff --git a/src/core/file_sys/fs_file.h b/src/core/file_sys/fs_file.h new file mode 100755 index 000000000..4fb77e8db --- /dev/null +++ b/src/core/file_sys/fs_file.h @@ -0,0 +1,65 @@ +// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/common_types.h" + +namespace FileSys { + +struct ReadOption { + u32 value; + + static const ReadOption None; +}; + +enum ReadOptionFlag : u32 { + ReadOptionFlag_None = (0 << 0), +}; + +inline constexpr const ReadOption ReadOption::None = {ReadOptionFlag_None}; + +inline constexpr bool operator==(const ReadOption& lhs, const ReadOption& rhs) { + return lhs.value == rhs.value; +} + +inline constexpr bool operator!=(const ReadOption& lhs, const ReadOption& rhs) { + return !(lhs == rhs); +} + +static_assert(sizeof(ReadOption) == sizeof(u32)); + +enum WriteOptionFlag : u32 { + WriteOptionFlag_None = (0 << 0), + WriteOptionFlag_Flush = (1 << 0), +}; + +struct WriteOption { + u32 value; + + constexpr inline bool HasFlushFlag() const { + return value & WriteOptionFlag_Flush; + } + + static const WriteOption None; + static const WriteOption Flush; +}; + +inline constexpr const WriteOption WriteOption::None = {WriteOptionFlag_None}; +inline constexpr const WriteOption WriteOption::Flush = {WriteOptionFlag_Flush}; + +inline constexpr bool operator==(const WriteOption& lhs, const WriteOption& rhs) { + return lhs.value == rhs.value; +} + +inline constexpr bool operator!=(const WriteOption& lhs, const WriteOption& rhs) { + return !(lhs == rhs); +} + +static_assert(sizeof(WriteOption) == sizeof(u32)); + +struct FileHandle { + void* handle; +}; + +} // namespace FileSys diff --git a/src/core/file_sys/fs_filesystem.h b/src/core/file_sys/fs_filesystem.h new file mode 100755 index 000000000..7f237b7fa --- /dev/null +++ b/src/core/file_sys/fs_filesystem.h @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/common_funcs.h" +#include "common/common_types.h" + +namespace FileSys { + +enum class OpenMode : u32 { + Read = (1 << 0), + Write = (1 << 1), + AllowAppend = (1 << 2), + + ReadWrite = (Read | Write), + All = (ReadWrite | AllowAppend), +}; +DECLARE_ENUM_FLAG_OPERATORS(OpenMode) + +enum class OpenDirectoryMode : u64 { + Directory = (1 << 0), + File = (1 << 1), + + All = (Directory | File), +}; +DECLARE_ENUM_FLAG_OPERATORS(OpenDirectoryMode) + +enum class DirectoryEntryType : u8 { + Directory = 0, + File = 1, +}; + +enum class CreateOption : u8 { + None = (0 << 0), + BigFile = (1 << 0), +}; + +} // namespace FileSys diff --git a/src/core/file_sys/fs_memory_management.h b/src/core/file_sys/fs_memory_management.h new file mode 100755 index 000000000..f03c6354b --- /dev/null +++ b/src/core/file_sys/fs_memory_management.h @@ -0,0 +1,40 @@ +// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include "common/alignment.h" + +namespace FileSys { + +constexpr size_t RequiredAlignment = alignof(u64); + +void* AllocateUnsafe(size_t size) { + // Allocate + void* const ptr = ::operator new(size, std::align_val_t{RequiredAlignment}); + + // Check alignment + ASSERT(Common::IsAligned(reinterpret_cast(ptr), RequiredAlignment)); + + // Return allocated pointer + return ptr; +} + +void DeallocateUnsafe(void* ptr, size_t size) { + // Deallocate the pointer + ::operator delete(ptr, std::align_val_t{RequiredAlignment}); +} + +void* Allocate(size_t size) { + return AllocateUnsafe(size); +} + +void Deallocate(void* ptr, size_t size) { + // If the pointer is non-null, deallocate it + if (ptr != nullptr) { + DeallocateUnsafe(ptr, size); + } +} + +} // namespace FileSys diff --git a/src/core/file_sys/fs_operate_range.h b/src/core/file_sys/fs_operate_range.h new file mode 100755 index 000000000..04ea64cc0 --- /dev/null +++ b/src/core/file_sys/fs_operate_range.h @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/common_types.h" + +namespace FileSys { + +enum class OperationId : s64 { + FillZero = 0, + DestroySignature = 1, + Invalidate = 2, + QueryRange = 3, + QueryUnpreparedRange = 4, + QueryLazyLoadCompletionRate = 5, + SetLazyLoadPriority = 6, + + ReadLazyLoadFileForciblyForDebug = 10001, +}; + +} // namespace FileSys diff --git a/src/core/file_sys/fs_path.h b/src/core/file_sys/fs_path.h new file mode 100755 index 000000000..56ba08a6a --- /dev/null +++ b/src/core/file_sys/fs_path.h @@ -0,0 +1,566 @@ +// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/alignment.h" +#include "common/common_funcs.h" +#include "core/file_sys/errors.h" +#include "core/file_sys/fs_memory_management.h" +#include "core/file_sys/fs_path_utility.h" +#include "core/file_sys/fs_string_util.h" +#include "core/hle/result.h" + +namespace FileSys { +class DirectoryPathParser; + +class Path { + YUZU_NON_COPYABLE(Path); + YUZU_NON_MOVEABLE(Path); + +private: + static constexpr const char* EmptyPath = ""; + static constexpr size_t WriteBufferAlignmentLength = 8; + +private: + friend class DirectoryPathParser; + +public: + class WriteBuffer { + YUZU_NON_COPYABLE(WriteBuffer); + + private: + char* m_buffer; + size_t m_length_and_is_normalized; + + public: + constexpr WriteBuffer() : m_buffer(nullptr), m_length_and_is_normalized(0) {} + + constexpr ~WriteBuffer() { + if (m_buffer != nullptr) { + Deallocate(m_buffer, this->GetLength()); + this->ResetBuffer(); + } + } + + constexpr WriteBuffer(WriteBuffer&& rhs) + : m_buffer(rhs.m_buffer), m_length_and_is_normalized(rhs.m_length_and_is_normalized) { + rhs.ResetBuffer(); + } + + constexpr WriteBuffer& operator=(WriteBuffer&& rhs) { + if (m_buffer != nullptr) { + Deallocate(m_buffer, this->GetLength()); + } + + m_buffer = rhs.m_buffer; + m_length_and_is_normalized = rhs.m_length_and_is_normalized; + + rhs.ResetBuffer(); + + return *this; + } + + constexpr void ResetBuffer() { + m_buffer = nullptr; + this->SetLength(0); + } + + constexpr char* Get() const { + return m_buffer; + } + + constexpr size_t GetLength() const { + return m_length_and_is_normalized >> 1; + } + + constexpr bool IsNormalized() const { + return static_cast(m_length_and_is_normalized & 1); + } + + constexpr void SetNormalized() { + m_length_and_is_normalized |= static_cast(1); + } + + constexpr void SetNotNormalized() { + m_length_and_is_normalized &= ~static_cast(1); + } + + private: + constexpr WriteBuffer(char* buffer, size_t length) + : m_buffer(buffer), m_length_and_is_normalized(0) { + this->SetLength(length); + } + + public: + static WriteBuffer Make(size_t length) { + if (void* alloc = Allocate(length); alloc != nullptr) { + return WriteBuffer(static_cast(alloc), length); + } else { + return WriteBuffer(); + } + } + + private: + constexpr void SetLength(size_t size) { + m_length_and_is_normalized = (m_length_and_is_normalized & 1) | (size << 1); + } + }; + +private: + const char* m_str; + WriteBuffer m_write_buffer; + +public: + constexpr Path() : m_str(EmptyPath), m_write_buffer() {} + + constexpr Path(const char* s) : m_str(s), m_write_buffer() { + m_write_buffer.SetNormalized(); + } + + constexpr ~Path() = default; + + constexpr Result SetShallowBuffer(const char* buffer) { + // Check pre-conditions + ASSERT(m_write_buffer.GetLength() == 0); + + // Check the buffer is valid + R_UNLESS(buffer != nullptr, ResultNullptrArgument); + + // Set buffer + this->SetReadOnlyBuffer(buffer); + + // Note that we're normalized + this->SetNormalized(); + + R_SUCCEED(); + } + + constexpr const char* GetString() const { + // Check pre-conditions + ASSERT(this->IsNormalized()); + + return m_str; + } + + constexpr size_t GetLength() const { + if (std::is_constant_evaluated()) { + return Strlen(this->GetString()); + } else { + return std::strlen(this->GetString()); + } + } + + constexpr bool IsEmpty() const { + return *m_str == '\x00'; + } + + constexpr bool IsMatchHead(const char* p, size_t len) const { + return Strncmp(this->GetString(), p, len) == 0; + } + + Result Initialize(const Path& rhs) { + // Check the other path is normalized + const bool normalized = rhs.IsNormalized(); + R_UNLESS(normalized, ResultNotNormalized); + + // Allocate buffer for our path + const auto len = rhs.GetLength(); + R_TRY(this->Preallocate(len + 1)); + + // Copy the path + const size_t copied = Strlcpy(m_write_buffer.Get(), rhs.GetString(), len + 1); + R_UNLESS(copied == len, ResultUnexpectedInPathA); + + // Set normalized + this->SetNormalized(); + R_SUCCEED(); + } + + Result Initialize(const char* path, size_t len) { + // Check the path is valid + R_UNLESS(path != nullptr, ResultNullptrArgument); + + // Initialize + R_TRY(this->InitializeImpl(path, len)); + + // Set not normalized + this->SetNotNormalized(); + + R_SUCCEED(); + } + + Result Initialize(const char* path) { + // Check the path is valid + R_UNLESS(path != nullptr, ResultNullptrArgument); + + R_RETURN(this->Initialize(path, std::strlen(path))); + } + + Result InitializeWithReplaceBackslash(const char* path) { + // Check the path is valid + R_UNLESS(path != nullptr, ResultNullptrArgument); + + // Initialize + R_TRY(this->InitializeImpl(path, std::strlen(path))); + + // Replace slashes as desired + if (const auto write_buffer_length = m_write_buffer.GetLength(); write_buffer_length > 1) { + Replace(m_write_buffer.Get(), write_buffer_length - 1, '\\', '/'); + } + + // Set not normalized + this->SetNotNormalized(); + + R_SUCCEED(); + } + + Result InitializeWithReplaceForwardSlashes(const char* path) { + // Check the path is valid + R_UNLESS(path != nullptr, ResultNullptrArgument); + + // Initialize + R_TRY(this->InitializeImpl(path, std::strlen(path))); + + // Replace slashes as desired + if (m_write_buffer.GetLength() > 1) { + if (auto* p = m_write_buffer.Get(); p[0] == '/' && p[1] == '/') { + p[0] = '\\'; + p[1] = '\\'; + } + } + + // Set not normalized + this->SetNotNormalized(); + + R_SUCCEED(); + } + + Result InitializeWithNormalization(const char* path, size_t size) { + // Check the path is valid + R_UNLESS(path != nullptr, ResultNullptrArgument); + + // Initialize + R_TRY(this->InitializeImpl(path, size)); + + // Set not normalized + this->SetNotNormalized(); + + // Perform normalization + PathFlags path_flags; + if (IsPathRelative(m_str)) { + path_flags.AllowRelativePath(); + } else if (IsWindowsPath(m_str, true)) { + path_flags.AllowWindowsPath(); + } else { + /* NOTE: In this case, Nintendo checks is normalized, then sets is normalized, then + * returns success. */ + /* This seems like a bug. */ + size_t dummy; + bool normalized; + R_TRY(PathFormatter::IsNormalized(std::addressof(normalized), std::addressof(dummy), + m_str)); + + this->SetNormalized(); + R_SUCCEED(); + } + + // Normalize + R_TRY(this->Normalize(path_flags)); + + this->SetNormalized(); + R_SUCCEED(); + } + + Result InitializeWithNormalization(const char* path) { + // Check the path is valid + R_UNLESS(path != nullptr, ResultNullptrArgument); + + R_RETURN(this->InitializeWithNormalization(path, std::strlen(path))); + } + + Result InitializeAsEmpty() { + // Clear our buffer + this->ClearBuffer(); + + // Set normalized + this->SetNormalized(); + + R_SUCCEED(); + } + + Result AppendChild(const char* child) { + // Check the path is valid + R_UNLESS(child != nullptr, ResultNullptrArgument); + + // Basic checks. If we have a path and the child is empty, we have nothing to do + const char* c = child; + if (m_str[0]) { + // Skip an early separator + if (*c == '/') { + ++c; + } + + R_SUCCEED_IF(*c == '\x00'); + } + + // If we don't have a string, we can just initialize + auto cur_len = std::strlen(m_str); + if (cur_len == 0) { + R_RETURN(this->Initialize(child)); + } + + // Remove a trailing separator + if (m_str[cur_len - 1] == '/' || m_str[cur_len - 1] == '\\') { + --cur_len; + } + + // Get the child path's length + auto child_len = std::strlen(c); + + // Reset our write buffer + WriteBuffer old_write_buffer; + if (m_write_buffer.Get() != nullptr) { + old_write_buffer = std::move(m_write_buffer); + this->ClearBuffer(); + } + + // Pre-allocate the new buffer + R_TRY(this->Preallocate(cur_len + 1 + child_len + 1)); + + // Get our write buffer + auto* dst = m_write_buffer.Get(); + if (old_write_buffer.Get() != nullptr && cur_len > 0) { + Strlcpy(dst, old_write_buffer.Get(), cur_len + 1); + } + + // Add separator + dst[cur_len] = '/'; + + // Copy the child path + const size_t copied = Strlcpy(dst + cur_len + 1, c, child_len + 1); + R_UNLESS(copied == child_len, ResultUnexpectedInPathA); + + R_SUCCEED(); + } + + Result AppendChild(const Path& rhs) { + R_RETURN(this->AppendChild(rhs.GetString())); + } + + Result Combine(const Path& parent, const Path& child) { + // Get the lengths + const auto p_len = parent.GetLength(); + const auto c_len = child.GetLength(); + + // Allocate our buffer + R_TRY(this->Preallocate(p_len + c_len + 1)); + + // Initialize as parent + R_TRY(this->Initialize(parent)); + + // If we're empty, we can just initialize as child + if (this->IsEmpty()) { + R_TRY(this->Initialize(child)); + } else { + // Otherwise, we should append the child + R_TRY(this->AppendChild(child)); + } + + R_SUCCEED(); + } + + Result RemoveChild() { + // If we don't have a write-buffer, ensure that we have one + if (m_write_buffer.Get() == nullptr) { + if (const auto len = std::strlen(m_str); len > 0) { + R_TRY(this->Preallocate(len)); + Strlcpy(m_write_buffer.Get(), m_str, len + 1); + } + } + + // Check that it's possible for us to remove a child + auto* p = m_write_buffer.Get(); + s32 len = std::strlen(p); + R_UNLESS(len != 1 || (p[0] != '/' && p[0] != '.'), ResultNotImplemented); + + // Handle a trailing separator + if (len > 0 && (p[len - 1] == '\\' || p[len - 1] == '/')) { + --len; + } + + // Remove the child path segment + while ((--len) >= 0 && p[len]) { + if (p[len] == '/' || p[len] == '\\') { + if (len > 0) { + p[len] = 0; + } else { + p[1] = 0; + len = 1; + } + break; + } + } + + // Check that length remains > 0 + R_UNLESS(len > 0, ResultNotImplemented); + + R_SUCCEED(); + } + + Result Normalize(const PathFlags& flags) { + // If we're already normalized, nothing to do + R_SUCCEED_IF(this->IsNormalized()); + + // Check if we're normalized + bool normalized; + size_t dummy; + R_TRY(PathFormatter::IsNormalized(std::addressof(normalized), std::addressof(dummy), m_str, + flags)); + + // If we're not normalized, normalize + if (!normalized) { + // Determine necessary buffer length + auto len = m_write_buffer.GetLength(); + if (flags.IsRelativePathAllowed() && IsPathRelative(m_str)) { + len += 2; + } + if (flags.IsWindowsPathAllowed() && IsWindowsPath(m_str, true)) { + len += 1; + } + + // Allocate a new buffer + const size_t size = Common::AlignUp(len, WriteBufferAlignmentLength); + auto buf = WriteBuffer::Make(size); + R_UNLESS(buf.Get() != nullptr, ResultAllocationMemoryFailedMakeUnique); + + // Normalize into it + R_TRY(PathFormatter::Normalize(buf.Get(), size, m_write_buffer.Get(), + m_write_buffer.GetLength(), flags)); + + // Set the normalized buffer as our buffer + this->SetModifiableBuffer(std::move(buf)); + } + + // Set normalized + this->SetNormalized(); + R_SUCCEED(); + } + +private: + void ClearBuffer() { + m_write_buffer.ResetBuffer(); + m_str = EmptyPath; + } + + void SetModifiableBuffer(WriteBuffer&& buffer) { + // Check pre-conditions + ASSERT(buffer.Get() != nullptr); + ASSERT(buffer.GetLength() > 0); + ASSERT(Common::IsAligned(buffer.GetLength(), WriteBufferAlignmentLength)); + + // Get whether we're normalized + if (m_write_buffer.IsNormalized()) { + buffer.SetNormalized(); + } else { + buffer.SetNotNormalized(); + } + + // Set write buffer + m_write_buffer = std::move(buffer); + m_str = m_write_buffer.Get(); + } + + constexpr void SetReadOnlyBuffer(const char* buffer) { + m_str = buffer; + m_write_buffer.ResetBuffer(); + } + + Result Preallocate(size_t length) { + // Allocate additional space, if needed + if (length > m_write_buffer.GetLength()) { + // Allocate buffer + const size_t size = Common::AlignUp(length, WriteBufferAlignmentLength); + auto buf = WriteBuffer::Make(size); + R_UNLESS(buf.Get() != nullptr, ResultAllocationMemoryFailedMakeUnique); + + // Set write buffer + this->SetModifiableBuffer(std::move(buf)); + } + + R_SUCCEED(); + } + + Result InitializeImpl(const char* path, size_t size) { + if (size > 0 && path[0]) { + // Pre allocate a buffer for the path + R_TRY(this->Preallocate(size + 1)); + + // Copy the path + const size_t copied = Strlcpy(m_write_buffer.Get(), path, size + 1); + R_UNLESS(copied >= size, ResultUnexpectedInPathA); + } else { + // We can just clear the buffer + this->ClearBuffer(); + } + + R_SUCCEED(); + } + + constexpr char* GetWriteBuffer() { + ASSERT(m_write_buffer.Get() != nullptr); + return m_write_buffer.Get(); + } + + constexpr size_t GetWriteBufferLength() const { + return m_write_buffer.GetLength(); + } + + constexpr bool IsNormalized() const { + return m_write_buffer.IsNormalized(); + } + + constexpr void SetNormalized() { + m_write_buffer.SetNormalized(); + } + + constexpr void SetNotNormalized() { + m_write_buffer.SetNotNormalized(); + } + +public: + bool operator==(const FileSys::Path& rhs) const { + return std::strcmp(this->GetString(), rhs.GetString()) == 0; + } + bool operator!=(const FileSys::Path& rhs) const { + return !(*this == rhs); + } + bool operator==(const char* p) const { + return std::strcmp(this->GetString(), p) == 0; + } + bool operator!=(const char* p) const { + return !(*this == p); + } +}; + +inline Result SetUpFixedPath(FileSys::Path* out, const char* s) { + // Verify the path is normalized + bool normalized; + size_t dummy; + R_TRY(PathNormalizer::IsNormalized(std::addressof(normalized), std::addressof(dummy), s)); + + R_UNLESS(normalized, ResultInvalidPathFormat); + + // Set the fixed path + R_RETURN(out->SetShallowBuffer(s)); +} + +constexpr inline bool IsWindowsDriveRootPath(const FileSys::Path& path) { + const char* const str = path.GetString(); + return IsWindowsDrive(str) && + (str[2] == StringTraits::DirectorySeparator || + str[2] == StringTraits::AlternateDirectorySeparator) && + str[3] == StringTraits::NullTerminator; +} + +} // namespace FileSys diff --git a/src/core/file_sys/fs_path_utility.h b/src/core/file_sys/fs_path_utility.h new file mode 100755 index 000000000..e9011d065 --- /dev/null +++ b/src/core/file_sys/fs_path_utility.h @@ -0,0 +1,1239 @@ +// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/assert.h" +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "common/scope_exit.h" +#include "core/file_sys/fs_directory.h" +#include "core/file_sys/fs_memory_management.h" +#include "core/file_sys/fs_string_util.h" +#include "core/hle/result.h" + +namespace FileSys { + +constexpr inline size_t MountNameLengthMax = 15; + +namespace StringTraits { + +constexpr inline char DirectorySeparator = '/'; +constexpr inline char DriveSeparator = ':'; +constexpr inline char Dot = '.'; +constexpr inline char NullTerminator = '\x00'; + +constexpr inline char AlternateDirectorySeparator = '\\'; + +constexpr inline const char InvalidCharacters[6] = {':', '*', '?', '<', '>', '|'}; +constexpr inline const char InvalidCharactersForHostName[6] = {':', '*', '<', '>', '|', '$'}; +constexpr inline const char InvalidCharactersForMountName[5] = {'*', '?', '<', '>', '|'}; + +namespace impl { + +template +consteval u64 MakeInvalidCharacterMask(size_t n) { + u64 mask = 0; + for (size_t i = 0; i < NumInvalidCharacters; ++i) { + if ((static_cast(InvalidCharacterSet[i]) >> 6) == n) { + mask |= static_cast(1) << (static_cast(InvalidCharacterSet[i]) & 0x3F); + } + } + return mask; +} + +template +constexpr bool IsInvalidCharacterImpl(char c) { + constexpr u64 Masks[4] = { + MakeInvalidCharacterMask(0), + MakeInvalidCharacterMask(1), + MakeInvalidCharacterMask(2), + MakeInvalidCharacterMask(3)}; + + return (Masks[static_cast(c) >> 6] & + (static_cast(1) << (static_cast(c) & 0x3F))) != 0; +} + +} // namespace impl + +constexpr bool IsInvalidCharacter(char c) { + return impl::IsInvalidCharacterImpl(c); +} +constexpr bool IsInvalidCharacterForHostName(char c) { + return impl::IsInvalidCharacterImpl(c); +} +constexpr bool IsInvalidCharacterForMountName(char c) { + return impl::IsInvalidCharacterImpl(c); +} + +} // namespace StringTraits + +constexpr inline size_t WindowsDriveLength = 2; +constexpr inline size_t UncPathPrefixLength = 2; +constexpr inline size_t DosDevicePathPrefixLength = 4; + +class PathFlags { +private: + static constexpr u32 WindowsPathFlag = (1 << 0); + static constexpr u32 RelativePathFlag = (1 << 1); + static constexpr u32 EmptyPathFlag = (1 << 2); + static constexpr u32 MountNameFlag = (1 << 3); + static constexpr u32 BackslashFlag = (1 << 4); + static constexpr u32 AllCharactersFlag = (1 << 5); + +private: + u32 m_value; + +public: + constexpr PathFlags() : m_value(0) { /* ... */ + } + +#define DECLARE_PATH_FLAG_HANDLER(__WHICH__) \ + constexpr bool Is##__WHICH__##Allowed() const { return (m_value & __WHICH__##Flag) != 0; } \ + constexpr void Allow##__WHICH__() { m_value |= __WHICH__##Flag; } + + DECLARE_PATH_FLAG_HANDLER(WindowsPath) + DECLARE_PATH_FLAG_HANDLER(RelativePath) + DECLARE_PATH_FLAG_HANDLER(EmptyPath) + DECLARE_PATH_FLAG_HANDLER(MountName) + DECLARE_PATH_FLAG_HANDLER(Backslash) + DECLARE_PATH_FLAG_HANDLER(AllCharacters) + +#undef DECLARE_PATH_FLAG_HANDLER +}; + +template + requires(std::same_as || std::same_as) +constexpr inline bool IsDosDevicePath(const T* path) { + ASSERT(path != nullptr); + + using namespace StringTraits; + + return path[0] == AlternateDirectorySeparator && path[1] == AlternateDirectorySeparator && + (path[2] == Dot || path[2] == '?') && + (path[3] == DirectorySeparator || path[3] == AlternateDirectorySeparator); +} + +template + requires(std::same_as || std::same_as) +constexpr inline bool IsUncPath(const T* path, bool allow_forward_slash = true, + bool allow_back_slash = true) { + ASSERT(path != nullptr); + + using namespace StringTraits; + + return (allow_forward_slash && path[0] == DirectorySeparator && + path[1] == DirectorySeparator) || + (allow_back_slash && path[0] == AlternateDirectorySeparator && + path[1] == AlternateDirectorySeparator); +} + +constexpr inline bool IsWindowsDrive(const char* path) { + ASSERT(path != nullptr); + + return (('a' <= path[0] && path[0] <= 'z') || ('A' <= path[0] && path[0] <= 'Z')) && + path[1] == StringTraits::DriveSeparator; +} + +constexpr inline bool IsWindowsPath(const char* path, bool allow_forward_slash_unc) { + return IsWindowsDrive(path) || IsDosDevicePath(path) || + IsUncPath(path, allow_forward_slash_unc, true); +} + +constexpr inline int GetWindowsSkipLength(const char* path) { + if (IsDosDevicePath(path)) { + return DosDevicePathPrefixLength; + } else if (IsWindowsDrive(path)) { + return WindowsDriveLength; + } else if (IsUncPath(path)) { + return UncPathPrefixLength; + } else { + return 0; + } +} + +constexpr inline bool IsPathAbsolute(const char* path) { + return IsWindowsPath(path, false) || path[0] == StringTraits::DirectorySeparator; +} + +constexpr inline bool IsPathRelative(const char* path) { + return path[0] && !IsPathAbsolute(path); +} + +constexpr inline bool IsCurrentDirectory(const char* path) { + return path[0] == StringTraits::Dot && + (path[1] == StringTraits::NullTerminator || path[1] == StringTraits::DirectorySeparator); +} + +constexpr inline bool IsParentDirectory(const char* path) { + return path[0] == StringTraits::Dot && path[1] == StringTraits::Dot && + (path[2] == StringTraits::NullTerminator || path[2] == StringTraits::DirectorySeparator); +} + +constexpr inline bool IsPathStartWithCurrentDirectory(const char* path) { + return IsCurrentDirectory(path) || IsParentDirectory(path); +} + +constexpr inline bool IsSubPath(const char* lhs, const char* rhs) { + // Check pre-conditions + ASSERT(lhs != nullptr); + ASSERT(rhs != nullptr); + + // Import StringTraits names for current scope + using namespace StringTraits; + + // Special case certain paths + if (IsUncPath(lhs) && !IsUncPath(rhs)) { + return false; + } + if (!IsUncPath(lhs) && IsUncPath(rhs)) { + return false; + } + + if (lhs[0] == DirectorySeparator && lhs[1] == NullTerminator && rhs[0] == DirectorySeparator && + rhs[1] != NullTerminator) { + return true; + } + if (rhs[0] == DirectorySeparator && rhs[1] == NullTerminator && lhs[0] == DirectorySeparator && + lhs[1] != NullTerminator) { + return true; + } + + // Check subpath + for (size_t i = 0; /* ... */; ++i) { + if (lhs[i] == NullTerminator) { + return rhs[i] == DirectorySeparator; + } else if (rhs[i] == NullTerminator) { + return lhs[i] == DirectorySeparator; + } else if (lhs[i] != rhs[i]) { + return false; + } + } +} + +// Path utilities +constexpr inline void Replace(char* dst, size_t dst_size, char old_char, char new_char) { + ASSERT(dst != nullptr); + for (char* cur = dst; cur < dst + dst_size && *cur; ++cur) { + if (*cur == old_char) { + *cur = new_char; + } + } +} + +constexpr inline Result CheckUtf8(const char* s) { + // Check pre-conditions + ASSERT(s != nullptr); + + // Iterate, checking for utf8-validity + while (*s) { + char utf8_buf[4] = {}; + + const auto pick_res = PickOutCharacterFromUtf8String(utf8_buf, std::addressof(s)); + R_UNLESS(pick_res == CharacterEncodingResult_Success, ResultInvalidPathFormat); + + u32 dummy; + const auto cvt_res = ConvertCharacterUtf8ToUtf32(std::addressof(dummy), utf8_buf); + R_UNLESS(cvt_res == CharacterEncodingResult_Success, ResultInvalidPathFormat); + } + + R_SUCCEED(); +} + +// Path formatting +class PathNormalizer { +private: + enum class PathState { + Start, + Normal, + FirstSeparator, + Separator, + CurrentDir, + ParentDir, + }; + +private: + static constexpr void ReplaceParentDirectoryPath(char* dst, const char* src) { + // Use StringTraits names for remainder of scope + using namespace StringTraits; + + // Start with a dir-separator + dst[0] = DirectorySeparator; + + auto i = 1; + while (src[i] != NullTerminator) { + if ((src[i - 1] == DirectorySeparator || src[i - 1] == AlternateDirectorySeparator) && + src[i + 0] == Dot && src[i + 1] == Dot && + (src[i + 2] == DirectorySeparator || src[i + 2] == AlternateDirectorySeparator)) { + dst[i - 1] = DirectorySeparator; + dst[i + 0] = Dot; + dst[i + 1] = Dot; + dst[i + 2] = DirectorySeparator; + i += 3; + } else { + if (src[i - 1] == AlternateDirectorySeparator && src[i + 0] == Dot && + src[i + 1] == Dot && src[i + 2] == NullTerminator) { + dst[i - 1] = DirectorySeparator; + dst[i + 0] = Dot; + dst[i + 1] = Dot; + i += 2; + break; + } + + dst[i] = src[i]; + ++i; + } + } + + dst[i] = StringTraits::NullTerminator; + } + +public: + static constexpr bool IsParentDirectoryPathReplacementNeeded(const char* path) { + // Use StringTraits names for remainder of scope + using namespace StringTraits; + + if (path[0] != DirectorySeparator && path[0] != AlternateDirectorySeparator) { + return false; + } + + // Check to find a parent reference using alternate separators + if (path[0] != NullTerminator && path[1] != NullTerminator && path[2] != NullTerminator) { + size_t i; + for (i = 0; path[i + 3] != NullTerminator; ++path) { + if (path[i + 1] != Dot || path[i + 2] != Dot) { + continue; + } + + const char c0 = path[i + 0]; + const char c3 = path[i + 3]; + + if (c0 == AlternateDirectorySeparator && + (c3 == DirectorySeparator || c3 == AlternateDirectorySeparator || + c3 == NullTerminator)) { + return true; + } + + if (c3 == AlternateDirectorySeparator && + (c0 == DirectorySeparator || c0 == AlternateDirectorySeparator)) { + return true; + } + } + + if (path[i + 0] == AlternateDirectorySeparator && path[i + 1] == Dot && + path[i + 2] == Dot /* && path[i + 3] == NullTerminator */) { + return true; + } + } + + return false; + } + + static constexpr Result IsNormalized(bool* out, size_t* out_len, const char* path, + bool allow_all_characters = false) { + // Use StringTraits names for remainder of scope + using namespace StringTraits; + + // Parse the path + auto state = PathState::Start; + size_t len = 0; + while (path[len] != NullTerminator) { + // Get the current character + const char c = path[len++]; + + // Check the current character is valid + if (!allow_all_characters && state != PathState::Start) { + R_UNLESS(!IsInvalidCharacter(c), ResultInvalidCharacter); + } + + // Process depending on current state + switch (state) { + // Import the PathState enums for convenience + using enum PathState; + + case Start: + R_UNLESS(c == DirectorySeparator, ResultInvalidPathFormat); + state = FirstSeparator; + break; + case Normal: + if (c == DirectorySeparator) { + state = Separator; + } + break; + case FirstSeparator: + case Separator: + if (c == DirectorySeparator) { + *out = false; + R_SUCCEED(); + } + + if (c == Dot) { + state = CurrentDir; + } else { + state = Normal; + } + break; + case CurrentDir: + if (c == DirectorySeparator) { + *out = false; + R_SUCCEED(); + } + + if (c == Dot) { + state = ParentDir; + } else { + state = Normal; + } + break; + case ParentDir: + if (c == DirectorySeparator) { + *out = false; + R_SUCCEED(); + } + + state = Normal; + break; + default: + UNREACHABLE(); + break; + } + } + + // Check the final state + switch (state) { + // Import the PathState enums for convenience + using enum PathState; + case Start: + R_THROW(ResultInvalidPathFormat); + case Normal: + case FirstSeparator: + *out = true; + break; + case Separator: + case CurrentDir: + case ParentDir: + *out = false; + break; + default: + UNREACHABLE(); + break; + } + + // Set the output length + *out_len = len; + R_SUCCEED(); + } + + static Result Normalize(char* dst, size_t* out_len, const char* path, size_t max_out_size, + bool is_windows_path, bool is_drive_relative_path, + bool allow_all_characters = false) { + // Use StringTraits names for remainder of scope + using namespace StringTraits; + + // Prepare to iterate + const char* cur_path = path; + size_t total_len = 0; + + // If path begins with a separator, check that we're not drive relative + if (cur_path[0] != DirectorySeparator) { + R_UNLESS(is_drive_relative_path, ResultInvalidPathFormat); + + dst[total_len++] = DirectorySeparator; + } + + // We're going to need to do path replacement, potentially + char* replacement_path = nullptr; + size_t replacement_path_size = 0; + + SCOPE_EXIT({ + if (replacement_path != nullptr) { + if (std::is_constant_evaluated()) { + delete[] replacement_path; + } else { + Deallocate(replacement_path, replacement_path_size); + } + } + }); + + // Perform path replacement, if necessary + if (IsParentDirectoryPathReplacementNeeded(cur_path)) { + if (std::is_constant_evaluated()) { + replacement_path_size = EntryNameLengthMax + 1; + replacement_path = new char[replacement_path_size]; + } else { + replacement_path_size = EntryNameLengthMax + 1; + replacement_path = static_cast(Allocate(replacement_path_size)); + } + + ReplaceParentDirectoryPath(replacement_path, cur_path); + + cur_path = replacement_path; + } + + // Iterate, normalizing path components + bool skip_next_sep = false; + size_t i = 0; + + while (cur_path[i] != NullTerminator) { + // Process a directory separator, if we run into one + if (cur_path[i] == DirectorySeparator) { + // Swallow separators + do { + ++i; + } while (cur_path[i] == DirectorySeparator); + + // Check if we hit end of string + if (cur_path[i] == NullTerminator) { + break; + } + + // If we aren't skipping the separator, write it, checking that we remain in bounds. + if (!skip_next_sep) { + if (total_len + 1 == max_out_size) { + dst[total_len] = NullTerminator; + *out_len = total_len; + R_THROW(ResultTooLongPath); + } + + dst[total_len++] = DirectorySeparator; + } + + // Don't skip the next separator + skip_next_sep = false; + } + + // Get the length of the current directory component + size_t dir_len = 0; + while (cur_path[i + dir_len] != DirectorySeparator && + cur_path[i + dir_len] != NullTerminator) { + // Check for validity + if (!allow_all_characters) { + R_UNLESS(!IsInvalidCharacter(cur_path[i + dir_len]), ResultInvalidCharacter); + } + + ++dir_len; + } + + // Handle the current dir component + if (IsCurrentDirectory(cur_path + i)) { + skip_next_sep = true; + } else if (IsParentDirectory(cur_path + i)) { + // We should have just written a separator + ASSERT(dst[total_len - 1] == DirectorySeparator); + + // We should have started with a separator, for non-windows paths + if (!is_windows_path) { + ASSERT(dst[0] == DirectorySeparator); + } + + // Remove the previous component + if (total_len == 1) { + R_UNLESS(is_windows_path, ResultDirectoryUnobtainable); + + --total_len; + } else { + total_len -= 2; + + do { + if (dst[total_len] == DirectorySeparator) { + break; + } + } while ((--total_len) != 0); + } + + // We should be pointing to a directory separator, for non-windows paths + if (!is_windows_path) { + ASSERT(dst[total_len] == DirectorySeparator); + } + + // We should remain in bounds + ASSERT(total_len < max_out_size); + } else { + // Copy, possibly truncating + if (total_len + dir_len + 1 > max_out_size) { + const size_t copy_len = max_out_size - (total_len + 1); + + for (size_t j = 0; j < copy_len; ++j) { + dst[total_len++] = cur_path[i + j]; + } + + dst[total_len] = NullTerminator; + *out_len = total_len; + R_THROW(ResultTooLongPath); + } + + for (size_t j = 0; j < dir_len; ++j) { + dst[total_len++] = cur_path[i + j]; + } + } + + // Advance past the current directory component + i += dir_len; + } + + if (skip_next_sep) { + --total_len; + } + + if (total_len == 0 && max_out_size != 0) { + total_len = 1; + dst[0] = DirectorySeparator; + } + + // NOTE: Probable nintendo bug, as max_out_size must be at least total_len + 1 for the null + // terminator. + R_UNLESS(max_out_size >= total_len - 1, ResultTooLongPath); + + dst[total_len] = NullTerminator; + + // Check that the result path is normalized + bool is_normalized; + size_t dummy; + R_TRY(IsNormalized(std::addressof(is_normalized), std::addressof(dummy), dst, + allow_all_characters)); + + // Assert that the result path is normalized + ASSERT(is_normalized); + + // Set the output length + *out_len = total_len; + R_SUCCEED(); + } +}; + +class PathFormatter { +private: + static constexpr Result CheckSharedName(const char* name, size_t len) { + // Use StringTraits names for remainder of scope + using namespace StringTraits; + + if (len == 1) { + R_UNLESS(name[0] != Dot, ResultInvalidPathFormat); + } else if (len == 2) { + R_UNLESS(name[0] != Dot || name[1] != Dot, ResultInvalidPathFormat); + } + + for (size_t i = 0; i < len; ++i) { + R_UNLESS(!IsInvalidCharacter(name[i]), ResultInvalidCharacter); + } + + R_SUCCEED(); + } + + static constexpr Result CheckHostName(const char* name, size_t len) { + // Use StringTraits names for remainder of scope + using namespace StringTraits; + + if (len == 2) { + R_UNLESS(name[0] != Dot || name[1] != Dot, ResultInvalidPathFormat); + } + + for (size_t i = 0; i < len; ++i) { + R_UNLESS(!IsInvalidCharacterForHostName(name[i]), ResultInvalidCharacter); + } + + R_SUCCEED(); + } + + static constexpr Result CheckInvalidBackslash(bool* out_contains_backslash, const char* path, + bool allow_backslash) { + // Use StringTraits names for remainder of scope + using namespace StringTraits; + + // Default to no backslashes, so we can just write if we see one + *out_contains_backslash = false; + + while (*path != NullTerminator) { + if (*(path++) == AlternateDirectorySeparator) { + *out_contains_backslash = true; + + R_UNLESS(allow_backslash, ResultInvalidCharacter); + } + } + + R_SUCCEED(); + } + +public: + static constexpr Result CheckPathFormat(const char* path, const PathFlags& flags) { + bool normalized; + size_t len; + R_RETURN(IsNormalized(std::addressof(normalized), std::addressof(len), path, flags)); + } + + static constexpr Result SkipMountName(const char** out, size_t* out_len, const char* path) { + R_RETURN(ParseMountName(out, out_len, nullptr, 0, path)); + } + + static constexpr Result ParseMountName(const char** out, size_t* out_len, char* out_mount_name, + size_t out_mount_name_buffer_size, const char* path) { + // Check pre-conditions + ASSERT(path != nullptr); + ASSERT(out_len != nullptr); + ASSERT(out != nullptr); + ASSERT((out_mount_name == nullptr) == (out_mount_name_buffer_size == 0)); + + // Use StringTraits names for remainder of scope + using namespace StringTraits; + + // Determine max mount length + const auto max_mount_len = + out_mount_name_buffer_size == 0 + ? MountNameLengthMax + 1 + : std::min(MountNameLengthMax + 1, out_mount_name_buffer_size); + + // Parse the path until we see a drive separator + size_t mount_len = 0; + for (/* ... */; mount_len < max_mount_len && path[mount_len]; ++mount_len) { + const char c = path[mount_len]; + + // If we see a drive separator, advance, then we're done with the pre-drive separator + // part of the mount. + if (c == DriveSeparator) { + ++mount_len; + break; + } + + // If we see a directory separator, we're not in a mount name + if (c == DirectorySeparator || c == AlternateDirectorySeparator) { + *out = path; + *out_len = 0; + R_SUCCEED(); + } + } + + // Check to be sure we're actually looking at a mount name + if (mount_len <= 2 || path[mount_len - 1] != DriveSeparator) { + *out = path; + *out_len = 0; + R_SUCCEED(); + } + + // Check that all characters in the mount name are allowable + for (size_t i = 0; i < mount_len; ++i) { + R_UNLESS(!IsInvalidCharacterForMountName(path[i]), ResultInvalidCharacter); + } + + // Copy out the mount name + if (out_mount_name_buffer_size > 0) { + R_UNLESS(mount_len < out_mount_name_buffer_size, ResultTooLongPath); + + for (size_t i = 0; i < mount_len; ++i) { + out_mount_name[i] = path[i]; + } + out_mount_name[mount_len] = NullTerminator; + } + + // Set the output + *out = path + mount_len; + *out_len = mount_len; + R_SUCCEED(); + } + + static constexpr Result SkipRelativeDotPath(const char** out, size_t* out_len, + const char* path) { + R_RETURN(ParseRelativeDotPath(out, out_len, nullptr, 0, path)); + } + + static constexpr Result ParseRelativeDotPath(const char** out, size_t* out_len, + char* out_relative, + size_t out_relative_buffer_size, + const char* path) { + // Check pre-conditions + ASSERT(path != nullptr); + ASSERT(out_len != nullptr); + ASSERT(out != nullptr); + ASSERT((out_relative == nullptr) == (out_relative_buffer_size == 0)); + + // Use StringTraits names for remainder of scope + using namespace StringTraits; + + // Initialize the output buffer, if we have one + if (out_relative_buffer_size > 0) { + out_relative[0] = NullTerminator; + } + + // Check if the path is relative + if (path[0] == Dot && (path[1] == NullTerminator || path[1] == DirectorySeparator || + path[1] == AlternateDirectorySeparator)) { + if (out_relative_buffer_size > 0) { + R_UNLESS(out_relative_buffer_size >= 2, ResultTooLongPath); + + out_relative[0] = Dot; + out_relative[1] = NullTerminator; + } + + *out = path + 1; + *out_len = 1; + R_SUCCEED(); + } + + // Ensure the path isn't a parent directory + R_UNLESS(!(path[0] == Dot && path[1] == Dot), ResultDirectoryUnobtainable); + + // There was no relative dot path + *out = path; + *out_len = 0; + R_SUCCEED(); + } + + static constexpr Result SkipWindowsPath(const char** out, size_t* out_len, bool* out_normalized, + const char* path, bool has_mount_name) { + // We're normalized if and only if the parsing doesn't throw ResultNotNormalized() + *out_normalized = true; + + R_TRY_CATCH(ParseWindowsPath(out, out_len, nullptr, 0, path, has_mount_name)) { + R_CATCH(ResultNotNormalized) { + *out_normalized = false; + } + } + R_END_TRY_CATCH; + ON_RESULT_INCLUDED(ResultNotNormalized) { + *out_normalized = false; + }; + + R_SUCCEED(); + } + + static constexpr Result ParseWindowsPath(const char** out, size_t* out_len, char* out_win, + size_t out_win_buffer_size, const char* path, + bool has_mount_name) { + // Check pre-conditions + ASSERT(path != nullptr); + ASSERT(out_len != nullptr); + ASSERT(out != nullptr); + ASSERT((out_win == nullptr) == (out_win_buffer_size == 0)); + + // Use StringTraits names for remainder of scope + using namespace StringTraits; + + // Initialize the output buffer, if we have one + if (out_win_buffer_size > 0) { + out_win[0] = NullTerminator; + } + + // Handle path start + const char* cur_path = path; + if (has_mount_name && path[0] == DirectorySeparator) { + if (path[1] == AlternateDirectorySeparator && path[2] == AlternateDirectorySeparator) { + R_UNLESS(out_win_buffer_size > 0, ResultNotNormalized); + + ++cur_path; + } else if (IsWindowsDrive(path + 1)) { + R_UNLESS(out_win_buffer_size > 0, ResultNotNormalized); + + ++cur_path; + } + } + + // Handle windows drive + if (IsWindowsDrive(cur_path)) { + // Parse up to separator + size_t win_path_len = WindowsDriveLength; + for (/* ... */; cur_path[win_path_len] != NullTerminator; ++win_path_len) { + R_UNLESS(!IsInvalidCharacter(cur_path[win_path_len]), ResultInvalidCharacter); + + if (cur_path[win_path_len] == DirectorySeparator || + cur_path[win_path_len] == AlternateDirectorySeparator) { + break; + } + } + + // Ensure that we're normalized, if we're required to be + if (out_win_buffer_size == 0) { + for (size_t i = 0; i < win_path_len; ++i) { + R_UNLESS(cur_path[i] != AlternateDirectorySeparator, ResultNotNormalized); + } + } else { + // Ensure we can copy into the normalized buffer + R_UNLESS(win_path_len < out_win_buffer_size, ResultTooLongPath); + + for (size_t i = 0; i < win_path_len; ++i) { + out_win[i] = cur_path[i]; + } + out_win[win_path_len] = NullTerminator; + + Replace(out_win, win_path_len, AlternateDirectorySeparator, DirectorySeparator); + } + + *out = cur_path + win_path_len; + *out_len = win_path_len; + R_SUCCEED(); + } + + // Handle DOS device + if (IsDosDevicePath(cur_path)) { + size_t dos_prefix_len = DosDevicePathPrefixLength; + + if (IsWindowsDrive(cur_path + dos_prefix_len)) { + dos_prefix_len += WindowsDriveLength; + } else { + --dos_prefix_len; + } + + if (out_win_buffer_size > 0) { + // Ensure we can copy into the normalized buffer + R_UNLESS(dos_prefix_len < out_win_buffer_size, ResultTooLongPath); + + for (size_t i = 0; i < dos_prefix_len; ++i) { + out_win[i] = cur_path[i]; + } + out_win[dos_prefix_len] = NullTerminator; + + Replace(out_win, dos_prefix_len, DirectorySeparator, AlternateDirectorySeparator); + } + + *out = cur_path + dos_prefix_len; + *out_len = dos_prefix_len; + R_SUCCEED(); + } + + // Handle UNC path + if (IsUncPath(cur_path, false, true)) { + const char* final_path = cur_path; + + R_UNLESS(cur_path[UncPathPrefixLength] != DirectorySeparator, ResultInvalidPathFormat); + R_UNLESS(cur_path[UncPathPrefixLength] != AlternateDirectorySeparator, + ResultInvalidPathFormat); + + size_t cur_component_offset = 0; + size_t pos = UncPathPrefixLength; + for (/* ... */; cur_path[pos] != NullTerminator; ++pos) { + if (cur_path[pos] == DirectorySeparator || + cur_path[pos] == AlternateDirectorySeparator) { + if (cur_component_offset != 0) { + R_TRY(CheckSharedName(cur_path + cur_component_offset, + pos - cur_component_offset)); + + final_path = cur_path + pos; + break; + } + + R_UNLESS(cur_path[pos + 1] != DirectorySeparator, ResultInvalidPathFormat); + R_UNLESS(cur_path[pos + 1] != AlternateDirectorySeparator, + ResultInvalidPathFormat); + + R_TRY(CheckHostName(cur_path + 2, pos - 2)); + + cur_component_offset = pos + 1; + } + } + + R_UNLESS(cur_component_offset != pos, ResultInvalidPathFormat); + + if (cur_component_offset != 0 && final_path == cur_path) { + R_TRY(CheckSharedName(cur_path + cur_component_offset, pos - cur_component_offset)); + + final_path = cur_path + pos; + } + + size_t unc_prefix_len = final_path - cur_path; + + // Ensure that we're normalized, if we're required to be + if (out_win_buffer_size == 0) { + for (size_t i = 0; i < unc_prefix_len; ++i) { + R_UNLESS(cur_path[i] != DirectorySeparator, ResultNotNormalized); + } + } else { + // Ensure we can copy into the normalized buffer + R_UNLESS(unc_prefix_len < out_win_buffer_size, ResultTooLongPath); + + for (size_t i = 0; i < unc_prefix_len; ++i) { + out_win[i] = cur_path[i]; + } + out_win[unc_prefix_len] = NullTerminator; + + Replace(out_win, unc_prefix_len, DirectorySeparator, AlternateDirectorySeparator); + } + + *out = cur_path + unc_prefix_len; + *out_len = unc_prefix_len; + R_SUCCEED(); + } + + // There's no windows path to parse + *out = path; + *out_len = 0; + R_SUCCEED(); + } + + static constexpr Result IsNormalized(bool* out, size_t* out_len, const char* path, + const PathFlags& flags = {}) { + // Ensure nothing is null + R_UNLESS(out != nullptr, ResultNullptrArgument); + R_UNLESS(out_len != nullptr, ResultNullptrArgument); + R_UNLESS(path != nullptr, ResultNullptrArgument); + + // Verify that the path is valid utf-8 + R_TRY(CheckUtf8(path)); + + // Use StringTraits names for remainder of scope + using namespace StringTraits; + + // Handle the case where the path is empty + if (path[0] == NullTerminator) { + R_UNLESS(flags.IsEmptyPathAllowed(), ResultInvalidPathFormat); + + *out = true; + *out_len = 0; + R_SUCCEED(); + } + + // All normalized paths start with a directory separator...unless they're windows paths, + // relative paths, or have mount names. + if (path[0] != DirectorySeparator) { + R_UNLESS(flags.IsWindowsPathAllowed() || flags.IsRelativePathAllowed() || + flags.IsMountNameAllowed(), + ResultInvalidPathFormat); + } + + // Check that the path is allowed to be a windows path, if it is + if (IsWindowsPath(path, false)) { + R_UNLESS(flags.IsWindowsPathAllowed(), ResultInvalidPathFormat); + } + + // Skip past the mount name, if one is present + size_t total_len = 0; + size_t mount_name_len = 0; + R_TRY(SkipMountName(std::addressof(path), std::addressof(mount_name_len), path)); + + // If we had a mount name, check that that was allowed + if (mount_name_len > 0) { + R_UNLESS(flags.IsMountNameAllowed(), ResultInvalidPathFormat); + + total_len += mount_name_len; + } + + // Check that the path starts as a normalized path should + if (path[0] != DirectorySeparator && !IsPathStartWithCurrentDirectory(path) && + !IsWindowsPath(path, false)) { + R_UNLESS(flags.IsRelativePathAllowed(), ResultInvalidPathFormat); + R_UNLESS(!IsInvalidCharacter(path[0]), ResultInvalidPathFormat); + + *out = false; + R_SUCCEED(); + } + + // Process relative path + size_t relative_len = 0; + R_TRY(SkipRelativeDotPath(std::addressof(path), std::addressof(relative_len), path)); + + // If we have a relative path, check that was allowed + if (relative_len > 0) { + R_UNLESS(flags.IsRelativePathAllowed(), ResultInvalidPathFormat); + + total_len += relative_len; + + if (path[0] == NullTerminator) { + *out = true; + *out_len = total_len; + R_SUCCEED(); + } + } + + // Process windows path + size_t windows_len = 0; + bool normalized_win = false; + R_TRY(SkipWindowsPath(std::addressof(path), std::addressof(windows_len), + std::addressof(normalized_win), path, mount_name_len > 0)); + + // If the windows path wasn't normalized, we're not normalized + if (!normalized_win) { + R_UNLESS(flags.IsWindowsPathAllowed(), ResultInvalidPathFormat); + + *out = false; + R_SUCCEED(); + } + + // If we had a windows path, check that was allowed + if (windows_len > 0) { + R_UNLESS(flags.IsWindowsPathAllowed(), ResultInvalidPathFormat); + + total_len += windows_len; + + // We can't have both a relative path and a windows path + R_UNLESS(relative_len == 0, ResultInvalidPathFormat); + + // A path ending in a windows path isn't normalized + if (path[0] == NullTerminator) { + *out = false; + R_SUCCEED(); + } + + // Check that there are no windows directory separators in the path + for (size_t i = 0; path[i] != NullTerminator; ++i) { + if (path[i] == AlternateDirectorySeparator) { + *out = false; + R_SUCCEED(); + } + } + } + + // Check that parent directory replacement is not needed if backslashes are allowed + if (flags.IsBackslashAllowed() && + PathNormalizer::IsParentDirectoryPathReplacementNeeded(path)) { + *out = false; + R_SUCCEED(); + } + + // Check that the backslash state is valid + bool is_backslash_contained = false; + R_TRY(CheckInvalidBackslash(std::addressof(is_backslash_contained), path, + flags.IsWindowsPathAllowed() || flags.IsBackslashAllowed())); + + // Check that backslashes are contained only if allowed + if (is_backslash_contained && !flags.IsBackslashAllowed()) { + *out = false; + R_SUCCEED(); + } + + // Check that the final result path is normalized + size_t normal_len = 0; + R_TRY(PathNormalizer::IsNormalized(out, std::addressof(normal_len), path, + flags.IsAllCharactersAllowed())); + + // Add the normal length + total_len += normal_len; + + // Set the output length + *out_len = total_len; + R_SUCCEED(); + } + + static Result Normalize(char* dst, size_t dst_size, const char* path, size_t path_len, + const PathFlags& flags) { + // Use StringTraits names for remainder of scope + using namespace StringTraits; + + // Prepare to iterate + const char* src = path; + size_t cur_pos = 0; + bool is_windows_path = false; + + // Check if the path is empty + if (src[0] == NullTerminator) { + if (dst_size != 0) { + dst[0] = NullTerminator; + } + + R_UNLESS(flags.IsEmptyPathAllowed(), ResultInvalidPathFormat); + + R_SUCCEED(); + } + + // Handle a mount name + size_t mount_name_len = 0; + if (flags.IsMountNameAllowed()) { + R_TRY(ParseMountName(std::addressof(src), std::addressof(mount_name_len), dst + cur_pos, + dst_size - cur_pos, src)); + + cur_pos += mount_name_len; + } + + // Handle a drive-relative prefix + bool is_drive_relative = false; + if (src[0] != DirectorySeparator && !IsPathStartWithCurrentDirectory(src) && + !IsWindowsPath(src, false)) { + R_UNLESS(flags.IsRelativePathAllowed(), ResultInvalidPathFormat); + R_UNLESS(!IsInvalidCharacter(src[0]), ResultInvalidPathFormat); + + dst[cur_pos++] = Dot; + is_drive_relative = true; + } + + size_t relative_len = 0; + if (flags.IsRelativePathAllowed()) { + R_UNLESS(cur_pos < dst_size, ResultTooLongPath); + + R_TRY(ParseRelativeDotPath(std::addressof(src), std::addressof(relative_len), + dst + cur_pos, dst_size - cur_pos, src)); + + cur_pos += relative_len; + + if (src[0] == NullTerminator) { + R_UNLESS(cur_pos < dst_size, ResultTooLongPath); + + dst[cur_pos] = NullTerminator; + R_SUCCEED(); + } + } + + // Handle a windows path + if (flags.IsWindowsPathAllowed()) { + const char* const orig = src; + + R_UNLESS(cur_pos < dst_size, ResultTooLongPath); + + size_t windows_len = 0; + R_TRY(ParseWindowsPath(std::addressof(src), std::addressof(windows_len), dst + cur_pos, + dst_size - cur_pos, src, mount_name_len != 0)); + + cur_pos += windows_len; + + if (src[0] == NullTerminator) { + /* NOTE: Bug in original code here repeated, should be checking cur_pos + 2. */ + R_UNLESS(cur_pos + 1 < dst_size, ResultTooLongPath); + + dst[cur_pos + 0] = DirectorySeparator; + dst[cur_pos + 1] = NullTerminator; + R_SUCCEED(); + } + + if ((src - orig) > 0) { + is_windows_path = true; + } + } + + // Check for invalid backslash + bool backslash_contained = false; + R_TRY(CheckInvalidBackslash(std::addressof(backslash_contained), src, + flags.IsWindowsPathAllowed() || flags.IsBackslashAllowed())); + + // Handle backslash replacement as necessary + if (backslash_contained && flags.IsWindowsPathAllowed()) { + // Create a temporary buffer holding a slash-replaced version of the path. + // NOTE: Nintendo unnecessarily allocates and replaces here a fully copy of the path, + // despite having skipped some of it already. + const size_t replaced_src_len = path_len - (src - path); + + char* replaced_src = nullptr; + SCOPE_EXIT({ + if (replaced_src != nullptr) { + if (std::is_constant_evaluated()) { + delete[] replaced_src; + } else { + Deallocate(replaced_src, replaced_src_len); + } + } + }); + + if (std::is_constant_evaluated()) { + replaced_src = new char[replaced_src_len]; + } else { + replaced_src = static_cast(Allocate(replaced_src_len)); + } + + Strlcpy(replaced_src, src, replaced_src_len); + + Replace(replaced_src, replaced_src_len, AlternateDirectorySeparator, + DirectorySeparator); + + size_t dummy; + R_TRY(PathNormalizer::Normalize(dst + cur_pos, std::addressof(dummy), replaced_src, + dst_size - cur_pos, is_windows_path, is_drive_relative, + flags.IsAllCharactersAllowed())); + } else { + // We can just do normalization + size_t dummy; + R_TRY(PathNormalizer::Normalize(dst + cur_pos, std::addressof(dummy), src, + dst_size - cur_pos, is_windows_path, is_drive_relative, + flags.IsAllCharactersAllowed())); + } + + R_SUCCEED(); + } +}; + +} // namespace FileSys diff --git a/src/core/file_sys/fs_string_util.h b/src/core/file_sys/fs_string_util.h new file mode 100755 index 000000000..874e09054 --- /dev/null +++ b/src/core/file_sys/fs_string_util.h @@ -0,0 +1,226 @@ +// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/assert.h" + +namespace FileSys { + +template +constexpr int Strlen(const T* str) { + ASSERT(str != nullptr); + + int length = 0; + while (*str++) { + ++length; + } + + return length; +} + +template +constexpr int Strnlen(const T* str, int count) { + ASSERT(str != nullptr); + ASSERT(count >= 0); + + int length = 0; + while (count-- && *str++) { + ++length; + } + + return length; +} + +template +constexpr int Strncmp(const T* lhs, const T* rhs, int count) { + ASSERT(lhs != nullptr); + ASSERT(rhs != nullptr); + ASSERT(count >= 0); + + if (count == 0) { + return 0; + } + + T l, r; + do { + l = *(lhs++); + r = *(rhs++); + } while (l && (l == r) && (--count)); + + return l - r; +} + +template +static constexpr int Strlcpy(T* dst, const T* src, int count) { + ASSERT(dst != nullptr); + ASSERT(src != nullptr); + + const T* cur = src; + if (count > 0) { + while ((--count) && *cur) { + *(dst++) = *(cur++); + } + *dst = 0; + } + + while (*cur) { + cur++; + } + + return static_cast(cur - src); +} + +enum CharacterEncodingResult { + CharacterEncodingResult_Success = 0, + CharacterEncodingResult_InsufficientLength = 1, + CharacterEncodingResult_InvalidFormat = 2, +}; + +namespace impl { + +class CharacterEncodingHelper { +public: + static constexpr int8_t Utf8NBytesInnerTable[0x100 + 1] = { + -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 7, 8, + }; + + static constexpr char GetUtf8NBytes(size_t i) { + return static_cast(Utf8NBytesInnerTable[1 + i]); + } +}; + +} // namespace impl + +constexpr inline CharacterEncodingResult ConvertCharacterUtf8ToUtf32(u32* dst, const char* src) { + // Check pre-conditions + ASSERT(dst != nullptr); + ASSERT(src != nullptr); + + // Perform the conversion + const auto* p = src; + switch (impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast(p[0]))) { + case 1: + *dst = static_cast(p[0]); + return CharacterEncodingResult_Success; + case 2: + if ((static_cast(p[0]) & 0x1E) != 0) { + if (impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast(p[1])) == + 0) { + *dst = (static_cast(p[0] & 0x1F) << 6) | (static_cast(p[1] & 0x3F) << 0); + return CharacterEncodingResult_Success; + } + } + break; + case 3: + if (impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast(p[1])) == 0 && + impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast(p[2])) == 0) { + const u32 c = (static_cast(p[0] & 0xF) << 12) | + (static_cast(p[1] & 0x3F) << 6) | + (static_cast(p[2] & 0x3F) << 0); + if ((c & 0xF800) != 0 && (c & 0xF800) != 0xD800) { + *dst = c; + return CharacterEncodingResult_Success; + } + } + return CharacterEncodingResult_InvalidFormat; + case 4: + if (impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast(p[1])) == 0 && + impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast(p[2])) == 0 && + impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast(p[3])) == 0) { + const u32 c = + (static_cast(p[0] & 0x7) << 18) | (static_cast(p[1] & 0x3F) << 12) | + (static_cast(p[2] & 0x3F) << 6) | (static_cast(p[3] & 0x3F) << 0); + if (c >= 0x10000 && c < 0x110000) { + *dst = c; + return CharacterEncodingResult_Success; + } + } + return CharacterEncodingResult_InvalidFormat; + default: + break; + } + + // We failed to convert + return CharacterEncodingResult_InvalidFormat; +} + +constexpr inline CharacterEncodingResult PickOutCharacterFromUtf8String(char* dst, + const char** str) { + // Check pre-conditions + ASSERT(dst != nullptr); + ASSERT(str != nullptr); + ASSERT(*str != nullptr); + + // Clear the output + dst[0] = 0; + dst[1] = 0; + dst[2] = 0; + dst[3] = 0; + + // Perform the conversion + const auto* p = *str; + u32 c = static_cast(*p); + switch (impl::CharacterEncodingHelper::GetUtf8NBytes(c)) { + case 1: + dst[0] = (*str)[0]; + ++(*str); + break; + case 2: + if ((p[0] & 0x1E) != 0) { + if (impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast(p[1])) == + 0) { + c = (static_cast(p[0] & 0x1F) << 6) | (static_cast(p[1] & 0x3F) << 0); + dst[0] = (*str)[0]; + dst[1] = (*str)[1]; + (*str) += 2; + break; + } + } + return CharacterEncodingResult_InvalidFormat; + case 3: + if (impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast(p[1])) == 0 && + impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast(p[2])) == 0) { + c = (static_cast(p[0] & 0xF) << 12) | (static_cast(p[1] & 0x3F) << 6) | + (static_cast(p[2] & 0x3F) << 0); + if ((c & 0xF800) != 0 && (c & 0xF800) != 0xD800) { + dst[0] = (*str)[0]; + dst[1] = (*str)[1]; + dst[2] = (*str)[2]; + (*str) += 3; + break; + } + } + return CharacterEncodingResult_InvalidFormat; + case 4: + if (impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast(p[1])) == 0 && + impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast(p[2])) == 0 && + impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast(p[3])) == 0) { + c = (static_cast(p[0] & 0x7) << 18) | (static_cast(p[1] & 0x3F) << 12) | + (static_cast(p[2] & 0x3F) << 6) | (static_cast(p[3] & 0x3F) << 0); + if (c >= 0x10000 && c < 0x110000) { + dst[0] = (*str)[0]; + dst[1] = (*str)[1]; + dst[2] = (*str)[2]; + dst[3] = (*str)[3]; + (*str) += 4; + break; + } + } + return CharacterEncodingResult_InvalidFormat; + default: + return CharacterEncodingResult_InvalidFormat; + } + + return CharacterEncodingResult_Success; +} + +} // namespace FileSys diff --git a/src/core/file_sys/fsmitm_romfsbuild.cpp b/src/core/file_sys/fsmitm_romfsbuild.cpp index de4dc5ed7..ab51a7778 100755 --- a/src/core/file_sys/fsmitm_romfsbuild.cpp +++ b/src/core/file_sys/fsmitm_romfsbuild.cpp @@ -8,8 +8,8 @@ #include "common/assert.h" #include "core/file_sys/fsmitm_romfsbuild.h" #include "core/file_sys/ips_layer.h" -#include "core/file_sys/vfs.h" -#include "core/file_sys/vfs_vector.h" +#include "core/file_sys/vfs/vfs.h" +#include "core/file_sys/vfs/vfs_vector.h" namespace FileSys { diff --git a/src/core/file_sys/fsmitm_romfsbuild.h b/src/core/file_sys/fsmitm_romfsbuild.h index b69bee23a..8984e87ab 100755 --- a/src/core/file_sys/fsmitm_romfsbuild.h +++ b/src/core/file_sys/fsmitm_romfsbuild.h @@ -7,7 +7,7 @@ #include #include #include "common/common_types.h" -#include "core/file_sys/vfs.h" +#include "core/file_sys/vfs/vfs.h" namespace FileSys { diff --git a/src/core/file_sys/fssystem/fs_i_storage.h b/src/core/file_sys/fssystem/fs_i_storage.h index 416dd57b8..37336c9ae 100755 --- a/src/core/file_sys/fssystem/fs_i_storage.h +++ b/src/core/file_sys/fssystem/fs_i_storage.h @@ -5,7 +5,7 @@ #include "common/overflow.h" #include "core/file_sys/errors.h" -#include "core/file_sys/vfs.h" +#include "core/file_sys/vfs/vfs.h" 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 index f25c95472..bc1cddbb0 100755 --- 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 @@ -4,7 +4,7 @@ #include "core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.h" #include "core/file_sys/fssystem/fssystem_aes_ctr_storage.h" #include "core/file_sys/fssystem/fssystem_nca_header.h" -#include "core/file_sys/vfs_offset.h" +#include "core/file_sys/vfs/vfs_offset.h" 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 index 339e49697..5abd93d33 100755 --- a/src/core/file_sys/fssystem/fssystem_aes_ctr_storage.h +++ b/src/core/file_sys/fssystem/fssystem_aes_ctr_storage.h @@ -9,7 +9,7 @@ #include "core/crypto/key_manager.h" #include "core/file_sys/errors.h" #include "core/file_sys/fssystem/fs_i_storage.h" -#include "core/file_sys/vfs.h" +#include "core/file_sys/vfs/vfs.h" namespace FileSys { diff --git a/src/core/file_sys/fssystem/fssystem_bucket_tree.h b/src/core/file_sys/fssystem/fssystem_bucket_tree.h index 46850cd48..3a5e21d1a 100755 --- a/src/core/file_sys/fssystem/fssystem_bucket_tree.h +++ b/src/core/file_sys/fssystem/fssystem_bucket_tree.h @@ -10,7 +10,7 @@ #include "common/common_types.h" #include "common/literals.h" -#include "core/file_sys/vfs.h" +#include "core/file_sys/vfs/vfs.h" #include "core/hle/result.h" namespace FileSys { diff --git a/src/core/file_sys/fssystem/fssystem_compressed_storage.h b/src/core/file_sys/fssystem/fssystem_compressed_storage.h index 33d93938e..74c98630e 100755 --- a/src/core/file_sys/fssystem/fssystem_compressed_storage.h +++ b/src/core/file_sys/fssystem/fssystem_compressed_storage.h @@ -10,7 +10,7 @@ #include "core/file_sys/fssystem/fssystem_bucket_tree.h" #include "core/file_sys/fssystem/fssystem_compression_common.h" #include "core/file_sys/fssystem/fssystem_pooled_buffer.h" -#include "core/file_sys/vfs.h" +#include "core/file_sys/vfs/vfs.h" namespace FileSys { 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 index 4a75b5308..39bb7b808 100755 --- a/src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.cpp +++ b/src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.cpp @@ -2,7 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h" -#include "core/file_sys/vfs_offset.h" +#include "core/file_sys/vfs/vfs_offset.h" 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 index 5cf697efe..bd129db47 100755 --- a/src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h +++ b/src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h @@ -8,7 +8,7 @@ #include "core/file_sys/fssystem/fs_types.h" #include "core/file_sys/fssystem/fssystem_alignment_matching_storage.h" #include "core/file_sys/fssystem/fssystem_integrity_verification_storage.h" -#include "core/file_sys/vfs_offset.h" +#include "core/file_sys/vfs/vfs_offset.h" 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 index 18df400af..41d3960b8 100755 --- a/src/core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.h +++ b/src/core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.h @@ -7,7 +7,7 @@ #include "core/file_sys/errors.h" #include "core/file_sys/fssystem/fs_i_storage.h" -#include "core/file_sys/vfs.h" +#include "core/file_sys/vfs/vfs.h" namespace FileSys { diff --git a/src/core/file_sys/fssystem/fssystem_indirect_storage.h b/src/core/file_sys/fssystem/fssystem_indirect_storage.h index 7854335bf..d4b95fd27 100755 --- a/src/core/file_sys/fssystem/fssystem_indirect_storage.h +++ b/src/core/file_sys/fssystem/fssystem_indirect_storage.h @@ -7,8 +7,8 @@ #include "core/file_sys/fssystem/fs_i_storage.h" #include "core/file_sys/fssystem/fssystem_bucket_tree.h" #include "core/file_sys/fssystem/fssystem_bucket_tree_template_impl.h" -#include "core/file_sys/vfs.h" -#include "core/file_sys/vfs_offset.h" +#include "core/file_sys/vfs/vfs.h" +#include "core/file_sys/vfs/vfs_offset.h" 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 index 5f8512b2a..240d1e388 100755 --- a/src/core/file_sys/fssystem/fssystem_integrity_romfs_storage.h +++ b/src/core/file_sys/fssystem/fssystem_integrity_romfs_storage.h @@ -5,7 +5,7 @@ #include "core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h" #include "core/file_sys/fssystem/fssystem_nca_header.h" -#include "core/file_sys/vfs_vector.h" +#include "core/file_sys/vfs/vfs_vector.h" 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 index 0f5432203..ab5a7984e 100755 --- a/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.cpp +++ b/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.cpp @@ -14,8 +14,8 @@ #include "core/file_sys/fssystem/fssystem_nca_file_system_driver.h" #include "core/file_sys/fssystem/fssystem_sparse_storage.h" #include "core/file_sys/fssystem/fssystem_switch_storage.h" -#include "core/file_sys/vfs_offset.h" -#include "core/file_sys/vfs_vector.h" +#include "core/file_sys/vfs/vfs_offset.h" +#include "core/file_sys/vfs/vfs_vector.h" 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 index 5771a21fc..5bc838de6 100755 --- a/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.h +++ b/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.h @@ -5,7 +5,7 @@ #include "core/file_sys/fssystem/fssystem_compression_common.h" #include "core/file_sys/fssystem/fssystem_nca_header.h" -#include "core/file_sys/vfs.h" +#include "core/file_sys/vfs/vfs.h" namespace FileSys { diff --git a/src/core/file_sys/fssystem/fssystem_nca_reader.cpp b/src/core/file_sys/fssystem/fssystem_nca_reader.cpp index a3714ab37..08924e2a6 100755 --- a/src/core/file_sys/fssystem/fssystem_nca_reader.cpp +++ b/src/core/file_sys/fssystem/fssystem_nca_reader.cpp @@ -3,7 +3,7 @@ #include "core/file_sys/fssystem/fssystem_aes_xts_storage.h" #include "core/file_sys/fssystem/fssystem_nca_file_system_driver.h" -#include "core/file_sys/vfs_offset.h" +#include "core/file_sys/vfs/vfs_offset.h" namespace FileSys { diff --git a/src/core/file_sys/ips_layer.cpp b/src/core/file_sys/ips_layer.cpp index 69abc37b5..1d6148e43 100755 --- a/src/core/file_sys/ips_layer.cpp +++ b/src/core/file_sys/ips_layer.cpp @@ -12,7 +12,7 @@ #include "common/logging/log.h" #include "common/swap.h" #include "core/file_sys/ips_layer.h" -#include "core/file_sys/vfs_vector.h" +#include "core/file_sys/vfs/vfs_vector.h" namespace FileSys { diff --git a/src/core/file_sys/ips_layer.h b/src/core/file_sys/ips_layer.h index b78a55084..a645b2dd7 100755 --- a/src/core/file_sys/ips_layer.h +++ b/src/core/file_sys/ips_layer.h @@ -8,7 +8,7 @@ #include #include "common/common_types.h" -#include "core/file_sys/vfs.h" +#include "core/file_sys/vfs/vfs.h" namespace FileSys { diff --git a/src/core/file_sys/kernel_executable.cpp b/src/core/file_sys/kernel_executable.cpp index 6e7f22701..992c486d4 100755 --- a/src/core/file_sys/kernel_executable.cpp +++ b/src/core/file_sys/kernel_executable.cpp @@ -5,7 +5,7 @@ #include "common/string_util.h" #include "core/file_sys/kernel_executable.h" -#include "core/file_sys/vfs_offset.h" +#include "core/file_sys/vfs/vfs_offset.h" #include "core/loader/loader.h" namespace FileSys { diff --git a/src/core/file_sys/kernel_executable.h b/src/core/file_sys/kernel_executable.h index 4e803c6b7..0132e8071 100755 --- a/src/core/file_sys/kernel_executable.h +++ b/src/core/file_sys/kernel_executable.h @@ -10,7 +10,7 @@ #include "common/common_funcs.h" #include "common/common_types.h" #include "common/swap.h" -#include "core/file_sys/vfs_types.h" +#include "core/file_sys/vfs/vfs_types.h" namespace Loader { enum class ResultStatus : u16; diff --git a/src/core/file_sys/nca_metadata.cpp b/src/core/file_sys/nca_metadata.cpp index ef5b8f7c3..8d00bf9df 100755 --- a/src/core/file_sys/nca_metadata.cpp +++ b/src/core/file_sys/nca_metadata.cpp @@ -6,7 +6,7 @@ #include "common/logging/log.h" #include "common/swap.h" #include "core/file_sys/nca_metadata.h" -#include "core/file_sys/vfs.h" +#include "core/file_sys/vfs/vfs.h" namespace FileSys { diff --git a/src/core/file_sys/nca_metadata.h b/src/core/file_sys/nca_metadata.h index 2c9e5c956..e08ab1ccb 100755 --- a/src/core/file_sys/nca_metadata.h +++ b/src/core/file_sys/nca_metadata.h @@ -8,7 +8,7 @@ #include "common/common_funcs.h" #include "common/common_types.h" #include "common/swap.h" -#include "core/file_sys/vfs_types.h" +#include "core/file_sys/vfs/vfs_types.h" namespace FileSys { class CNMT; diff --git a/src/core/file_sys/partition_filesystem.cpp b/src/core/file_sys/partition_filesystem.cpp index ee591a131..2d8b910dd 100755 --- a/src/core/file_sys/partition_filesystem.cpp +++ b/src/core/file_sys/partition_filesystem.cpp @@ -9,7 +9,7 @@ #include "common/logging/log.h" #include "core/file_sys/partition_filesystem.h" -#include "core/file_sys/vfs_offset.h" +#include "core/file_sys/vfs/vfs_offset.h" #include "core/loader/loader.h" namespace FileSys { diff --git a/src/core/file_sys/partition_filesystem.h b/src/core/file_sys/partition_filesystem.h index 2ba2feee1..d529b8d59 100755 --- a/src/core/file_sys/partition_filesystem.h +++ b/src/core/file_sys/partition_filesystem.h @@ -9,7 +9,7 @@ #include "common/common_funcs.h" #include "common/common_types.h" #include "common/swap.h" -#include "core/file_sys/vfs.h" +#include "core/file_sys/vfs/vfs.h" namespace Loader { enum class ResultStatus : u16; diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp index ba7d85e6f..467f94029 100755 --- a/src/core/file_sys/patch_manager.cpp +++ b/src/core/file_sys/patch_manager.cpp @@ -21,9 +21,9 @@ #include "core/file_sys/patch_manager.h" #include "core/file_sys/registered_cache.h" #include "core/file_sys/romfs.h" -#include "core/file_sys/vfs_cached.h" -#include "core/file_sys/vfs_layered.h" -#include "core/file_sys/vfs_vector.h" +#include "core/file_sys/vfs/vfs_cached.h" +#include "core/file_sys/vfs/vfs_layered.h" +#include "core/file_sys/vfs/vfs_vector.h" #include "core/hle/service/filesystem/filesystem.h" #include "core/hle/service/ns/language.h" #include "core/hle/service/set/settings_server.h" diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h index 8d682d42f..e1c35af3b 100755 --- a/src/core/file_sys/patch_manager.h +++ b/src/core/file_sys/patch_manager.h @@ -9,7 +9,7 @@ #include #include "common/common_types.h" #include "core/file_sys/nca_metadata.h" -#include "core/file_sys/vfs_types.h" +#include "core/file_sys/vfs/vfs_types.h" #include "core/memory/dmnt_cheat_types.h" namespace Core { diff --git a/src/core/file_sys/program_metadata.cpp b/src/core/file_sys/program_metadata.cpp index 382fb418f..37c79f264 100755 --- a/src/core/file_sys/program_metadata.cpp +++ b/src/core/file_sys/program_metadata.cpp @@ -7,7 +7,7 @@ #include "common/logging/log.h" #include "common/scope_exit.h" #include "core/file_sys/program_metadata.h" -#include "core/file_sys/vfs.h" +#include "core/file_sys/vfs/vfs.h" #include "core/loader/loader.h" namespace FileSys { diff --git a/src/core/file_sys/program_metadata.h b/src/core/file_sys/program_metadata.h index 70d87ff99..c5fbbfd5a 100755 --- a/src/core/file_sys/program_metadata.h +++ b/src/core/file_sys/program_metadata.h @@ -10,7 +10,7 @@ #include "common/common_funcs.h" #include "common/common_types.h" #include "common/swap.h" -#include "core/file_sys/vfs_types.h" +#include "core/file_sys/vfs/vfs_types.h" namespace Loader { enum class ResultStatus : u16; diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp index e98cc53b5..706f03aa6 100755 --- a/src/core/file_sys/registered_cache.cpp +++ b/src/core/file_sys/registered_cache.cpp @@ -17,7 +17,7 @@ #include "core/file_sys/nca_metadata.h" #include "core/file_sys/registered_cache.h" #include "core/file_sys/submission_package.h" -#include "core/file_sys/vfs_concat.h" +#include "core/file_sys/vfs/vfs_concat.h" #include "core/loader/loader.h" namespace FileSys { diff --git a/src/core/file_sys/registered_cache.h b/src/core/file_sys/registered_cache.h index bac1a042e..81c527ea0 100755 --- a/src/core/file_sys/registered_cache.h +++ b/src/core/file_sys/registered_cache.h @@ -11,7 +11,7 @@ #include #include "common/common_types.h" #include "core/crypto/key_manager.h" -#include "core/file_sys/vfs.h" +#include "core/file_sys/vfs/vfs.h" namespace FileSys { class CNMT; diff --git a/src/core/file_sys/romfs.cpp b/src/core/file_sys/romfs.cpp index cc3d540f5..437836424 100755 --- a/src/core/file_sys/romfs.cpp +++ b/src/core/file_sys/romfs.cpp @@ -9,11 +9,11 @@ #include "common/swap.h" #include "core/file_sys/fsmitm_romfsbuild.h" #include "core/file_sys/romfs.h" -#include "core/file_sys/vfs.h" -#include "core/file_sys/vfs_cached.h" -#include "core/file_sys/vfs_concat.h" -#include "core/file_sys/vfs_offset.h" -#include "core/file_sys/vfs_vector.h" +#include "core/file_sys/vfs/vfs.h" +#include "core/file_sys/vfs/vfs_cached.h" +#include "core/file_sys/vfs/vfs_concat.h" +#include "core/file_sys/vfs/vfs_offset.h" +#include "core/file_sys/vfs/vfs_vector.h" namespace FileSys { namespace { diff --git a/src/core/file_sys/romfs.h b/src/core/file_sys/romfs.h index 1835836db..ccfefee51 100755 --- a/src/core/file_sys/romfs.h +++ b/src/core/file_sys/romfs.h @@ -3,7 +3,7 @@ #pragma once -#include "core/file_sys/vfs.h" +#include "core/file_sys/vfs/vfs.h" namespace FileSys { diff --git a/src/core/file_sys/romfs_factory.h b/src/core/file_sys/romfs_factory.h index d01025fb5..e59bd3ab0 100755 --- a/src/core/file_sys/romfs_factory.h +++ b/src/core/file_sys/romfs_factory.h @@ -6,7 +6,7 @@ #include #include "common/common_types.h" -#include "core/file_sys/vfs_types.h" +#include "core/file_sys/vfs/vfs_types.h" #include "core/hle/result.h" namespace Loader { diff --git a/src/core/file_sys/savedata_factory.cpp b/src/core/file_sys/savedata_factory.cpp index 668a82f23..aa2f7e8f4 100755 --- a/src/core/file_sys/savedata_factory.cpp +++ b/src/core/file_sys/savedata_factory.cpp @@ -8,7 +8,7 @@ #include "common/uuid.h" #include "core/core.h" #include "core/file_sys/savedata_factory.h" -#include "core/file_sys/vfs.h" +#include "core/file_sys/vfs/vfs.h" namespace FileSys { diff --git a/src/core/file_sys/savedata_factory.h b/src/core/file_sys/savedata_factory.h index 2f1047d43..2134eb405 100755 --- a/src/core/file_sys/savedata_factory.h +++ b/src/core/file_sys/savedata_factory.h @@ -7,7 +7,7 @@ #include #include "common/common_funcs.h" #include "common/common_types.h" -#include "core/file_sys/vfs.h" +#include "core/file_sys/vfs/vfs.h" #include "core/hle/result.h" namespace Core { diff --git a/src/core/file_sys/sdmc_factory.cpp b/src/core/file_sys/sdmc_factory.cpp index b044817bc..75ed7585a 100755 --- a/src/core/file_sys/sdmc_factory.cpp +++ b/src/core/file_sys/sdmc_factory.cpp @@ -4,7 +4,7 @@ #include #include "core/file_sys/registered_cache.h" #include "core/file_sys/sdmc_factory.h" -#include "core/file_sys/vfs.h" +#include "core/file_sys/vfs/vfs.h" #include "core/file_sys/xts_archive.h" namespace FileSys { diff --git a/src/core/file_sys/sdmc_factory.h b/src/core/file_sys/sdmc_factory.h index 8dc65fa13..d1c170670 100755 --- a/src/core/file_sys/sdmc_factory.h +++ b/src/core/file_sys/sdmc_factory.h @@ -4,7 +4,7 @@ #pragma once #include -#include "core/file_sys/vfs_types.h" +#include "core/file_sys/vfs/vfs_types.h" #include "core/hle/result.h" namespace FileSys { diff --git a/src/core/file_sys/submission_package.h b/src/core/file_sys/submission_package.h index e6031f924..d10980fa5 100755 --- a/src/core/file_sys/submission_package.h +++ b/src/core/file_sys/submission_package.h @@ -9,7 +9,7 @@ #include #include "common/common_types.h" #include "core/file_sys/nca_metadata.h" -#include "core/file_sys/vfs.h" +#include "core/file_sys/vfs/vfs.h" namespace Core::Crypto { class KeyManager; diff --git a/src/core/file_sys/system_archive/mii_model.cpp b/src/core/file_sys/system_archive/mii_model.cpp index a7fefc40e..b67c21218 100755 --- a/src/core/file_sys/system_archive/mii_model.cpp +++ b/src/core/file_sys/system_archive/mii_model.cpp @@ -2,7 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "core/file_sys/system_archive/mii_model.h" -#include "core/file_sys/vfs_vector.h" +#include "core/file_sys/vfs/vfs_vector.h" namespace FileSys::SystemArchive { diff --git a/src/core/file_sys/system_archive/mii_model.h b/src/core/file_sys/system_archive/mii_model.h index ee12da595..9ff460290 100755 --- a/src/core/file_sys/system_archive/mii_model.h +++ b/src/core/file_sys/system_archive/mii_model.h @@ -3,7 +3,7 @@ #pragma once -#include "core/file_sys/vfs_types.h" +#include "core/file_sys/vfs/vfs_types.h" namespace FileSys::SystemArchive { diff --git a/src/core/file_sys/system_archive/ng_word.cpp b/src/core/file_sys/system_archive/ng_word.cpp index cbcd19f13..0f3af1684 100755 --- a/src/core/file_sys/system_archive/ng_word.cpp +++ b/src/core/file_sys/system_archive/ng_word.cpp @@ -4,7 +4,7 @@ #include #include "common/common_types.h" #include "core/file_sys/system_archive/ng_word.h" -#include "core/file_sys/vfs_vector.h" +#include "core/file_sys/vfs/vfs_vector.h" namespace FileSys::SystemArchive { diff --git a/src/core/file_sys/system_archive/ng_word.h b/src/core/file_sys/system_archive/ng_word.h index e00fedcfe..821b5c001 100755 --- a/src/core/file_sys/system_archive/ng_word.h +++ b/src/core/file_sys/system_archive/ng_word.h @@ -3,7 +3,7 @@ #pragma once -#include "core/file_sys/vfs_types.h" +#include "core/file_sys/vfs/vfs_types.h" namespace FileSys::SystemArchive { diff --git a/src/core/file_sys/system_archive/shared_font.cpp b/src/core/file_sys/system_archive/shared_font.cpp index 0b00f1e88..e378adcb3 100755 --- a/src/core/file_sys/system_archive/shared_font.cpp +++ b/src/core/file_sys/system_archive/shared_font.cpp @@ -8,7 +8,7 @@ #include "core/file_sys/system_archive/data/font_nintendo_extended.h" #include "core/file_sys/system_archive/data/font_standard.h" #include "core/file_sys/system_archive/shared_font.h" -#include "core/file_sys/vfs_vector.h" +#include "core/file_sys/vfs/vfs_vector.h" #include "core/hle/service/ns/iplatform_service_manager.h" namespace FileSys::SystemArchive { diff --git a/src/core/file_sys/system_archive/shared_font.h b/src/core/file_sys/system_archive/shared_font.h index 42acc5613..27d58ae3a 100755 --- a/src/core/file_sys/system_archive/shared_font.h +++ b/src/core/file_sys/system_archive/shared_font.h @@ -3,7 +3,7 @@ #pragma once -#include "core/file_sys/vfs_types.h" +#include "core/file_sys/vfs/vfs_types.h" namespace FileSys::SystemArchive { diff --git a/src/core/file_sys/system_archive/system_archive.h b/src/core/file_sys/system_archive/system_archive.h index 149f8974b..4de1c4a48 100755 --- a/src/core/file_sys/system_archive/system_archive.h +++ b/src/core/file_sys/system_archive/system_archive.h @@ -4,7 +4,7 @@ #pragma once #include "common/common_types.h" -#include "core/file_sys/vfs_types.h" +#include "core/file_sys/vfs/vfs_types.h" namespace FileSys::SystemArchive { diff --git a/src/core/file_sys/system_archive/system_version.cpp b/src/core/file_sys/system_archive/system_version.cpp index 1152b2731..964169fd8 100755 --- a/src/core/file_sys/system_archive/system_version.cpp +++ b/src/core/file_sys/system_archive/system_version.cpp @@ -3,7 +3,7 @@ #include "common/logging/log.h" #include "core/file_sys/system_archive/system_version.h" -#include "core/file_sys/vfs_vector.h" +#include "core/file_sys/vfs/vfs_vector.h" #include "core/hle/api_version.h" namespace FileSys::SystemArchive { diff --git a/src/core/file_sys/system_archive/system_version.h b/src/core/file_sys/system_archive/system_version.h index f3247755c..85f778c74 100755 --- a/src/core/file_sys/system_archive/system_version.h +++ b/src/core/file_sys/system_archive/system_version.h @@ -4,7 +4,7 @@ #pragma once #include -#include "core/file_sys/vfs_types.h" +#include "core/file_sys/vfs/vfs_types.h" namespace FileSys::SystemArchive { diff --git a/src/core/file_sys/system_archive/time_zone_binary.cpp b/src/core/file_sys/system_archive/time_zone_binary.cpp index d4fb1d983..2069032e2 100755 --- a/src/core/file_sys/system_archive/time_zone_binary.cpp +++ b/src/core/file_sys/system_archive/time_zone_binary.cpp @@ -5,7 +5,7 @@ #include "common/swap.h" #include "core/file_sys/system_archive/time_zone_binary.h" -#include "core/file_sys/vfs_vector.h" +#include "core/file_sys/vfs/vfs_vector.h" #include "nx_tzdb.h" diff --git a/src/core/file_sys/system_archive/time_zone_binary.h b/src/core/file_sys/system_archive/time_zone_binary.h index 6a8a73255..6ca1fdf8d 100755 --- a/src/core/file_sys/system_archive/time_zone_binary.h +++ b/src/core/file_sys/system_archive/time_zone_binary.h @@ -3,7 +3,7 @@ #pragma once -#include "core/file_sys/vfs_types.h" +#include "core/file_sys/vfs/vfs_types.h" namespace FileSys::SystemArchive { diff --git a/src/core/file_sys/vfs/vfs.cpp b/src/core/file_sys/vfs/vfs.cpp new file mode 100755 index 000000000..a04292760 --- /dev/null +++ b/src/core/file_sys/vfs/vfs.cpp @@ -0,0 +1,551 @@ +// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include "common/fs/path_util.h" +#include "core/file_sys/vfs/vfs.h" + +namespace FileSys { + +VfsFilesystem::VfsFilesystem(VirtualDir root_) : root(std::move(root_)) {} + +VfsFilesystem::~VfsFilesystem() = default; + +std::string VfsFilesystem::GetName() const { + return root->GetName(); +} + +bool VfsFilesystem::IsReadable() const { + return root->IsReadable(); +} + +bool VfsFilesystem::IsWritable() const { + return root->IsWritable(); +} + +VfsEntryType VfsFilesystem::GetEntryType(std::string_view path_) const { + const auto path = Common::FS::SanitizePath(path_); + if (root->GetFileRelative(path) != nullptr) + return VfsEntryType::File; + if (root->GetDirectoryRelative(path) != nullptr) + return VfsEntryType::Directory; + + return VfsEntryType::None; +} + +VirtualFile VfsFilesystem::OpenFile(std::string_view path_, OpenMode perms) { + const auto path = Common::FS::SanitizePath(path_); + return root->GetFileRelative(path); +} + +VirtualFile VfsFilesystem::CreateFile(std::string_view path_, OpenMode perms) { + const auto path = Common::FS::SanitizePath(path_); + return root->CreateFileRelative(path); +} + +VirtualFile VfsFilesystem::CopyFile(std::string_view old_path_, std::string_view new_path_) { + const auto old_path = Common::FS::SanitizePath(old_path_); + const auto new_path = Common::FS::SanitizePath(new_path_); + + // VfsDirectory impls are only required to implement copy across the current directory. + if (Common::FS::GetParentPath(old_path) == Common::FS::GetParentPath(new_path)) { + if (!root->Copy(Common::FS::GetFilename(old_path), Common::FS::GetFilename(new_path))) + return nullptr; + return OpenFile(new_path, OpenMode::ReadWrite); + } + + // Do it using RawCopy. Non-default impls are encouraged to optimize this. + const auto old_file = OpenFile(old_path, OpenMode::Read); + if (old_file == nullptr) + return nullptr; + auto new_file = OpenFile(new_path, OpenMode::Read); + if (new_file != nullptr) + return nullptr; + new_file = CreateFile(new_path, OpenMode::Write); + if (new_file == nullptr) + return nullptr; + if (!VfsRawCopy(old_file, new_file)) + return nullptr; + return new_file; +} + +VirtualFile VfsFilesystem::MoveFile(std::string_view old_path, std::string_view new_path) { + const auto sanitized_old_path = Common::FS::SanitizePath(old_path); + const auto sanitized_new_path = Common::FS::SanitizePath(new_path); + + // Again, non-default impls are highly encouraged to provide a more optimized version of this. + auto out = CopyFile(sanitized_old_path, sanitized_new_path); + if (out == nullptr) + return nullptr; + if (DeleteFile(sanitized_old_path)) + return out; + return nullptr; +} + +bool VfsFilesystem::DeleteFile(std::string_view path_) { + const auto path = Common::FS::SanitizePath(path_); + auto parent = OpenDirectory(Common::FS::GetParentPath(path), OpenMode::Write); + if (parent == nullptr) + return false; + return parent->DeleteFile(Common::FS::GetFilename(path)); +} + +VirtualDir VfsFilesystem::OpenDirectory(std::string_view path_, OpenMode perms) { + const auto path = Common::FS::SanitizePath(path_); + return root->GetDirectoryRelative(path); +} + +VirtualDir VfsFilesystem::CreateDirectory(std::string_view path_, OpenMode perms) { + const auto path = Common::FS::SanitizePath(path_); + return root->CreateDirectoryRelative(path); +} + +VirtualDir VfsFilesystem::CopyDirectory(std::string_view old_path_, std::string_view new_path_) { + const auto old_path = Common::FS::SanitizePath(old_path_); + const auto new_path = Common::FS::SanitizePath(new_path_); + + // Non-default impls are highly encouraged to provide a more optimized version of this. + auto old_dir = OpenDirectory(old_path, OpenMode::Read); + if (old_dir == nullptr) + return nullptr; + auto new_dir = OpenDirectory(new_path, OpenMode::Read); + if (new_dir != nullptr) + return nullptr; + new_dir = CreateDirectory(new_path, OpenMode::Write); + if (new_dir == nullptr) + return nullptr; + + for (const auto& file : old_dir->GetFiles()) { + const auto x = CopyFile(old_path + '/' + file->GetName(), new_path + '/' + file->GetName()); + if (x == nullptr) + return nullptr; + } + + for (const auto& dir : old_dir->GetSubdirectories()) { + const auto x = + CopyDirectory(old_path + '/' + dir->GetName(), new_path + '/' + dir->GetName()); + if (x == nullptr) + return nullptr; + } + + return new_dir; +} + +VirtualDir VfsFilesystem::MoveDirectory(std::string_view old_path, std::string_view new_path) { + const auto sanitized_old_path = Common::FS::SanitizePath(old_path); + const auto sanitized_new_path = Common::FS::SanitizePath(new_path); + + // Non-default impls are highly encouraged to provide a more optimized version of this. + auto out = CopyDirectory(sanitized_old_path, sanitized_new_path); + if (out == nullptr) + return nullptr; + if (DeleteDirectory(sanitized_old_path)) + return out; + return nullptr; +} + +bool VfsFilesystem::DeleteDirectory(std::string_view path_) { + const auto path = Common::FS::SanitizePath(path_); + auto parent = OpenDirectory(Common::FS::GetParentPath(path), OpenMode::Write); + if (parent == nullptr) + return false; + return parent->DeleteSubdirectoryRecursive(Common::FS::GetFilename(path)); +} + +VfsFile::~VfsFile() = default; + +std::string VfsFile::GetExtension() const { + return std::string(Common::FS::GetExtensionFromFilename(GetName())); +} + +VfsDirectory::~VfsDirectory() = default; + +std::optional VfsFile::ReadByte(std::size_t offset) const { + u8 out{}; + const std::size_t size = Read(&out, sizeof(u8), offset); + if (size == 1) { + return out; + } + + return std::nullopt; +} + +std::vector VfsFile::ReadBytes(std::size_t size, std::size_t offset) const { + std::vector out(size); + std::size_t read_size = Read(out.data(), size, offset); + out.resize(read_size); + return out; +} + +std::vector VfsFile::ReadAllBytes() const { + return ReadBytes(GetSize()); +} + +bool VfsFile::WriteByte(u8 data, std::size_t offset) { + return Write(&data, 1, offset) == 1; +} + +std::size_t VfsFile::WriteBytes(const std::vector& data, std::size_t offset) { + return Write(data.data(), data.size(), offset); +} + +std::string VfsFile::GetFullPath() const { + if (GetContainingDirectory() == nullptr) + return '/' + GetName(); + + return GetContainingDirectory()->GetFullPath() + '/' + GetName(); +} + +VirtualFile VfsDirectory::GetFileRelative(std::string_view path) const { + auto vec = Common::FS::SplitPathComponents(path); + if (vec.empty()) { + return nullptr; + } + + if (vec.size() == 1) { + return GetFile(vec[0]); + } + + auto dir = GetSubdirectory(vec[0]); + for (std::size_t component = 1; component < vec.size() - 1; ++component) { + if (dir == nullptr) { + return nullptr; + } + + dir = dir->GetSubdirectory(vec[component]); + } + + if (dir == nullptr) { + return nullptr; + } + + return dir->GetFile(vec.back()); +} + +VirtualFile VfsDirectory::GetFileAbsolute(std::string_view path) const { + if (IsRoot()) { + return GetFileRelative(path); + } + + return GetParentDirectory()->GetFileAbsolute(path); +} + +VirtualDir VfsDirectory::GetDirectoryRelative(std::string_view path) const { + auto vec = Common::FS::SplitPathComponents(path); + if (vec.empty()) { + // TODO(DarkLordZach): Return this directory if path is '/' or similar. Can't currently + // because of const-ness + return nullptr; + } + + auto dir = GetSubdirectory(vec[0]); + for (std::size_t component = 1; component < vec.size(); ++component) { + if (dir == nullptr) { + return nullptr; + } + + dir = dir->GetSubdirectory(vec[component]); + } + + return dir; +} + +VirtualDir VfsDirectory::GetDirectoryAbsolute(std::string_view path) const { + if (IsRoot()) { + return GetDirectoryRelative(path); + } + + return GetParentDirectory()->GetDirectoryAbsolute(path); +} + +VirtualFile VfsDirectory::GetFile(std::string_view name) const { + const auto& files = GetFiles(); + const auto iter = std::find_if(files.begin(), files.end(), + [&name](const auto& file1) { return name == file1->GetName(); }); + return iter == files.end() ? nullptr : *iter; +} + +FileTimeStampRaw VfsDirectory::GetFileTimeStamp([[maybe_unused]] std::string_view path) const { + return {}; +} + +VirtualDir VfsDirectory::GetSubdirectory(std::string_view name) const { + const auto& subs = GetSubdirectories(); + const auto iter = std::find_if(subs.begin(), subs.end(), + [&name](const auto& file1) { return name == file1->GetName(); }); + return iter == subs.end() ? nullptr : *iter; +} + +bool VfsDirectory::IsRoot() const { + return GetParentDirectory() == nullptr; +} + +std::size_t VfsDirectory::GetSize() const { + const auto& files = GetFiles(); + const auto sum_sizes = [](const auto& range) { + return std::accumulate(range.begin(), range.end(), 0ULL, + [](const auto& f1, const auto& f2) { return f1 + f2->GetSize(); }); + }; + + const auto file_total = sum_sizes(files); + const auto& sub_dir = GetSubdirectories(); + const auto subdir_total = sum_sizes(sub_dir); + + return file_total + subdir_total; +} + +VirtualFile VfsDirectory::CreateFileRelative(std::string_view path) { + auto vec = Common::FS::SplitPathComponents(path); + if (vec.empty()) { + return nullptr; + } + + if (vec.size() == 1) { + return CreateFile(vec[0]); + } + + auto dir = GetSubdirectory(vec[0]); + if (dir == nullptr) { + dir = CreateSubdirectory(vec[0]); + if (dir == nullptr) { + return nullptr; + } + } + + return dir->CreateFileRelative(Common::FS::GetPathWithoutTop(path)); +} + +VirtualFile VfsDirectory::CreateFileAbsolute(std::string_view path) { + if (IsRoot()) { + return CreateFileRelative(path); + } + + return GetParentDirectory()->CreateFileAbsolute(path); +} + +VirtualDir VfsDirectory::CreateDirectoryRelative(std::string_view path) { + auto vec = Common::FS::SplitPathComponents(path); + if (vec.empty()) { + return nullptr; + } + + if (vec.size() == 1) { + return CreateSubdirectory(vec[0]); + } + + auto dir = GetSubdirectory(vec[0]); + if (dir == nullptr) { + dir = CreateSubdirectory(vec[0]); + if (dir == nullptr) { + return nullptr; + } + } + + return dir->CreateDirectoryRelative(Common::FS::GetPathWithoutTop(path)); +} + +VirtualDir VfsDirectory::CreateDirectoryAbsolute(std::string_view path) { + if (IsRoot()) { + return CreateDirectoryRelative(path); + } + + return GetParentDirectory()->CreateDirectoryAbsolute(path); +} + +bool VfsDirectory::DeleteSubdirectoryRecursive(std::string_view name) { + auto dir = GetSubdirectory(name); + if (dir == nullptr) { + return false; + } + + bool success = true; + for (const auto& file : dir->GetFiles()) { + if (!DeleteFile(file->GetName())) { + success = false; + } + } + + for (const auto& sdir : dir->GetSubdirectories()) { + if (!dir->DeleteSubdirectoryRecursive(sdir->GetName())) { + success = false; + } + } + + return success; +} + +bool VfsDirectory::CleanSubdirectoryRecursive(std::string_view name) { + auto dir = GetSubdirectory(name); + if (dir == nullptr) { + return false; + } + + bool success = true; + for (const auto& file : dir->GetFiles()) { + if (!dir->DeleteFile(file->GetName())) { + success = false; + } + } + + for (const auto& sdir : dir->GetSubdirectories()) { + if (!dir->DeleteSubdirectoryRecursive(sdir->GetName())) { + success = false; + } + } + + return success; +} + +bool VfsDirectory::Copy(std::string_view src, std::string_view dest) { + const auto f1 = GetFile(src); + auto f2 = CreateFile(dest); + if (f1 == nullptr || f2 == nullptr) { + return false; + } + + if (!f2->Resize(f1->GetSize())) { + DeleteFile(dest); + return false; + } + + return f2->WriteBytes(f1->ReadAllBytes()) == f1->GetSize(); +} + +std::map> VfsDirectory::GetEntries() const { + std::map> out; + for (const auto& dir : GetSubdirectories()) + out.emplace(dir->GetName(), VfsEntryType::Directory); + for (const auto& file : GetFiles()) + out.emplace(file->GetName(), VfsEntryType::File); + return out; +} + +std::string VfsDirectory::GetFullPath() const { + if (IsRoot()) + return GetName(); + + return GetParentDirectory()->GetFullPath() + '/' + GetName(); +} + +bool ReadOnlyVfsDirectory::IsWritable() const { + return false; +} + +bool ReadOnlyVfsDirectory::IsReadable() const { + return true; +} + +VirtualDir ReadOnlyVfsDirectory::CreateSubdirectory(std::string_view name) { + return nullptr; +} + +VirtualFile ReadOnlyVfsDirectory::CreateFile(std::string_view name) { + return nullptr; +} + +VirtualFile ReadOnlyVfsDirectory::CreateFileAbsolute(std::string_view path) { + return nullptr; +} + +VirtualFile ReadOnlyVfsDirectory::CreateFileRelative(std::string_view path) { + return nullptr; +} + +VirtualDir ReadOnlyVfsDirectory::CreateDirectoryAbsolute(std::string_view path) { + return nullptr; +} + +VirtualDir ReadOnlyVfsDirectory::CreateDirectoryRelative(std::string_view path) { + return nullptr; +} + +bool ReadOnlyVfsDirectory::DeleteSubdirectory(std::string_view name) { + return false; +} + +bool ReadOnlyVfsDirectory::DeleteSubdirectoryRecursive(std::string_view name) { + return false; +} + +bool ReadOnlyVfsDirectory::CleanSubdirectoryRecursive(std::string_view name) { + return false; +} + +bool ReadOnlyVfsDirectory::DeleteFile(std::string_view name) { + return false; +} + +bool ReadOnlyVfsDirectory::Rename(std::string_view name) { + return false; +} + +bool DeepEquals(const VirtualFile& file1, const VirtualFile& file2, std::size_t block_size) { + if (file1->GetSize() != file2->GetSize()) + return false; + + std::vector f1_v(block_size); + std::vector f2_v(block_size); + for (std::size_t i = 0; i < file1->GetSize(); i += block_size) { + auto f1_vs = file1->Read(f1_v.data(), block_size, i); + auto f2_vs = file2->Read(f2_v.data(), block_size, i); + + if (f1_vs != f2_vs) + return false; + auto iters = std::mismatch(f1_v.begin(), f1_v.end(), f2_v.begin(), f2_v.end()); + if (iters.first != f1_v.end() && iters.second != f2_v.end()) + return false; + } + + return true; +} + +bool VfsRawCopy(const VirtualFile& src, const VirtualFile& dest, std::size_t block_size) { + if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable()) + return false; + if (!dest->Resize(src->GetSize())) + return false; + + std::vector temp(std::min(block_size, src->GetSize())); + for (std::size_t i = 0; i < src->GetSize(); i += block_size) { + const auto read = std::min(block_size, src->GetSize() - i); + + if (src->Read(temp.data(), read, i) != read) { + return false; + } + + if (dest->Write(temp.data(), read, i) != read) { + return false; + } + } + + return true; +} + +bool VfsRawCopyD(const VirtualDir& src, const VirtualDir& dest, std::size_t block_size) { + if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable()) + return false; + + for (const auto& file : src->GetFiles()) { + const auto out = dest->CreateFile(file->GetName()); + if (!VfsRawCopy(file, out, block_size)) + return false; + } + + for (const auto& dir : src->GetSubdirectories()) { + const auto out = dest->CreateSubdirectory(dir->GetName()); + if (!VfsRawCopyD(dir, out, block_size)) + return false; + } + + return true; +} + +VirtualDir GetOrCreateDirectoryRelative(const VirtualDir& rel, std::string_view path) { + const auto res = rel->GetDirectoryRelative(path); + if (res == nullptr) + return rel->CreateDirectoryRelative(path); + return res; +} +} // namespace FileSys diff --git a/src/core/file_sys/vfs/vfs.h b/src/core/file_sys/vfs/vfs.h new file mode 100755 index 000000000..f846a9669 --- /dev/null +++ b/src/core/file_sys/vfs/vfs.h @@ -0,0 +1,326 @@ +// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "core/file_sys/fs_filesystem.h" +#include "core/file_sys/vfs/vfs_types.h" + +namespace FileSys { + +// An enumeration representing what can be at the end of a path in a VfsFilesystem +enum class VfsEntryType { + None, + File, + Directory, +}; + +// A class representing an abstract filesystem. A default implementation given the root VirtualDir +// is provided for convenience, but if the Vfs implementation has any additional state or +// functionality, they will need to override. +class VfsFilesystem { +public: + YUZU_NON_COPYABLE(VfsFilesystem); + YUZU_NON_MOVEABLE(VfsFilesystem); + + explicit VfsFilesystem(VirtualDir root); + virtual ~VfsFilesystem(); + + // Gets the friendly name for the filesystem. + virtual std::string GetName() const; + + // Return whether or not the user has read permissions on this filesystem. + virtual bool IsReadable() const; + // Return whether or not the user has write permission on this filesystem. + virtual bool IsWritable() const; + + // Determine if the entry at path is non-existent, a file, or a directory. + virtual VfsEntryType GetEntryType(std::string_view path) const; + + // Opens the file with path relative to root. If it doesn't exist, returns nullptr. + virtual VirtualFile OpenFile(std::string_view path, OpenMode perms); + // Creates a new, empty file at path + virtual VirtualFile CreateFile(std::string_view path, OpenMode perms); + // Copies the file from old_path to new_path, returning the new file on success and nullptr on + // failure. + virtual VirtualFile CopyFile(std::string_view old_path, std::string_view new_path); + // Moves the file from old_path to new_path, returning the moved file on success and nullptr on + // failure. + virtual VirtualFile MoveFile(std::string_view old_path, std::string_view new_path); + // Deletes the file with path relative to root, returning true on success. + virtual bool DeleteFile(std::string_view path); + + // Opens the directory with path relative to root. If it doesn't exist, returns nullptr. + virtual VirtualDir OpenDirectory(std::string_view path, OpenMode perms); + // Creates a new, empty directory at path + virtual VirtualDir CreateDirectory(std::string_view path, OpenMode perms); + // Copies the directory from old_path to new_path, returning the new directory on success and + // nullptr on failure. + virtual VirtualDir CopyDirectory(std::string_view old_path, std::string_view new_path); + // Moves the directory from old_path to new_path, returning the moved directory on success and + // nullptr on failure. + virtual VirtualDir MoveDirectory(std::string_view old_path, std::string_view new_path); + // Deletes the directory with path relative to root, returning true on success. + virtual bool DeleteDirectory(std::string_view path); + +protected: + // Root directory in default implementation. + VirtualDir root; +}; + +// A class representing a file in an abstract filesystem. +class VfsFile { +public: + YUZU_NON_COPYABLE(VfsFile); + YUZU_NON_MOVEABLE(VfsFile); + + VfsFile() = default; + virtual ~VfsFile(); + + // Retrieves the file name. + virtual std::string GetName() const = 0; + // Retrieves the extension of the file name. + virtual std::string GetExtension() const; + // Retrieves the size of the file. + virtual std::size_t GetSize() const = 0; + // Resizes the file to new_size. Returns whether or not the operation was successful. + virtual bool Resize(std::size_t new_size) = 0; + // Gets a pointer to the directory containing this file, returning nullptr if there is none. + virtual VirtualDir GetContainingDirectory() const = 0; + + // Returns whether or not the file can be written to. + virtual bool IsWritable() const = 0; + // Returns whether or not the file can be read from. + virtual bool IsReadable() const = 0; + + // The primary method of reading from the file. Reads length bytes into data starting at offset + // into file. Returns number of bytes successfully read. + virtual std::size_t Read(u8* data, std::size_t length, std::size_t offset = 0) const = 0; + // The primary method of writing to the file. Writes length bytes from data starting at offset + // into file. Returns number of bytes successfully written. + virtual std::size_t Write(const u8* data, std::size_t length, std::size_t offset = 0) = 0; + + // Reads exactly one byte at the offset provided, returning std::nullopt on error. + virtual std::optional ReadByte(std::size_t offset = 0) const; + // Reads size bytes starting at offset in file into a vector. + virtual std::vector ReadBytes(std::size_t size, std::size_t offset = 0) const; + // Reads all the bytes from the file into a vector. Equivalent to 'file->Read(file->GetSize(), + // 0)' + virtual std::vector ReadAllBytes() const; + + // Reads an array of type T, size number_elements starting at offset. + // Returns the number of bytes (sizeof(T)*number_elements) read successfully. + template + std::size_t ReadArray(T* data, std::size_t number_elements, std::size_t offset = 0) const { + static_assert(std::is_trivially_copyable_v, "Data type must be trivially copyable."); + + return Read(reinterpret_cast(data), number_elements * sizeof(T), offset); + } + + // Reads size bytes into the memory starting at data starting at offset into the file. + // Returns the number of bytes read successfully. + template + std::size_t ReadBytes(T* data, std::size_t size, std::size_t offset = 0) const { + static_assert(std::is_trivially_copyable_v, "Data type must be trivially copyable."); + return Read(reinterpret_cast(data), size, offset); + } + + // Reads one object of type T starting at offset in file. + // Returns the number of bytes read successfully (sizeof(T)). + template + std::size_t ReadObject(T* data, std::size_t offset = 0) const { + static_assert(std::is_trivially_copyable_v, "Data type must be trivially copyable."); + return Read(reinterpret_cast(data), sizeof(T), offset); + } + + // Writes exactly one byte to offset in file and returns whether or not the byte was written + // successfully. + virtual bool WriteByte(u8 data, std::size_t offset = 0); + // Writes a vector of bytes to offset in file and returns the number of bytes successfully + // written. + virtual std::size_t WriteBytes(const std::vector& data, std::size_t offset = 0); + + // Writes an array of type T, size number_elements to offset in file. + // Returns the number of bytes (sizeof(T)*number_elements) written successfully. + template + std::size_t WriteArray(const T* data, std::size_t number_elements, std::size_t offset = 0) { + static_assert(std::is_trivially_copyable_v, "Data type must be trivially copyable."); + return Write(reinterpret_cast(data), number_elements * sizeof(T), offset); + } + + // Writes size bytes starting at memory location data to offset in file. + // Returns the number of bytes written successfully. + template + std::size_t WriteBytes(const T* data, std::size_t size, std::size_t offset = 0) { + static_assert(std::is_trivially_copyable_v, "Data type must be trivially copyable."); + return Write(reinterpret_cast(data), size, offset); + } + + // Writes one object of type T to offset in file. + // Returns the number of bytes written successfully (sizeof(T)). + template + std::size_t WriteObject(const T& data, std::size_t offset = 0) { + static_assert(std::is_trivially_copyable_v, "Data type must be trivially copyable."); + return Write(reinterpret_cast(&data), sizeof(T), offset); + } + + // Renames the file to name. Returns whether or not the operation was successful. + virtual bool Rename(std::string_view name) = 0; + + // Returns the full path of this file as a string, recursively + virtual std::string GetFullPath() const; +}; + +// A class representing a directory in an abstract filesystem. +class VfsDirectory { +public: + YUZU_NON_COPYABLE(VfsDirectory); + YUZU_NON_MOVEABLE(VfsDirectory); + + VfsDirectory() = default; + virtual ~VfsDirectory(); + + // Retrieves the file located at path as if the current directory was root. Returns nullptr if + // not found. + virtual VirtualFile GetFileRelative(std::string_view path) const; + // Calls GetFileRelative(path) on the root of the current directory. + virtual VirtualFile GetFileAbsolute(std::string_view path) const; + + // Retrieves the directory located at path as if the current directory was root. Returns nullptr + // if not found. + virtual VirtualDir GetDirectoryRelative(std::string_view path) const; + // Calls GetDirectoryRelative(path) on the root of the current directory. + virtual VirtualDir GetDirectoryAbsolute(std::string_view path) const; + + // Returns a vector containing all of the files in this directory. + virtual std::vector GetFiles() const = 0; + // Returns the file with filename matching name. Returns nullptr if directory doesn't have a + // file with name. + virtual VirtualFile GetFile(std::string_view name) const; + + // Returns a struct containing the file's timestamp. + virtual FileTimeStampRaw GetFileTimeStamp(std::string_view path) const; + + // Returns a vector containing all of the subdirectories in this directory. + virtual std::vector GetSubdirectories() const = 0; + // Returns the directory with name matching name. Returns nullptr if directory doesn't have a + // directory with name. + virtual VirtualDir GetSubdirectory(std::string_view name) const; + + // Returns whether or not the directory can be written to. + virtual bool IsWritable() const = 0; + // Returns whether of not the directory can be read from. + virtual bool IsReadable() const = 0; + + // Returns whether or not the directory is the root of the current file tree. + virtual bool IsRoot() const; + + // Returns the name of the directory. + virtual std::string GetName() const = 0; + // Returns the total size of all files and subdirectories in this directory. + virtual std::size_t GetSize() const; + // Returns the parent directory of this directory. Returns nullptr if this directory is root or + // has no parent. + virtual VirtualDir GetParentDirectory() const = 0; + + // Creates a new subdirectory with name name. Returns a pointer to the new directory or nullptr + // if the operation failed. + virtual VirtualDir CreateSubdirectory(std::string_view name) = 0; + // Creates a new file with name name. Returns a pointer to the new file or nullptr if the + // operation failed. + virtual VirtualFile CreateFile(std::string_view name) = 0; + + // Creates a new file at the path relative to this directory. Also creates directories if + // they do not exist and is supported by this implementation. Returns nullptr on any failure. + virtual VirtualFile CreateFileRelative(std::string_view path); + + // Creates a new file at the path relative to root of this directory. Also creates directories + // if they do not exist and is supported by this implementation. Returns nullptr on any failure. + virtual VirtualFile CreateFileAbsolute(std::string_view path); + + // Creates a new directory at the path relative to this directory. Also creates directories if + // they do not exist and is supported by this implementation. Returns nullptr on any failure. + virtual VirtualDir CreateDirectoryRelative(std::string_view path); + + // Creates a new directory at the path relative to root of this directory. Also creates + // directories if they do not exist and is supported by this implementation. Returns nullptr on + // any failure. + virtual VirtualDir CreateDirectoryAbsolute(std::string_view path); + + // Deletes the subdirectory with the given name and returns true on success. + virtual bool DeleteSubdirectory(std::string_view name) = 0; + + // Deletes all subdirectories and files within the provided directory and then deletes + // the directory itself. Returns true on success. + virtual bool DeleteSubdirectoryRecursive(std::string_view name); + + // Deletes all subdirectories and files within the provided directory. + // Unlike DeleteSubdirectoryRecursive, this does not delete the provided directory. + virtual bool CleanSubdirectoryRecursive(std::string_view name); + + // Returns whether or not the file with name name was deleted successfully. + virtual bool DeleteFile(std::string_view name) = 0; + + // Returns whether or not this directory was renamed to name. + virtual bool Rename(std::string_view name) = 0; + + // Returns whether or not the file with name src was successfully copied to a new file with name + // dest. + virtual bool Copy(std::string_view src, std::string_view dest); + + // Gets all of the entries directly in the directory (files and dirs), returning a map between + // item name -> type. + virtual std::map> GetEntries() const; + + // Returns the full path of this directory as a string, recursively + virtual std::string GetFullPath() const; +}; + +// A convenience partial-implementation of VfsDirectory that stubs out methods that should only work +// if writable. This is to avoid redundant empty methods everywhere. +class ReadOnlyVfsDirectory : public VfsDirectory { +public: + bool IsWritable() const override; + bool IsReadable() const override; + VirtualDir CreateSubdirectory(std::string_view name) override; + VirtualFile CreateFile(std::string_view name) override; + VirtualFile CreateFileAbsolute(std::string_view path) override; + VirtualFile CreateFileRelative(std::string_view path) override; + VirtualDir CreateDirectoryAbsolute(std::string_view path) override; + VirtualDir CreateDirectoryRelative(std::string_view path) override; + bool DeleteSubdirectory(std::string_view name) override; + bool DeleteSubdirectoryRecursive(std::string_view name) override; + bool CleanSubdirectoryRecursive(std::string_view name) override; + bool DeleteFile(std::string_view name) override; + bool Rename(std::string_view name) override; +}; + +// Compare the two files, byte-for-byte, in increments specified by block_size +bool DeepEquals(const VirtualFile& file1, const VirtualFile& file2, + std::size_t block_size = 0x1000); + +// A method that copies the raw data between two different implementations of VirtualFile. If you +// are using the same implementation, it is probably better to use the Copy method in the parent +// directory of src/dest. +bool VfsRawCopy(const VirtualFile& src, const VirtualFile& dest, std::size_t block_size = 0x1000); + +// A method that performs a similar function to VfsRawCopy above, but instead copies entire +// directories. It suffers the same performance penalties as above and an implementation-specific +// Copy should always be preferred. +bool VfsRawCopyD(const VirtualDir& src, const VirtualDir& dest, std::size_t block_size = 0x1000); + +// Checks if the directory at path relative to rel exists. If it does, returns that. If it does not +// it attempts to create it and returns the new dir or nullptr on failure. +VirtualDir GetOrCreateDirectoryRelative(const VirtualDir& rel, std::string_view path); + +} // namespace FileSys diff --git a/src/core/file_sys/vfs/vfs_cached.cpp b/src/core/file_sys/vfs/vfs_cached.cpp new file mode 100755 index 000000000..01cd0f1e0 --- /dev/null +++ b/src/core/file_sys/vfs/vfs_cached.cpp @@ -0,0 +1,63 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/file_sys/vfs/vfs_cached.h" +#include "core/file_sys/vfs/vfs_types.h" + +namespace FileSys { + +CachedVfsDirectory::CachedVfsDirectory(VirtualDir&& source_dir) + : name(source_dir->GetName()), parent(source_dir->GetParentDirectory()) { + for (auto& dir : source_dir->GetSubdirectories()) { + dirs.emplace(dir->GetName(), std::make_shared(std::move(dir))); + } + for (auto& file : source_dir->GetFiles()) { + files.emplace(file->GetName(), std::move(file)); + } +} + +CachedVfsDirectory::~CachedVfsDirectory() = default; + +VirtualFile CachedVfsDirectory::GetFile(std::string_view file_name) const { + auto it = files.find(file_name); + if (it != files.end()) { + return it->second; + } + + return nullptr; +} + +VirtualDir CachedVfsDirectory::GetSubdirectory(std::string_view dir_name) const { + auto it = dirs.find(dir_name); + if (it != dirs.end()) { + return it->second; + } + + return nullptr; +} + +std::vector CachedVfsDirectory::GetFiles() const { + std::vector out; + for (auto& [file_name, file] : files) { + out.push_back(file); + } + return out; +} + +std::vector CachedVfsDirectory::GetSubdirectories() const { + std::vector out; + for (auto& [dir_name, dir] : dirs) { + out.push_back(dir); + } + return out; +} + +std::string CachedVfsDirectory::GetName() const { + return name; +} + +VirtualDir CachedVfsDirectory::GetParentDirectory() const { + return parent; +} + +} // namespace FileSys diff --git a/src/core/file_sys/vfs/vfs_cached.h b/src/core/file_sys/vfs/vfs_cached.h new file mode 100755 index 000000000..47dff7224 --- /dev/null +++ b/src/core/file_sys/vfs/vfs_cached.h @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include "core/file_sys/vfs/vfs.h" + +namespace FileSys { + +class CachedVfsDirectory : public ReadOnlyVfsDirectory { +public: + CachedVfsDirectory(VirtualDir&& source_directory); + + ~CachedVfsDirectory() override; + VirtualFile GetFile(std::string_view file_name) const override; + VirtualDir GetSubdirectory(std::string_view dir_name) const override; + std::vector GetFiles() const override; + std::vector GetSubdirectories() const override; + std::string GetName() const override; + VirtualDir GetParentDirectory() const override; + +private: + std::string name; + VirtualDir parent; + std::map> dirs; + std::map> files; +}; + +} // namespace FileSys diff --git a/src/core/file_sys/vfs/vfs_concat.cpp b/src/core/file_sys/vfs/vfs_concat.cpp new file mode 100755 index 000000000..b5cc9a9e9 --- /dev/null +++ b/src/core/file_sys/vfs/vfs_concat.cpp @@ -0,0 +1,192 @@ +// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include + +#include "common/assert.h" +#include "core/file_sys/vfs/vfs_concat.h" +#include "core/file_sys/vfs/vfs_static.h" + +namespace FileSys { + +ConcatenatedVfsFile::ConcatenatedVfsFile(std::string&& name_, ConcatenationMap&& concatenation_map_) + : concatenation_map(std::move(concatenation_map_)), name(std::move(name_)) { + DEBUG_ASSERT(this->VerifyContinuity()); +} + +bool ConcatenatedVfsFile::VerifyContinuity() const { + u64 last_offset = 0; + for (auto& entry : concatenation_map) { + if (entry.offset != last_offset) { + return false; + } + + last_offset = entry.offset + entry.file->GetSize(); + } + + return true; +} + +ConcatenatedVfsFile::~ConcatenatedVfsFile() = default; + +VirtualFile ConcatenatedVfsFile::MakeConcatenatedFile(std::string&& name, + std::vector&& files) { + // Fold trivial cases. + if (files.empty()) { + return nullptr; + } + if (files.size() == 1) { + return files.front(); + } + + // Make the concatenation map from the input. + std::vector concatenation_map; + concatenation_map.reserve(files.size()); + u64 last_offset = 0; + + for (auto& file : files) { + const auto size = file->GetSize(); + + concatenation_map.emplace_back(ConcatenationEntry{ + .offset = last_offset, + .file = std::move(file), + }); + + last_offset += size; + } + + return VirtualFile(new ConcatenatedVfsFile(std::move(name), std::move(concatenation_map))); +} + +VirtualFile ConcatenatedVfsFile::MakeConcatenatedFile( + u8 filler_byte, std::string&& name, std::vector>&& files) { + // Fold trivial cases. + if (files.empty()) { + return nullptr; + } + if (files.size() == 1) { + return files.begin()->second; + } + + // Make the concatenation map from the input. + std::vector concatenation_map; + + concatenation_map.reserve(files.size()); + u64 last_offset = 0; + + // Iteration of a multimap is ordered, so offset will be strictly non-decreasing. + for (auto& [offset, file] : files) { + const auto size = file->GetSize(); + + if (offset > last_offset) { + concatenation_map.emplace_back(ConcatenationEntry{ + .offset = last_offset, + .file = std::make_shared(filler_byte, offset - last_offset), + }); + } + + concatenation_map.emplace_back(ConcatenationEntry{ + .offset = offset, + .file = std::move(file), + }); + + last_offset = offset + size; + } + + return VirtualFile(new ConcatenatedVfsFile(std::move(name), std::move(concatenation_map))); +} + +std::string ConcatenatedVfsFile::GetName() const { + if (concatenation_map.empty()) { + return ""; + } + if (!name.empty()) { + return name; + } + return concatenation_map.front().file->GetName(); +} + +std::size_t ConcatenatedVfsFile::GetSize() const { + if (concatenation_map.empty()) { + return 0; + } + return concatenation_map.back().offset + concatenation_map.back().file->GetSize(); +} + +bool ConcatenatedVfsFile::Resize(std::size_t new_size) { + return false; +} + +VirtualDir ConcatenatedVfsFile::GetContainingDirectory() const { + if (concatenation_map.empty()) { + return nullptr; + } + return concatenation_map.front().file->GetContainingDirectory(); +} + +bool ConcatenatedVfsFile::IsWritable() const { + return false; +} + +bool ConcatenatedVfsFile::IsReadable() const { + return true; +} + +std::size_t ConcatenatedVfsFile::Read(u8* data, std::size_t length, std::size_t offset) const { + const ConcatenationEntry key{ + .offset = offset, + .file = nullptr, + }; + + // Read nothing if the map is empty. + if (concatenation_map.empty()) { + return 0; + } + + // Binary search to find the iterator to the first position we can check. + // It must exist, since we are not empty and are comparing unsigned integers. + auto it = std::prev(std::upper_bound(concatenation_map.begin(), concatenation_map.end(), key)); + u64 cur_length = length; + u64 cur_offset = offset; + + while (cur_length > 0 && it != concatenation_map.end()) { + // Check if we can read the file at this position. + const auto& file = it->file; + const u64 map_offset = it->offset; + const u64 file_size = file->GetSize(); + + if (cur_offset > map_offset + file_size) { + // Entirely out of bounds read. + break; + } + + // Read the file at this position. + const u64 file_seek = cur_offset - map_offset; + const u64 intended_read_size = std::min(cur_length, file_size - file_seek); + const u64 actual_read_size = + file->Read(data + (cur_offset - offset), intended_read_size, file_seek); + + // Update tracking. + cur_offset += actual_read_size; + cur_length -= actual_read_size; + it++; + + // If we encountered a short read, we're done. + if (actual_read_size < intended_read_size) { + break; + } + } + + return cur_offset - offset; +} + +std::size_t ConcatenatedVfsFile::Write(const u8* data, std::size_t length, std::size_t offset) { + return 0; +} + +bool ConcatenatedVfsFile::Rename(std::string_view new_name) { + return false; +} + +} // namespace FileSys diff --git a/src/core/file_sys/vfs/vfs_concat.h b/src/core/file_sys/vfs/vfs_concat.h new file mode 100755 index 000000000..6d12af762 --- /dev/null +++ b/src/core/file_sys/vfs/vfs_concat.h @@ -0,0 +1,57 @@ +// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include "core/file_sys/vfs/vfs.h" + +namespace FileSys { + +// Class that wraps multiple vfs files and concatenates them, making reads seamless. Currently +// read-only. +class ConcatenatedVfsFile : public VfsFile { +private: + struct ConcatenationEntry { + u64 offset; + VirtualFile file; + + auto operator<=>(const ConcatenationEntry& other) const { + return this->offset <=> other.offset; + } + }; + using ConcatenationMap = std::vector; + + explicit ConcatenatedVfsFile(std::string&& name, + std::vector&& concatenation_map); + bool VerifyContinuity() const; + +public: + ~ConcatenatedVfsFile() override; + + /// Wrapper function to allow for more efficient handling of files.size() == 0, 1 cases. + static VirtualFile MakeConcatenatedFile(std::string&& name, std::vector&& files); + + /// Convenience function that turns a map of offsets to files into a concatenated file, filling + /// gaps with a given filler byte. + static VirtualFile MakeConcatenatedFile(u8 filler_byte, std::string&& name, + std::vector>&& files); + + std::string GetName() const override; + std::size_t GetSize() const override; + bool Resize(std::size_t new_size) override; + VirtualDir GetContainingDirectory() const override; + bool IsWritable() const override; + bool IsReadable() const override; + std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override; + std::size_t Write(const u8* data, std::size_t length, std::size_t offset) override; + bool Rename(std::string_view new_name) override; + +private: + ConcatenationMap concatenation_map; + std::string name; +}; + +} // namespace FileSys diff --git a/src/core/file_sys/vfs/vfs_layered.cpp b/src/core/file_sys/vfs/vfs_layered.cpp new file mode 100755 index 000000000..47b2a3c78 --- /dev/null +++ b/src/core/file_sys/vfs/vfs_layered.cpp @@ -0,0 +1,132 @@ +// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include +#include "core/file_sys/vfs/vfs_layered.h" + +namespace FileSys { + +LayeredVfsDirectory::LayeredVfsDirectory(std::vector dirs_, std::string name_) + : dirs(std::move(dirs_)), name(std::move(name_)) {} + +LayeredVfsDirectory::~LayeredVfsDirectory() = default; + +VirtualDir LayeredVfsDirectory::MakeLayeredDirectory(std::vector dirs, + std::string name) { + if (dirs.empty()) + return nullptr; + if (dirs.size() == 1) + return dirs[0]; + + return VirtualDir(new LayeredVfsDirectory(std::move(dirs), std::move(name))); +} + +VirtualFile LayeredVfsDirectory::GetFileRelative(std::string_view path) const { + for (const auto& layer : dirs) { + const auto file = layer->GetFileRelative(path); + if (file != nullptr) + return file; + } + + return nullptr; +} + +VirtualDir LayeredVfsDirectory::GetDirectoryRelative(std::string_view path) const { + std::vector out; + for (const auto& layer : dirs) { + auto dir = layer->GetDirectoryRelative(path); + if (dir != nullptr) { + out.emplace_back(std::move(dir)); + } + } + + return MakeLayeredDirectory(std::move(out)); +} + +VirtualFile LayeredVfsDirectory::GetFile(std::string_view file_name) const { + return GetFileRelative(file_name); +} + +VirtualDir LayeredVfsDirectory::GetSubdirectory(std::string_view subdir_name) const { + return GetDirectoryRelative(subdir_name); +} + +std::string LayeredVfsDirectory::GetFullPath() const { + return dirs[0]->GetFullPath(); +} + +std::vector LayeredVfsDirectory::GetFiles() const { + std::vector out; + std::unordered_set out_names; + + for (const auto& layer : dirs) { + for (auto& file : layer->GetFiles()) { + const auto [it, is_new] = out_names.emplace(file->GetName()); + if (is_new) { + out.emplace_back(std::move(file)); + } + } + } + + return out; +} + +std::vector LayeredVfsDirectory::GetSubdirectories() const { + std::vector out; + std::unordered_set out_names; + + for (const auto& layer : dirs) { + for (const auto& sd : layer->GetSubdirectories()) { + out_names.emplace(sd->GetName()); + } + } + + out.reserve(out_names.size()); + for (const auto& subdir : out_names) { + out.emplace_back(GetSubdirectory(subdir)); + } + + return out; +} + +bool LayeredVfsDirectory::IsWritable() const { + return false; +} + +bool LayeredVfsDirectory::IsReadable() const { + return true; +} + +std::string LayeredVfsDirectory::GetName() const { + return name.empty() ? dirs[0]->GetName() : name; +} + +VirtualDir LayeredVfsDirectory::GetParentDirectory() const { + return dirs[0]->GetParentDirectory(); +} + +VirtualDir LayeredVfsDirectory::CreateSubdirectory(std::string_view subdir_name) { + return nullptr; +} + +VirtualFile LayeredVfsDirectory::CreateFile(std::string_view file_name) { + return nullptr; +} + +bool LayeredVfsDirectory::DeleteSubdirectory(std::string_view subdir_name) { + return false; +} + +bool LayeredVfsDirectory::DeleteFile(std::string_view file_name) { + return false; +} + +bool LayeredVfsDirectory::Rename(std::string_view new_name) { + name = new_name; + return true; +} + +} // namespace FileSys diff --git a/src/core/file_sys/vfs/vfs_layered.h b/src/core/file_sys/vfs/vfs_layered.h new file mode 100755 index 000000000..0027ffa9a --- /dev/null +++ b/src/core/file_sys/vfs/vfs_layered.h @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include "core/file_sys/vfs/vfs.h" + +namespace FileSys { + +// Class that stacks multiple VfsDirectories on top of each other, attempting to read from the first +// one and falling back to the one after. The highest priority directory (overwrites all others) +// should be element 0 in the dirs vector. +class LayeredVfsDirectory : public VfsDirectory { + explicit LayeredVfsDirectory(std::vector dirs_, std::string name_); + +public: + ~LayeredVfsDirectory() override; + + /// Wrapper function to allow for more efficient handling of dirs.size() == 0, 1 cases. + static VirtualDir MakeLayeredDirectory(std::vector dirs, std::string name = ""); + + VirtualFile GetFileRelative(std::string_view path) const override; + VirtualDir GetDirectoryRelative(std::string_view path) const override; + VirtualFile GetFile(std::string_view file_name) const override; + VirtualDir GetSubdirectory(std::string_view subdir_name) const override; + std::string GetFullPath() const override; + + std::vector GetFiles() const override; + std::vector GetSubdirectories() const override; + bool IsWritable() const override; + bool IsReadable() const override; + std::string GetName() const override; + VirtualDir GetParentDirectory() const override; + VirtualDir CreateSubdirectory(std::string_view subdir_name) override; + VirtualFile CreateFile(std::string_view file_name) override; + bool DeleteSubdirectory(std::string_view subdir_name) override; + bool DeleteFile(std::string_view file_name) override; + bool Rename(std::string_view new_name) override; + +private: + std::vector dirs; + std::string name; +}; + +} // namespace FileSys diff --git a/src/core/file_sys/vfs/vfs_offset.cpp b/src/core/file_sys/vfs/vfs_offset.cpp new file mode 100755 index 000000000..1a37d2670 --- /dev/null +++ b/src/core/file_sys/vfs/vfs_offset.cpp @@ -0,0 +1,98 @@ +// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include + +#include "core/file_sys/vfs/vfs_offset.h" + +namespace FileSys { + +OffsetVfsFile::OffsetVfsFile(VirtualFile file_, std::size_t size_, std::size_t offset_, + std::string name_, VirtualDir parent_) + : file(file_), offset(offset_), size(size_), name(std::move(name_)), + parent(parent_ == nullptr ? file->GetContainingDirectory() : std::move(parent_)) {} + +OffsetVfsFile::~OffsetVfsFile() = default; + +std::string OffsetVfsFile::GetName() const { + return name.empty() ? file->GetName() : name; +} + +std::size_t OffsetVfsFile::GetSize() const { + return size; +} + +bool OffsetVfsFile::Resize(std::size_t new_size) { + if (offset + new_size < file->GetSize()) { + size = new_size; + } else { + auto res = file->Resize(offset + new_size); + if (!res) + return false; + size = new_size; + } + + return true; +} + +VirtualDir OffsetVfsFile::GetContainingDirectory() const { + return parent; +} + +bool OffsetVfsFile::IsWritable() const { + return file->IsWritable(); +} + +bool OffsetVfsFile::IsReadable() const { + return file->IsReadable(); +} + +std::size_t OffsetVfsFile::Read(u8* data, std::size_t length, std::size_t r_offset) const { + return file->Read(data, TrimToFit(length, r_offset), offset + r_offset); +} + +std::size_t OffsetVfsFile::Write(const u8* data, std::size_t length, std::size_t r_offset) { + return file->Write(data, TrimToFit(length, r_offset), offset + r_offset); +} + +std::optional OffsetVfsFile::ReadByte(std::size_t r_offset) const { + if (r_offset >= size) { + return std::nullopt; + } + + return file->ReadByte(offset + r_offset); +} + +std::vector OffsetVfsFile::ReadBytes(std::size_t r_size, std::size_t r_offset) const { + return file->ReadBytes(TrimToFit(r_size, r_offset), offset + r_offset); +} + +std::vector OffsetVfsFile::ReadAllBytes() const { + return file->ReadBytes(size, offset); +} + +bool OffsetVfsFile::WriteByte(u8 data, std::size_t r_offset) { + if (r_offset < size) + return file->WriteByte(data, offset + r_offset); + + return false; +} + +std::size_t OffsetVfsFile::WriteBytes(const std::vector& data, std::size_t r_offset) { + return file->Write(data.data(), TrimToFit(data.size(), r_offset), offset + r_offset); +} + +bool OffsetVfsFile::Rename(std::string_view new_name) { + return file->Rename(new_name); +} + +std::size_t OffsetVfsFile::GetOffset() const { + return offset; +} + +std::size_t OffsetVfsFile::TrimToFit(std::size_t r_size, std::size_t r_offset) const { + return std::clamp(r_size, std::size_t{0}, size - r_offset); +} + +} // namespace FileSys diff --git a/src/core/file_sys/vfs/vfs_offset.h b/src/core/file_sys/vfs/vfs_offset.h new file mode 100755 index 000000000..4abe41d8e --- /dev/null +++ b/src/core/file_sys/vfs/vfs_offset.h @@ -0,0 +1,50 @@ +// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "core/file_sys/vfs/vfs.h" + +namespace FileSys { + +// An implementation of VfsFile that wraps around another VfsFile at a certain offset. +// Similar to seeking to an offset. +// If the file is writable, operations that would write past the end of the offset file will expand +// the size of this wrapper. +class OffsetVfsFile : public VfsFile { +public: + OffsetVfsFile(VirtualFile file, std::size_t size, std::size_t offset = 0, + std::string new_name = "", VirtualDir new_parent = nullptr); + ~OffsetVfsFile() override; + + std::string GetName() const override; + std::size_t GetSize() const override; + bool Resize(std::size_t new_size) override; + VirtualDir GetContainingDirectory() const override; + bool IsWritable() const override; + bool IsReadable() const override; + std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override; + std::size_t Write(const u8* data, std::size_t length, std::size_t offset) override; + std::optional ReadByte(std::size_t offset) const override; + std::vector ReadBytes(std::size_t size, std::size_t offset) const override; + std::vector ReadAllBytes() const override; + bool WriteByte(u8 data, std::size_t offset) override; + std::size_t WriteBytes(const std::vector& data, std::size_t offset) override; + + bool Rename(std::string_view new_name) override; + + std::size_t GetOffset() const; + +private: + std::size_t TrimToFit(std::size_t r_size, std::size_t r_offset) const; + + VirtualFile file; + std::size_t offset; + std::size_t size; + std::string name; + VirtualDir parent; +}; + +} // namespace FileSys diff --git a/src/core/file_sys/vfs/vfs_real.cpp b/src/core/file_sys/vfs/vfs_real.cpp new file mode 100755 index 000000000..627d5d251 --- /dev/null +++ b/src/core/file_sys/vfs/vfs_real.cpp @@ -0,0 +1,527 @@ +// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include +#include "common/assert.h" +#include "common/fs/file.h" +#include "common/fs/fs.h" +#include "common/fs/path_util.h" +#include "common/logging/log.h" +#include "core/file_sys/vfs/vfs.h" +#include "core/file_sys/vfs/vfs_real.h" + +// For FileTimeStampRaw +#include + +#ifdef _MSC_VER +#define stat _stat64 +#endif + +namespace FileSys { + +namespace FS = Common::FS; + +namespace { + +constexpr size_t MaxOpenFiles = 512; + +constexpr FS::FileAccessMode ModeFlagsToFileAccessMode(OpenMode mode) { + switch (mode) { + case OpenMode::Read: + return FS::FileAccessMode::Read; + case OpenMode::Write: + case OpenMode::ReadWrite: + case OpenMode::AllowAppend: + case OpenMode::All: + return FS::FileAccessMode::ReadWrite; + default: + return {}; + } +} + +} // Anonymous namespace + +RealVfsFilesystem::RealVfsFilesystem() : VfsFilesystem(nullptr) {} +RealVfsFilesystem::~RealVfsFilesystem() = default; + +std::string RealVfsFilesystem::GetName() const { + return "Real"; +} + +bool RealVfsFilesystem::IsReadable() const { + return true; +} + +bool RealVfsFilesystem::IsWritable() const { + return true; +} + +VfsEntryType RealVfsFilesystem::GetEntryType(std::string_view path_) const { + const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); + if (!FS::Exists(path)) { + return VfsEntryType::None; + } + if (FS::IsDir(path)) { + return VfsEntryType::Directory; + } + + return VfsEntryType::File; +} + +VirtualFile RealVfsFilesystem::OpenFileFromEntry(std::string_view path_, std::optional size, + OpenMode perms) { + const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); + std::scoped_lock lk{list_lock}; + + if (auto it = cache.find(path); it != cache.end()) { + if (auto file = it->second.lock(); file) { + return file; + } + } + + if (!size && !FS::IsFile(path)) { + return nullptr; + } + + auto reference = std::make_unique(); + this->InsertReferenceIntoListLocked(*reference); + + auto file = std::shared_ptr( + new RealVfsFile(*this, std::move(reference), path, perms, size)); + cache[path] = file; + + return file; +} + +VirtualFile RealVfsFilesystem::OpenFile(std::string_view path_, OpenMode perms) { + return OpenFileFromEntry(path_, {}, perms); +} + +VirtualFile RealVfsFilesystem::CreateFile(std::string_view path_, OpenMode perms) { + const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); + { + std::scoped_lock lk{list_lock}; + cache.erase(path); + } + + // Current usages of CreateFile expect to delete the contents of an existing file. + if (FS::IsFile(path)) { + FS::IOFile temp{path, FS::FileAccessMode::Write, FS::FileType::BinaryFile}; + + if (!temp.IsOpen()) { + return nullptr; + } + + temp.Close(); + + return OpenFile(path, perms); + } + + if (!FS::NewFile(path)) { + return nullptr; + } + + return OpenFile(path, perms); +} + +VirtualFile RealVfsFilesystem::CopyFile(std::string_view old_path_, std::string_view new_path_) { + // Unused + return nullptr; +} + +VirtualFile RealVfsFilesystem::MoveFile(std::string_view old_path_, std::string_view new_path_) { + const auto old_path = FS::SanitizePath(old_path_, FS::DirectorySeparator::PlatformDefault); + const auto new_path = FS::SanitizePath(new_path_, FS::DirectorySeparator::PlatformDefault); + { + std::scoped_lock lk{list_lock}; + cache.erase(old_path); + cache.erase(new_path); + } + if (!FS::RenameFile(old_path, new_path)) { + return nullptr; + } + return OpenFile(new_path, OpenMode::ReadWrite); +} + +bool RealVfsFilesystem::DeleteFile(std::string_view path_) { + const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); + { + std::scoped_lock lk{list_lock}; + cache.erase(path); + } + return FS::RemoveFile(path); +} + +VirtualDir RealVfsFilesystem::OpenDirectory(std::string_view path_, OpenMode perms) { + const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); + return std::shared_ptr(new RealVfsDirectory(*this, path, perms)); +} + +VirtualDir RealVfsFilesystem::CreateDirectory(std::string_view path_, OpenMode perms) { + const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); + if (!FS::CreateDirs(path)) { + return nullptr; + } + return std::shared_ptr(new RealVfsDirectory(*this, path, perms)); +} + +VirtualDir RealVfsFilesystem::CopyDirectory(std::string_view old_path_, + std::string_view new_path_) { + // Unused + return nullptr; +} + +VirtualDir RealVfsFilesystem::MoveDirectory(std::string_view old_path_, + std::string_view new_path_) { + const auto old_path = FS::SanitizePath(old_path_, FS::DirectorySeparator::PlatformDefault); + const auto new_path = FS::SanitizePath(new_path_, FS::DirectorySeparator::PlatformDefault); + + if (!FS::RenameDir(old_path, new_path)) { + return nullptr; + } + return OpenDirectory(new_path, OpenMode::ReadWrite); +} + +bool RealVfsFilesystem::DeleteDirectory(std::string_view path_) { + const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); + return FS::RemoveDirRecursively(path); +} + +std::unique_lock RealVfsFilesystem::RefreshReference(const std::string& path, + OpenMode perms, + FileReference& reference) { + std::unique_lock lk{list_lock}; + + // Temporarily remove from list. + this->RemoveReferenceFromListLocked(reference); + + // Restore file if needed. + if (!reference.file) { + this->EvictSingleReferenceLocked(); + + reference.file = + FS::FileOpen(path, ModeFlagsToFileAccessMode(perms), FS::FileType::BinaryFile); + if (reference.file) { + num_open_files++; + } + } + + // Reinsert into list. + this->InsertReferenceIntoListLocked(reference); + + return lk; +} + +void RealVfsFilesystem::DropReference(std::unique_ptr&& reference) { + std::scoped_lock lk{list_lock}; + + // Remove from list. + this->RemoveReferenceFromListLocked(*reference); + + // Close the file. + if (reference->file) { + reference->file.reset(); + num_open_files--; + } +} + +void RealVfsFilesystem::EvictSingleReferenceLocked() { + if (num_open_files < MaxOpenFiles || open_references.empty()) { + return; + } + + // Get and remove from list. + auto& reference = open_references.back(); + this->RemoveReferenceFromListLocked(reference); + + // Close the file. + if (reference.file) { + reference.file.reset(); + num_open_files--; + } + + // Reinsert into closed list. + this->InsertReferenceIntoListLocked(reference); +} + +void RealVfsFilesystem::InsertReferenceIntoListLocked(FileReference& reference) { + if (reference.file) { + open_references.push_front(reference); + } else { + closed_references.push_front(reference); + } +} + +void RealVfsFilesystem::RemoveReferenceFromListLocked(FileReference& reference) { + if (reference.file) { + open_references.erase(open_references.iterator_to(reference)); + } else { + closed_references.erase(closed_references.iterator_to(reference)); + } +} + +RealVfsFile::RealVfsFile(RealVfsFilesystem& base_, std::unique_ptr reference_, + const std::string& path_, OpenMode perms_, std::optional size_) + : base(base_), reference(std::move(reference_)), path(path_), + parent_path(FS::GetParentPath(path_)), path_components(FS::SplitPathComponentsCopy(path_)), + size(size_), perms(perms_) {} + +RealVfsFile::~RealVfsFile() { + base.DropReference(std::move(reference)); +} + +std::string RealVfsFile::GetName() const { + return path_components.empty() ? "" : std::string(path_components.back()); +} + +std::size_t RealVfsFile::GetSize() const { + if (size) { + return *size; + } + auto lk = base.RefreshReference(path, perms, *reference); + return reference->file ? reference->file->GetSize() : 0; +} + +bool RealVfsFile::Resize(std::size_t new_size) { + size.reset(); + auto lk = base.RefreshReference(path, perms, *reference); + return reference->file ? reference->file->SetSize(new_size) : false; +} + +VirtualDir RealVfsFile::GetContainingDirectory() const { + return base.OpenDirectory(parent_path, perms); +} + +bool RealVfsFile::IsWritable() const { + return True(perms & OpenMode::Write); +} + +bool RealVfsFile::IsReadable() const { + return True(perms & OpenMode::Read); +} + +std::size_t RealVfsFile::Read(u8* data, std::size_t length, std::size_t offset) const { + auto lk = base.RefreshReference(path, perms, *reference); + if (!reference->file || !reference->file->Seek(static_cast(offset))) { + return 0; + } + return reference->file->ReadSpan(std::span{data, length}); +} + +std::size_t RealVfsFile::Write(const u8* data, std::size_t length, std::size_t offset) { + size.reset(); + auto lk = base.RefreshReference(path, perms, *reference); + if (!reference->file || !reference->file->Seek(static_cast(offset))) { + return 0; + } + return reference->file->WriteSpan(std::span{data, length}); +} + +bool RealVfsFile::Rename(std::string_view name) { + return base.MoveFile(path, parent_path + '/' + std::string(name)) != nullptr; +} + +// TODO(DarkLordZach): MSVC would not let me combine the following two functions using 'if +// constexpr' because there is a compile error in the branch not used. + +template <> +std::vector RealVfsDirectory::IterateEntries() const { + if (perms == OpenMode::AllowAppend) { + return {}; + } + + std::vector out; + + const FS::DirEntryCallable callback = [this, + &out](const std::filesystem::directory_entry& entry) { + const auto full_path_string = FS::PathToUTF8String(entry.path()); + + out.emplace_back(base.OpenFileFromEntry(full_path_string, entry.file_size(), perms)); + + return true; + }; + + FS::IterateDirEntries(path, callback, FS::DirEntryFilter::File); + + return out; +} + +template <> +std::vector RealVfsDirectory::IterateEntries() const { + if (perms == OpenMode::AllowAppend) { + return {}; + } + + std::vector out; + + const FS::DirEntryCallable callback = [this, + &out](const std::filesystem::directory_entry& entry) { + const auto full_path_string = FS::PathToUTF8String(entry.path()); + + out.emplace_back(base.OpenDirectory(full_path_string, perms)); + + return true; + }; + + FS::IterateDirEntries(path, callback, FS::DirEntryFilter::Directory); + + return out; +} + +RealVfsDirectory::RealVfsDirectory(RealVfsFilesystem& base_, const std::string& path_, + OpenMode perms_) + : base(base_), path(FS::RemoveTrailingSlash(path_)), parent_path(FS::GetParentPath(path)), + path_components(FS::SplitPathComponentsCopy(path)), perms(perms_) { + if (!FS::Exists(path) && True(perms & OpenMode::Write)) { + void(FS::CreateDirs(path)); + } +} + +RealVfsDirectory::~RealVfsDirectory() = default; + +VirtualFile RealVfsDirectory::GetFileRelative(std::string_view relative_path) const { + const auto full_path = FS::SanitizePath(path + '/' + std::string(relative_path)); + if (!FS::Exists(full_path) || FS::IsDir(full_path)) { + return nullptr; + } + return base.OpenFile(full_path, perms); +} + +VirtualDir RealVfsDirectory::GetDirectoryRelative(std::string_view relative_path) const { + const auto full_path = FS::SanitizePath(path + '/' + std::string(relative_path)); + if (!FS::Exists(full_path) || !FS::IsDir(full_path)) { + return nullptr; + } + return base.OpenDirectory(full_path, perms); +} + +VirtualFile RealVfsDirectory::GetFile(std::string_view name) const { + return GetFileRelative(name); +} + +VirtualDir RealVfsDirectory::GetSubdirectory(std::string_view name) const { + return GetDirectoryRelative(name); +} + +VirtualFile RealVfsDirectory::CreateFileRelative(std::string_view relative_path) { + const auto full_path = FS::SanitizePath(path + '/' + std::string(relative_path)); + if (!FS::CreateParentDirs(full_path)) { + return nullptr; + } + return base.CreateFile(full_path, perms); +} + +VirtualDir RealVfsDirectory::CreateDirectoryRelative(std::string_view relative_path) { + const auto full_path = FS::SanitizePath(path + '/' + std::string(relative_path)); + return base.CreateDirectory(full_path, perms); +} + +bool RealVfsDirectory::DeleteSubdirectoryRecursive(std::string_view name) { + const auto full_path = FS::SanitizePath(this->path + '/' + std::string(name)); + return base.DeleteDirectory(full_path); +} + +std::vector RealVfsDirectory::GetFiles() const { + return IterateEntries(); +} + +FileTimeStampRaw RealVfsDirectory::GetFileTimeStamp(std::string_view path_) const { + const auto full_path = FS::SanitizePath(path + '/' + std::string(path_)); + const auto fs_path = std::filesystem::path{FS::ToU8String(full_path)}; + struct stat file_status; + +#ifdef _WIN32 + const auto stat_result = _wstat64(fs_path.c_str(), &file_status); +#else + const auto stat_result = stat(fs_path.c_str(), &file_status); +#endif + + if (stat_result != 0) { + return {}; + } + + return { + .created{static_cast(file_status.st_ctime)}, + .accessed{static_cast(file_status.st_atime)}, + .modified{static_cast(file_status.st_mtime)}, + }; +} + +std::vector RealVfsDirectory::GetSubdirectories() const { + return IterateEntries(); +} + +bool RealVfsDirectory::IsWritable() const { + return True(perms & OpenMode::Write); +} + +bool RealVfsDirectory::IsReadable() const { + return True(perms & OpenMode::Read); +} + +std::string RealVfsDirectory::GetName() const { + return path_components.empty() ? "" : std::string(path_components.back()); +} + +VirtualDir RealVfsDirectory::GetParentDirectory() const { + if (path_components.size() <= 1) { + return nullptr; + } + + return base.OpenDirectory(parent_path, perms); +} + +VirtualDir RealVfsDirectory::CreateSubdirectory(std::string_view name) { + const std::string subdir_path = (path + '/').append(name); + return base.CreateDirectory(subdir_path, perms); +} + +VirtualFile RealVfsDirectory::CreateFile(std::string_view name) { + const std::string file_path = (path + '/').append(name); + return base.CreateFile(file_path, perms); +} + +bool RealVfsDirectory::DeleteSubdirectory(std::string_view name) { + const std::string subdir_path = (path + '/').append(name); + return base.DeleteDirectory(subdir_path); +} + +bool RealVfsDirectory::DeleteFile(std::string_view name) { + const std::string file_path = (path + '/').append(name); + return base.DeleteFile(file_path); +} + +bool RealVfsDirectory::Rename(std::string_view name) { + const std::string new_name = (parent_path + '/').append(name); + return base.MoveFile(path, new_name) != nullptr; +} + +std::string RealVfsDirectory::GetFullPath() const { + auto out = path; + std::replace(out.begin(), out.end(), '\\', '/'); + return out; +} + +std::map> RealVfsDirectory::GetEntries() const { + if (perms == OpenMode::AllowAppend) { + return {}; + } + + std::map> out; + + const FS::DirEntryCallable callback = [&out](const std::filesystem::directory_entry& entry) { + const auto filename = FS::PathToUTF8String(entry.path().filename()); + out.insert_or_assign(filename, + entry.is_directory() ? VfsEntryType::Directory : VfsEntryType::File); + return true; + }; + + FS::IterateDirEntries(path, callback); + + return out; +} + +} // namespace FileSys diff --git a/src/core/file_sys/vfs/vfs_real.h b/src/core/file_sys/vfs/vfs_real.h new file mode 100755 index 000000000..5c2172cce --- /dev/null +++ b/src/core/file_sys/vfs/vfs_real.h @@ -0,0 +1,148 @@ +// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include "common/intrusive_list.h" +#include "core/file_sys/fs_filesystem.h" +#include "core/file_sys/vfs/vfs.h" + +namespace Common::FS { +class IOFile; +} + +namespace FileSys { + +struct FileReference : public Common::IntrusiveListBaseNode { + std::shared_ptr file{}; +}; + +class RealVfsFile; +class RealVfsDirectory; + +class RealVfsFilesystem : public VfsFilesystem { +public: + RealVfsFilesystem(); + ~RealVfsFilesystem() override; + + std::string GetName() const override; + bool IsReadable() const override; + bool IsWritable() const override; + VfsEntryType GetEntryType(std::string_view path) const override; + VirtualFile OpenFile(std::string_view path, OpenMode perms = OpenMode::Read) override; + VirtualFile CreateFile(std::string_view path, OpenMode perms = OpenMode::ReadWrite) override; + VirtualFile CopyFile(std::string_view old_path, std::string_view new_path) override; + VirtualFile MoveFile(std::string_view old_path, std::string_view new_path) override; + bool DeleteFile(std::string_view path) override; + VirtualDir OpenDirectory(std::string_view path, OpenMode perms = OpenMode::Read) override; + VirtualDir CreateDirectory(std::string_view path, + OpenMode perms = OpenMode::ReadWrite) override; + VirtualDir CopyDirectory(std::string_view old_path, std::string_view new_path) override; + VirtualDir MoveDirectory(std::string_view old_path, std::string_view new_path) override; + bool DeleteDirectory(std::string_view path) override; + +private: + using ReferenceListType = Common::IntrusiveListBaseTraits::ListType; + std::map, std::less<>> cache; + ReferenceListType open_references; + ReferenceListType closed_references; + std::mutex list_lock; + size_t num_open_files{}; + +private: + friend class RealVfsFile; + std::unique_lock RefreshReference(const std::string& path, OpenMode perms, + FileReference& reference); + void DropReference(std::unique_ptr&& reference); + +private: + friend class RealVfsDirectory; + VirtualFile OpenFileFromEntry(std::string_view path, std::optional size, + OpenMode perms = OpenMode::Read); + +private: + void EvictSingleReferenceLocked(); + void InsertReferenceIntoListLocked(FileReference& reference); + void RemoveReferenceFromListLocked(FileReference& reference); +}; + +// An implementation of VfsFile that represents a file on the user's computer. +class RealVfsFile : public VfsFile { + friend class RealVfsDirectory; + friend class RealVfsFilesystem; + +public: + ~RealVfsFile() override; + + std::string GetName() const override; + std::size_t GetSize() const override; + bool Resize(std::size_t new_size) override; + VirtualDir GetContainingDirectory() const override; + bool IsWritable() const override; + bool IsReadable() const override; + std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override; + std::size_t Write(const u8* data, std::size_t length, std::size_t offset) override; + bool Rename(std::string_view name) override; + +private: + RealVfsFile(RealVfsFilesystem& base, std::unique_ptr reference, + const std::string& path, OpenMode perms = OpenMode::Read, + std::optional size = {}); + + RealVfsFilesystem& base; + std::unique_ptr reference; + std::string path; + std::string parent_path; + std::vector path_components; + std::optional size; + OpenMode perms; +}; + +// An implementation of VfsDirectory that represents a directory on the user's computer. +class RealVfsDirectory : public VfsDirectory { + friend class RealVfsFilesystem; + +public: + ~RealVfsDirectory() override; + + VirtualFile GetFileRelative(std::string_view relative_path) const override; + VirtualDir GetDirectoryRelative(std::string_view relative_path) const override; + VirtualFile GetFile(std::string_view name) const override; + VirtualDir GetSubdirectory(std::string_view name) const override; + VirtualFile CreateFileRelative(std::string_view relative_path) override; + VirtualDir CreateDirectoryRelative(std::string_view relative_path) override; + bool DeleteSubdirectoryRecursive(std::string_view name) override; + std::vector GetFiles() const override; + FileTimeStampRaw GetFileTimeStamp(std::string_view path) const override; + std::vector GetSubdirectories() const override; + bool IsWritable() const override; + bool IsReadable() const override; + std::string GetName() const override; + VirtualDir GetParentDirectory() const override; + VirtualDir CreateSubdirectory(std::string_view name) override; + VirtualFile CreateFile(std::string_view name) override; + bool DeleteSubdirectory(std::string_view name) override; + bool DeleteFile(std::string_view name) override; + bool Rename(std::string_view name) override; + std::string GetFullPath() const override; + std::map> GetEntries() const override; + +private: + RealVfsDirectory(RealVfsFilesystem& base, const std::string& path, + OpenMode perms = OpenMode::Read); + + template + std::vector> IterateEntries() const; + + RealVfsFilesystem& base; + std::string path; + std::string parent_path; + std::vector path_components; + OpenMode perms; +}; + +} // namespace FileSys diff --git a/src/core/file_sys/vfs/vfs_static.h b/src/core/file_sys/vfs/vfs_static.h new file mode 100755 index 000000000..bb53560ac --- /dev/null +++ b/src/core/file_sys/vfs/vfs_static.h @@ -0,0 +1,80 @@ +// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include + +#include "core/file_sys/vfs/vfs.h" + +namespace FileSys { + +class StaticVfsFile : public VfsFile { +public: + explicit StaticVfsFile(u8 value_, std::size_t size_ = 0, std::string name_ = "", + VirtualDir parent_ = nullptr) + : value{value_}, size{size_}, name{std::move(name_)}, parent{std::move(parent_)} {} + + std::string GetName() const override { + return name; + } + + std::size_t GetSize() const override { + return size; + } + + bool Resize(std::size_t new_size) override { + size = new_size; + return true; + } + + VirtualDir GetContainingDirectory() const override { + return parent; + } + + bool IsWritable() const override { + return false; + } + + bool IsReadable() const override { + return true; + } + + std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override { + const auto read = std::min(length, size - offset); + std::fill(data, data + read, value); + return read; + } + + std::size_t Write(const u8* data, std::size_t length, std::size_t offset) override { + return 0; + } + + std::optional ReadByte(std::size_t offset) const override { + if (offset >= size) { + return std::nullopt; + } + + return value; + } + + std::vector ReadBytes(std::size_t length, std::size_t offset) const override { + const auto read = std::min(length, size - offset); + return std::vector(read, value); + } + + bool Rename(std::string_view new_name) override { + name = new_name; + return true; + } + +private: + u8 value; + std::size_t size; + std::string name; + VirtualDir parent; +}; + +} // namespace FileSys diff --git a/src/core/file_sys/vfs/vfs_types.h b/src/core/file_sys/vfs/vfs_types.h new file mode 100755 index 000000000..4a583ed64 --- /dev/null +++ b/src/core/file_sys/vfs/vfs_types.h @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "common/common_types.h" + +namespace FileSys { + +class VfsDirectory; +class VfsFile; +class VfsFilesystem; + +// Declarations for Vfs* pointer types + +using VirtualDir = std::shared_ptr; +using VirtualFile = std::shared_ptr; +using VirtualFilesystem = std::shared_ptr; + +struct FileTimeStampRaw { + u64 created{}; + u64 accessed{}; + u64 modified{}; + u64 padding{}; +}; + +} // namespace FileSys diff --git a/src/core/file_sys/vfs/vfs_vector.cpp b/src/core/file_sys/vfs/vfs_vector.cpp new file mode 100755 index 000000000..0d54461c8 --- /dev/null +++ b/src/core/file_sys/vfs/vfs_vector.cpp @@ -0,0 +1,133 @@ +// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include "core/file_sys/vfs/vfs_vector.h" + +namespace FileSys { +VectorVfsFile::VectorVfsFile(std::vector initial_data, std::string name_, VirtualDir parent_) + : data(std::move(initial_data)), parent(std::move(parent_)), name(std::move(name_)) {} + +VectorVfsFile::~VectorVfsFile() = default; + +std::string VectorVfsFile::GetName() const { + return name; +} + +size_t VectorVfsFile::GetSize() const { + return data.size(); +} + +bool VectorVfsFile::Resize(size_t new_size) { + data.resize(new_size); + return true; +} + +VirtualDir VectorVfsFile::GetContainingDirectory() const { + return parent; +} + +bool VectorVfsFile::IsWritable() const { + return true; +} + +bool VectorVfsFile::IsReadable() const { + return true; +} + +std::size_t VectorVfsFile::Read(u8* data_, std::size_t length, std::size_t offset) const { + const auto read = std::min(length, data.size() - offset); + std::memcpy(data_, data.data() + offset, read); + return read; +} + +std::size_t VectorVfsFile::Write(const u8* data_, std::size_t length, std::size_t offset) { + if (offset + length > data.size()) + data.resize(offset + length); + const auto write = std::min(length, data.size() - offset); + std::memcpy(data.data() + offset, data_, write); + return write; +} + +bool VectorVfsFile::Rename(std::string_view name_) { + name = name_; + return true; +} + +void VectorVfsFile::Assign(std::vector new_data) { + data = std::move(new_data); +} + +VectorVfsDirectory::VectorVfsDirectory(std::vector files_, + std::vector dirs_, std::string name_, + VirtualDir parent_) + : files(std::move(files_)), dirs(std::move(dirs_)), parent(std::move(parent_)), + name(std::move(name_)) {} + +VectorVfsDirectory::~VectorVfsDirectory() = default; + +std::vector VectorVfsDirectory::GetFiles() const { + return files; +} + +std::vector VectorVfsDirectory::GetSubdirectories() const { + return dirs; +} + +bool VectorVfsDirectory::IsWritable() const { + return false; +} + +bool VectorVfsDirectory::IsReadable() const { + return true; +} + +std::string VectorVfsDirectory::GetName() const { + return name; +} + +VirtualDir VectorVfsDirectory::GetParentDirectory() const { + return parent; +} + +template +static bool FindAndRemoveVectorElement(std::vector& vec, std::string_view name) { + const auto iter = + std::find_if(vec.begin(), vec.end(), [name](const T& e) { return e->GetName() == name; }); + if (iter == vec.end()) + return false; + + vec.erase(iter); + return true; +} + +bool VectorVfsDirectory::DeleteSubdirectory(std::string_view subdir_name) { + return FindAndRemoveVectorElement(dirs, subdir_name); +} + +bool VectorVfsDirectory::DeleteFile(std::string_view file_name) { + return FindAndRemoveVectorElement(files, file_name); +} + +bool VectorVfsDirectory::Rename(std::string_view name_) { + name = name_; + return true; +} + +VirtualDir VectorVfsDirectory::CreateSubdirectory(std::string_view subdir_name) { + return nullptr; +} + +VirtualFile VectorVfsDirectory::CreateFile(std::string_view file_name) { + return nullptr; +} + +void VectorVfsDirectory::AddFile(VirtualFile file) { + files.push_back(std::move(file)); +} + +void VectorVfsDirectory::AddDirectory(VirtualDir dir) { + dirs.push_back(std::move(dir)); +} +} // namespace FileSys diff --git a/src/core/file_sys/vfs/vfs_vector.h b/src/core/file_sys/vfs/vfs_vector.h new file mode 100755 index 000000000..587187dd2 --- /dev/null +++ b/src/core/file_sys/vfs/vfs_vector.h @@ -0,0 +1,131 @@ +// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include +#include "core/file_sys/vfs/vfs.h" + +namespace FileSys { + +// An implementation of VfsFile that is backed by a statically-sized array +template +class ArrayVfsFile : public VfsFile { +public: + explicit ArrayVfsFile(const std::array& data_, std::string name_ = "", + VirtualDir parent_ = nullptr) + : data(data_), name(std::move(name_)), parent(std::move(parent_)) {} + + std::string GetName() const override { + return name; + } + + std::size_t GetSize() const override { + return size; + } + + bool Resize(std::size_t new_size) override { + return false; + } + + VirtualDir GetContainingDirectory() const override { + return parent; + } + + bool IsWritable() const override { + return false; + } + + bool IsReadable() const override { + return true; + } + + std::size_t Read(u8* data_, std::size_t length, std::size_t offset) const override { + const auto read = std::min(length, size - offset); + std::memcpy(data_, data.data() + offset, read); + return read; + } + + std::size_t Write(const u8* data_, std::size_t length, std::size_t offset) override { + return 0; + } + + bool Rename(std::string_view new_name) override { + name = new_name; + return true; + } + +private: + std::array data; + std::string name; + VirtualDir parent; +}; + +template +std::shared_ptr> MakeArrayFile(const std::array& data, + Args&&... args) { + return std::make_shared>(data, std::forward(args)...); +} + +// An implementation of VfsFile that is backed by a vector optionally supplied upon construction +class VectorVfsFile : public VfsFile { +public: + explicit VectorVfsFile(std::vector initial_data = {}, std::string name_ = "", + VirtualDir parent_ = nullptr); + ~VectorVfsFile() override; + + std::string GetName() const override; + std::size_t GetSize() const override; + bool Resize(std::size_t new_size) override; + VirtualDir GetContainingDirectory() const override; + bool IsWritable() const override; + bool IsReadable() const override; + std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override; + std::size_t Write(const u8* data, std::size_t length, std::size_t offset) override; + bool Rename(std::string_view name) override; + + virtual void Assign(std::vector new_data); + +private: + std::vector data; + VirtualDir parent; + std::string name; +}; + +// An implementation of VfsDirectory that maintains two vectors for subdirectories and files. +// Vector data is supplied upon construction. +class VectorVfsDirectory : public VfsDirectory { +public: + explicit VectorVfsDirectory(std::vector files = {}, + std::vector dirs = {}, std::string name = "", + VirtualDir parent = nullptr); + ~VectorVfsDirectory() override; + + std::vector GetFiles() const override; + std::vector GetSubdirectories() const override; + bool IsWritable() const override; + bool IsReadable() const override; + std::string GetName() const override; + VirtualDir GetParentDirectory() const override; + bool DeleteSubdirectory(std::string_view subdir_name) override; + bool DeleteFile(std::string_view file_name) override; + bool Rename(std::string_view name) override; + VirtualDir CreateSubdirectory(std::string_view subdir_name) override; + VirtualFile CreateFile(std::string_view file_name) override; + + virtual void AddFile(VirtualFile file); + virtual void AddDirectory(VirtualDir dir); + +private: + std::vector files; + std::vector dirs; + + VirtualDir parent; + std::string name; +}; + +} // namespace FileSys diff --git a/src/core/file_sys/xts_archive.cpp b/src/core/file_sys/xts_archive.cpp index 7e05cb5d1..801d2a62b 100755 --- a/src/core/file_sys/xts_archive.cpp +++ b/src/core/file_sys/xts_archive.cpp @@ -17,7 +17,7 @@ #include "core/crypto/key_manager.h" #include "core/crypto/xts_encryption_layer.h" #include "core/file_sys/content_archive.h" -#include "core/file_sys/vfs_offset.h" +#include "core/file_sys/vfs/vfs_offset.h" #include "core/file_sys/xts_archive.h" #include "core/loader/loader.h" diff --git a/src/core/file_sys/xts_archive.h b/src/core/file_sys/xts_archive.h index a20273862..87ed9be8e 100755 --- a/src/core/file_sys/xts_archive.h +++ b/src/core/file_sys/xts_archive.h @@ -8,7 +8,7 @@ #include "common/common_types.h" #include "common/swap.h" #include "core/crypto/key_manager.h" -#include "core/file_sys/vfs.h" +#include "core/file_sys/vfs/vfs.h" namespace Loader { enum class ResultStatus : u16; diff --git a/src/core/frontend/applets/error.cpp b/src/core/frontend/applets/error.cpp index 87adcdd1d..f5125ffd6 100755 --- a/src/core/frontend/applets/error.cpp +++ b/src/core/frontend/applets/error.cpp @@ -12,7 +12,7 @@ void DefaultErrorApplet::Close() const {} void DefaultErrorApplet::ShowError(Result error, FinishedCallback finished) const { LOG_CRITICAL(Service_Fatal, "Application requested error display: {:04}-{:04} (raw={:08X})", - error.module.Value(), error.description.Value(), error.raw); + error.GetModule(), error.GetDescription(), error.raw); } void DefaultErrorApplet::ShowErrorWithTimestamp(Result error, std::chrono::seconds time, @@ -20,7 +20,7 @@ void DefaultErrorApplet::ShowErrorWithTimestamp(Result error, std::chrono::secon LOG_CRITICAL( Service_Fatal, "Application requested error display: {:04X}-{:04X} (raw={:08X}) with timestamp={:016X}", - error.module.Value(), error.description.Value(), error.raw, time.count()); + error.GetModule(), error.GetDescription(), error.raw, time.count()); } void DefaultErrorApplet::ShowCustomErrorText(Result error, std::string main_text, @@ -28,7 +28,7 @@ void DefaultErrorApplet::ShowCustomErrorText(Result error, std::string main_text FinishedCallback finished) const { LOG_CRITICAL(Service_Fatal, "Application requested custom error with error_code={:04X}-{:04X} (raw={:08X})", - error.module.Value(), error.description.Value(), error.raw); + error.GetModule(), error.GetDescription(), error.raw); LOG_CRITICAL(Service_Fatal, " Main Text: {}", main_text); LOG_CRITICAL(Service_Fatal, " Detail Text: {}", detail_text); } diff --git a/src/core/hle/result.h b/src/core/hle/result.h index cfde4137f..a14e1372a 100755 --- a/src/core/hle/result.h +++ b/src/core/hle/result.h @@ -189,14 +189,14 @@ enum class ErrorModule : u32 { union Result { u32 raw; - BitField<0, 9, ErrorModule> module; - BitField<9, 13, u32> description; + using Module = BitField<0, 9, ErrorModule>; + using Description = BitField<9, 13, u32>; Result() = default; constexpr explicit Result(u32 raw_) : raw(raw_) {} constexpr Result(ErrorModule module_, u32 description_) - : raw(module.FormatValue(module_) | description.FormatValue(description_)) {} + : raw(Module::FormatValue(module_) | Description::FormatValue(description_)) {} [[nodiscard]] constexpr bool IsSuccess() const { return raw == 0; @@ -211,7 +211,15 @@ union Result { } [[nodiscard]] constexpr u32 GetInnerValue() const { - return static_cast(module.Value()) | (description << module.bits); + return raw; + } + + [[nodiscard]] constexpr ErrorModule GetModule() const { + return Module::ExtractValue(raw); + } + + [[nodiscard]] constexpr u32 GetDescription() const { + return Description::ExtractValue(raw); } [[nodiscard]] constexpr bool Includes(Result result) const { @@ -274,8 +282,9 @@ public: } [[nodiscard]] constexpr bool Includes(Result other) const { - return code.module == other.module && code.description <= other.description && - other.description <= description_end; + return code.GetModule() == other.GetModule() && + code.GetDescription() <= other.GetDescription() && + other.GetDescription() <= description_end; } private: @@ -330,6 +339,16 @@ constexpr bool EvaluateResultFailure(const Result& r) { return R_FAILED(r); } +template +constexpr bool EvaluateAnyResultIncludes(const Result& r) { + return ((r == R) || ...); +} + +template +constexpr bool EvaluateResultNotIncluded(const Result& r) { + return !EvaluateAnyResultIncludes(r); +} + template constexpr void UpdateCurrentResultReference(T result_reference, Result result) = delete; // Intentionally not defined @@ -371,6 +390,13 @@ constexpr void UpdateCurrentResultReference(Result result_referenc DECLARE_CURRENT_RESULT_REFERENCE_AND_STORAGE(__COUNTER__); \ ON_RESULT_SUCCESS_2 +#define ON_RESULT_INCLUDED_2(...) \ + ON_RESULT_RETURN_IMPL(ResultImpl::EvaluateAnyResultIncludes<__VA_ARGS__>) + +#define ON_RESULT_INCLUDED(...) \ + DECLARE_CURRENT_RESULT_REFERENCE_AND_STORAGE(__COUNTER__); \ + ON_RESULT_INCLUDED_2(__VA_ARGS__) + constexpr inline Result __TmpCurrentResultReference = ResultSuccess; /// Returns a result. diff --git a/src/core/hle/service/am/applets/applet_error.cpp b/src/core/hle/service/am/applets/applet_error.cpp index e35c5f1a6..6ff346045 100755 --- a/src/core/hle/service/am/applets/applet_error.cpp +++ b/src/core/hle/service/am/applets/applet_error.cpp @@ -27,8 +27,8 @@ struct ErrorCode { static constexpr ErrorCode FromResult(Result result) { return { - .error_category{2000 + static_cast(result.module.Value())}, - .error_number{result.description.Value()}, + .error_category{2000 + static_cast(result.GetModule())}, + .error_number{result.GetDescription()}, }; } 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 4cec088b6..1ee1d7234 100755 --- a/src/core/hle/service/am/applets/applet_web_browser.cpp +++ b/src/core/hle/service/am/applets/applet_web_browser.cpp @@ -9,13 +9,13 @@ #include "common/string_util.h" #include "core/core.h" #include "core/file_sys/content_archive.h" -#include "core/file_sys/mode.h" +#include "core/file_sys/fs_filesystem.h" #include "core/file_sys/nca_metadata.h" #include "core/file_sys/patch_manager.h" #include "core/file_sys/registered_cache.h" #include "core/file_sys/romfs.h" #include "core/file_sys/system_archive/system_archive.h" -#include "core/file_sys/vfs_vector.h" +#include "core/file_sys/vfs/vfs_vector.h" #include "core/frontend/applets/web_browser.h" #include "core/hle/result.h" #include "core/hle/service/am/am.h" @@ -213,7 +213,7 @@ void ExtractSharedFonts(Core::System& system) { std::move(decrypted_data), DECRYPTED_SHARED_FONTS[i]); const auto temp_dir = system.GetFilesystem()->CreateDirectory( - Common::FS::PathToUTF8String(fonts_dir), FileSys::Mode::ReadWrite); + Common::FS::PathToUTF8String(fonts_dir), FileSys::OpenMode::ReadWrite); const auto out_file = temp_dir->CreateFile(DECRYPTED_SHARED_FONTS[i]); @@ -333,7 +333,7 @@ void WebBrowser::ExtractOfflineRomFS() { const auto extracted_romfs_dir = FileSys::ExtractRomFS(offline_romfs); const auto temp_dir = system.GetFilesystem()->CreateDirectory( - Common::FS::PathToUTF8String(offline_cache_dir), FileSys::Mode::ReadWrite); + Common::FS::PathToUTF8String(offline_cache_dir), FileSys::OpenMode::ReadWrite); FileSys::VfsRawCopyD(extracted_romfs_dir, temp_dir); } diff --git a/src/core/hle/service/am/applets/applet_web_browser.h b/src/core/hle/service/am/applets/applet_web_browser.h index 7fb77c40f..e63eca84f 100755 --- a/src/core/hle/service/am/applets/applet_web_browser.h +++ b/src/core/hle/service/am/applets/applet_web_browser.h @@ -7,7 +7,7 @@ #include #include "common/common_types.h" -#include "core/file_sys/vfs_types.h" +#include "core/file_sys/vfs/vfs_types.h" #include "core/hle/result.h" #include "core/hle/service/am/applets/applet_web_browser_types.h" #include "core/hle/service/am/applets/applets.h" diff --git a/src/core/hle/service/audio/audren_u.cpp b/src/core/hle/service/audio/audren_u.cpp index c8cc3bb44..191df61b5 100755 --- a/src/core/hle/service/audio/audren_u.cpp +++ b/src/core/hle/service/audio/audren_u.cpp @@ -139,7 +139,8 @@ private: ctx.WriteBufferC(performance_buffer.data(), performance_buffer.size(), 1); } } else { - LOG_ERROR(Service_Audio, "RequestUpdate failed error 0x{:02X}!", result.description); + LOG_ERROR(Service_Audio, "RequestUpdate failed error 0x{:02X}!", + result.GetDescription()); } IPC::ResponseBuilder rb{ctx, 2}; diff --git a/src/core/hle/service/bcat/backend/backend.h b/src/core/hle/service/bcat/backend/backend.h index 00c43d99b..50cc630ec 100755 --- a/src/core/hle/service/bcat/backend/backend.h +++ b/src/core/hle/service/bcat/backend/backend.h @@ -8,7 +8,7 @@ #include #include "common/common_types.h" -#include "core/file_sys/vfs_types.h" +#include "core/file_sys/vfs/vfs_types.h" #include "core/hle/result.h" #include "core/hle/service/kernel_helpers.h" diff --git a/src/core/hle/service/bcat/bcat_module.cpp b/src/core/hle/service/bcat/bcat_module.cpp index 4a58ba9a4..d03ebf485 100755 --- a/src/core/hle/service/bcat/bcat_module.cpp +++ b/src/core/hle/service/bcat/bcat_module.cpp @@ -8,7 +8,7 @@ #include "common/settings.h" #include "common/string_util.h" #include "core/core.h" -#include "core/file_sys/vfs.h" +#include "core/file_sys/vfs/vfs.h" #include "core/hle/kernel/k_readable_event.h" #include "core/hle/service/bcat/backend/backend.h" #include "core/hle/service/bcat/bcat.h" diff --git a/src/core/hle/service/caps/caps_a.cpp b/src/core/hle/service/caps/caps_a.cpp index 023841346..00f934160 100755 --- a/src/core/hle/service/caps/caps_a.cpp +++ b/src/core/hle/service/caps/caps_a.cpp @@ -202,14 +202,14 @@ Result IAlbumAccessorService::TranslateResult(Result in_result) { } if ((in_result.raw & 0x3801ff) == ResultUnknown1024.raw) { - if (in_result.description - 0x514 < 100) { + if (in_result.GetDescription() - 0x514 < 100) { return ResultInvalidFileData; } - if (in_result.description - 0x5dc < 100) { + if (in_result.GetDescription() - 0x5dc < 100) { return ResultInvalidFileData; } - if (in_result.description - 0x578 < 100) { + if (in_result.GetDescription() - 0x578 < 100) { if (in_result == ResultFileCountLimit) { return ResultUnknown22; } @@ -244,9 +244,10 @@ Result IAlbumAccessorService::TranslateResult(Result in_result) { return ResultUnknown1024; } - if (in_result.module == ErrorModule::FS) { - if ((in_result.description >> 0xc < 0x7d) || (in_result.description - 1000 < 2000) || - (((in_result.description - 3000) >> 3) < 0x271)) { + if (in_result.GetModule() == ErrorModule::FS) { + if ((in_result.GetDescription() >> 0xc < 0x7d) || + (in_result.GetDescription() - 1000 < 2000) || + (((in_result.GetDescription() - 3000) >> 3) < 0x271)) { // TODO: Translate FS error return in_result; } diff --git a/src/core/hle/service/cmif_serialization.h b/src/core/hle/service/cmif_serialization.h new file mode 100755 index 000000000..8e8cf2507 --- /dev/null +++ b/src/core/hle/service/cmif_serialization.h @@ -0,0 +1,337 @@ +// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/div_ceil.h" + +#include "core/hle/service/cmif_types.h" +#include "core/hle/service/ipc_helpers.h" +#include "core/hle/service/service.h" + +namespace Service { + +// clang-format off +struct RequestLayout { + u32 copy_handle_count; + u32 move_handle_count; + u32 cmif_raw_data_size; + u32 domain_interface_count; +}; + +template +constexpr u32 GetArgumentRawDataSize() { + if constexpr (ArgIndex >= std::tuple_size_v) { + return static_cast(DataOffset); + } else { + using ArgType = std::tuple_element_t; + + if constexpr (ArgumentTraits::Type == Type1 || ArgumentTraits::Type == Type2) { + constexpr size_t ArgAlign = alignof(ArgType); + constexpr size_t ArgSize = sizeof(ArgType); + + static_assert(PrevAlign <= ArgAlign, "Input argument is not ordered by alignment"); + + constexpr size_t ArgOffset = Common::AlignUp(DataOffset, ArgAlign); + constexpr size_t ArgEnd = ArgOffset + ArgSize; + + return GetArgumentRawDataSize(); + } else { + return GetArgumentRawDataSize(); + } + } +} + +template +constexpr u32 GetArgumentTypeCount() { + if constexpr (ArgIndex >= std::tuple_size_v) { + return static_cast(ArgCount); + } else { + using ArgType = std::tuple_element_t; + + if constexpr (ArgumentTraits::Type == DataType) { + return GetArgumentTypeCount(); + } else { + return GetArgumentTypeCount(); + } + } +} + +template +constexpr RequestLayout GetNonDomainReplyInLayout() { + return RequestLayout{ + .copy_handle_count = GetArgumentTypeCount(), + .move_handle_count = 0, + .cmif_raw_data_size = GetArgumentRawDataSize(), + .domain_interface_count = 0, + }; +} + +template +constexpr RequestLayout GetDomainReplyInLayout() { + return RequestLayout{ + .copy_handle_count = GetArgumentTypeCount(), + .move_handle_count = 0, + .cmif_raw_data_size = GetArgumentRawDataSize(), + .domain_interface_count = GetArgumentTypeCount(), + }; +} + +template +constexpr RequestLayout GetNonDomainReplyOutLayout() { + return RequestLayout{ + .copy_handle_count = GetArgumentTypeCount(), + .move_handle_count = GetArgumentTypeCount() + GetArgumentTypeCount(), + .cmif_raw_data_size = GetArgumentRawDataSize(), + .domain_interface_count = 0, + }; +} + +template +constexpr RequestLayout GetDomainReplyOutLayout() { + return RequestLayout{ + .copy_handle_count = GetArgumentTypeCount(), + .move_handle_count = GetArgumentTypeCount(), + .cmif_raw_data_size = GetArgumentRawDataSize(), + .domain_interface_count = GetArgumentTypeCount(), + }; +} + +template +constexpr RequestLayout GetReplyInLayout() { + return Domain ? GetDomainReplyInLayout() : GetNonDomainReplyInLayout(); +} + +template +constexpr RequestLayout GetReplyOutLayout() { + return Domain ? GetDomainReplyOutLayout() : GetNonDomainReplyOutLayout(); +} + +using OutTemporaryBuffers = std::array, 3>; + +template +void ReadInArgument(CallArguments& args, const u8* raw_data, HLERequestContext& ctx, OutTemporaryBuffers& temp) { + if constexpr (ArgIndex >= std::tuple_size_v) { + return; + } else { + using ArgType = std::tuple_element_t; + + if constexpr (ArgumentTraits::Type == ArgumentType::InData || ArgumentTraits::Type == ArgumentType::InProcessId) { + constexpr size_t ArgAlign = alignof(ArgType); + constexpr size_t ArgSize = sizeof(ArgType); + + static_assert(PrevAlign <= ArgAlign, "Input argument is not ordered by alignment"); + static_assert(!RawDataFinished, "All input interface arguments must appear after raw data"); + + constexpr size_t ArgOffset = Common::AlignUp(DataOffset, ArgAlign); + constexpr size_t ArgEnd = ArgOffset + ArgSize; + + if constexpr (ArgumentTraits::Type == ArgumentType::InProcessId) { + // TODO: abort parsing if PID is not provided? + // TODO: validate against raw data value? + std::get(args).pid = ctx.GetPID(); + } else { + std::memcpy(&std::get(args), raw_data + ArgOffset, ArgSize); + } + + return ReadInArgument(args, raw_data, ctx, temp); + } else if constexpr (ArgumentTraits::Type == ArgumentType::InInterface) { + constexpr size_t ArgAlign = alignof(u32); + constexpr size_t ArgSize = sizeof(u32); + constexpr size_t ArgOffset = Common::AlignUp(DataOffset, ArgAlign); + constexpr size_t ArgEnd = ArgOffset + ArgSize; + + static_assert(Domain); + ASSERT(ctx.GetDomainMessageHeader().input_object_count > 0); + + u32 value{}; + std::memcpy(&value, raw_data + ArgOffset, ArgSize); + std::get(args) = ctx.GetDomainHandler(value - 1); + + return ReadInArgument(args, raw_data, ctx, temp); + } else if constexpr (ArgumentTraits::Type == ArgumentType::InCopyHandle) { + std::get(args) = std::move(ctx.GetObjectFromHandle(ctx.GetCopyHandle(HandleIndex))); + + return ReadInArgument(args, raw_data, ctx, temp); + } else if constexpr (ArgumentTraits::Type == ArgumentType::InLargeData) { + constexpr size_t BufferSize = sizeof(ArgType); + + // Clear the existing data. + std::memset(&std::get(args), 0, BufferSize); + + std::span buffer{}; + + ASSERT(ctx.CanReadBuffer(InBufferIndex)); + if constexpr (ArgType::Attr & BufferAttr_HipcAutoSelect) { + buffer = ctx.ReadBuffer(InBufferIndex); + } else if constexpr (ArgType::Attr & BufferAttr_HipcMapAlias) { + buffer = ctx.ReadBufferA(InBufferIndex); + } else /* if (ArgType::Attr & BufferAttr_HipcPointer) */ { + buffer = ctx.ReadBufferX(InBufferIndex); + } + + std::memcpy(&std::get(args), buffer.data(), std::min(BufferSize, buffer.size())); + + return ReadInArgument(args, raw_data, ctx, temp); + } else if constexpr (ArgumentTraits::Type == ArgumentType::InBuffer) { + using ElementType = typename ArgType::Type; + + std::span buffer{}; + + if (ctx.CanReadBuffer(InBufferIndex)) { + if constexpr (ArgType::Attr & BufferAttr_HipcAutoSelect) { + buffer = ctx.ReadBuffer(InBufferIndex); + } else if constexpr (ArgType::Attr & BufferAttr_HipcMapAlias) { + buffer = ctx.ReadBufferA(InBufferIndex); + } else /* if (ArgType::Attr & BufferAttr_HipcPointer) */ { + buffer = ctx.ReadBufferX(InBufferIndex); + } + } + + ElementType* ptr = (ElementType*) buffer.data(); + size_t size = buffer.size() / sizeof(ElementType); + + std::get(args) = std::span(ptr, size); + + return ReadInArgument(args, raw_data, ctx, temp); + } else if constexpr (ArgumentTraits::Type == ArgumentType::OutLargeData) { + constexpr size_t BufferSize = sizeof(ArgType); + + // Clear the existing data. + std::memset(&std::get(args), 0, BufferSize); + + return ReadInArgument(args, raw_data, ctx, temp); + } else if constexpr (ArgumentTraits::Type == ArgumentType::OutBuffer) { + using ElementType = typename ArgType::Type; + + // Set up scratch buffer. + auto& buffer = temp[OutBufferIndex]; + if (ctx.CanWriteBuffer(OutBufferIndex)) { + buffer.resize_destructive(ctx.GetWriteBufferSize(OutBufferIndex)); + } else { + buffer.resize_destructive(0); + } + + ElementType* ptr = (ElementType*) buffer.data(); + size_t size = buffer.size() / sizeof(ElementType); + + std::get(args) = std::span(ptr, size); + + return ReadInArgument(args, raw_data, ctx, temp); + } else { + return ReadInArgument(args, raw_data, ctx, temp); + } + } +} + +template +void WriteOutArgument(CallArguments& args, u8* raw_data, HLERequestContext& ctx, OutTemporaryBuffers& temp) { + if constexpr (ArgIndex >= std::tuple_size_v) { + return; + } else { + using ArgType = std::tuple_element_t; + + if constexpr (ArgumentTraits::Type == ArgumentType::OutData) { + constexpr size_t ArgAlign = alignof(ArgType); + constexpr size_t ArgSize = sizeof(ArgType); + + static_assert(PrevAlign <= ArgAlign, "Output argument is not ordered by alignment"); + static_assert(!RawDataFinished, "All output interface arguments must appear after raw data"); + + constexpr size_t ArgOffset = Common::AlignUp(DataOffset, ArgAlign); + constexpr size_t ArgEnd = ArgOffset + ArgSize; + + std::memcpy(raw_data + ArgOffset, &std::get(args), ArgSize); + + return WriteOutArgument(args, raw_data, ctx, temp); + } else if constexpr (ArgumentTraits::Type == ArgumentType::OutInterface) { + if constexpr (Domain) { + ctx.AddDomainObject(std::get(args)); + } else { + ctx.AddMoveInterface(std::get(args)); + } + + return WriteOutArgument(args, raw_data, ctx, temp); + } else if constexpr (ArgumentTraits::Type == ArgumentType::OutCopyHandle) { + ctx.AddCopyObject(std::get(args).GetPointerUnsafe()); + + return WriteOutArgument(args, raw_data, ctx, temp); + } else if constexpr (ArgumentTraits::Type == ArgumentType::OutMoveHandle) { + ctx.AddMoveObject(std::get(args).GetPointerUnsafe()); + + return WriteOutArgument(args, raw_data, ctx, temp); + } else if constexpr (ArgumentTraits::Type == ArgumentType::OutLargeData) { + constexpr size_t BufferSize = sizeof(ArgType); + + ASSERT(ctx.CanWriteBuffer(OutBufferIndex)); + if constexpr (ArgType::Attr & BufferAttr_HipcAutoSelect) { + ctx.WriteBuffer(std::get(args), OutBufferIndex); + } else if constexpr (ArgType::Attr & BufferAttr_HipcMapAlias) { + ctx.WriteBufferB(&std::get(args), BufferSize, OutBufferIndex); + } else /* if (ArgType::Attr & BufferAttr_HipcPointer) */ { + ctx.WriteBufferC(&std::get(args), BufferSize, OutBufferIndex); + } + + return WriteOutArgument(args, raw_data, ctx, temp); + } else if constexpr (ArgumentTraits::Type == ArgumentType::OutBuffer) { + auto& buffer = temp[OutBufferIndex]; + const size_t size = buffer.size(); + + if (ctx.CanWriteBuffer(OutBufferIndex)) { + if constexpr (ArgType::Attr & BufferAttr_HipcAutoSelect) { + ctx.WriteBuffer(buffer.data(), size, OutBufferIndex); + } else if constexpr (ArgType::Attr & BufferAttr_HipcMapAlias) { + ctx.WriteBufferB(buffer.data(), size, OutBufferIndex); + } else /* if (ArgType::Attr & BufferAttr_HipcPointer) */ { + ctx.WriteBufferC(buffer.data(), size, OutBufferIndex); + } + } + + return WriteOutArgument( args, raw_data, ctx, temp); + } else { + return WriteOutArgument(args, raw_data, ctx, temp); + } + } +} + +template +void CmifReplyWrapImpl(HLERequestContext& ctx, T& t, Result (T::*f)(A...)) { + // Verify domain state. + if constexpr (Domain) { + ASSERT_MSG(ctx.GetManager()->IsDomain(), "Domain reply used on non-domain session"); + } else { + ASSERT_MSG(!ctx.GetManager()->IsDomain(), "Non-domain reply used on domain session"); + } + + using MethodArguments = std::tuple...>; + + OutTemporaryBuffers buffers{}; + auto call_arguments = std::tuple::Type...>(); + + // Read inputs. + const size_t offset_plus_command_id = ctx.GetDataPayloadOffset() + 2; + ReadInArgument(call_arguments, reinterpret_cast(ctx.CommandBuffer() + offset_plus_command_id), ctx, buffers); + + // Call. + const auto Callable = [&](CallArgs&... args) { + return (t.*f)(args...); + }; + const Result res = std::apply(Callable, call_arguments); + + // Write result. + constexpr RequestLayout layout = GetReplyOutLayout(); + IPC::ResponseBuilder rb{ctx, 2 + Common::DivCeil(layout.cmif_raw_data_size, sizeof(u32)), layout.copy_handle_count, layout.move_handle_count + layout.domain_interface_count}; + rb.Push(res); + + // Write out arguments. + WriteOutArgument(call_arguments, reinterpret_cast(ctx.CommandBuffer() + rb.GetCurrentOffset()), ctx, buffers); +} +// clang-format on + +template +template +inline void ServiceFramework::CmifReplyWrap(HLERequestContext& ctx) { + return CmifReplyWrapImpl(ctx, *static_cast(this), F); +} + +} // namespace Service diff --git a/src/core/hle/service/cmif_types.h b/src/core/hle/service/cmif_types.h new file mode 100755 index 000000000..b80028c19 --- /dev/null +++ b/src/core/hle/service/cmif_types.h @@ -0,0 +1,234 @@ +// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "core/hle/service/hle_ipc.h" + +namespace Service { + +// clang-format off +template +class Out { +public: + /* implicit */ Out(T& t) : raw(&t) {} + ~Out() = default; + + T* Get() const { + return raw; + } + + T& operator*() { + return *raw; + } + +private: + T* raw; +}; + +template +using SharedPointer = std::shared_ptr; + +struct ClientProcessId { + explicit operator bool() const { + return pid != 0; + } + + const u64& operator*() const { + return pid; + } + + u64 pid; +}; + +using ClientAppletResourceUserId = ClientProcessId; + +template +class InCopyHandle : public Kernel::KScopedAutoObject { +public: + using Type = T; + + template + /* implicit */ InCopyHandle(Args&&... args) : Kernel::KScopedAutoObject(std::forward(args)...) {} + ~InCopyHandle() = default; + + InCopyHandle& operator=(InCopyHandle&& rhs) { + Kernel::KScopedAutoObject::operator=(std::move(rhs)); + return *this; + } +}; + +template +class OutCopyHandle : public Kernel::KScopedAutoObject { +public: + using Type = T; + + template + /* implicit */ OutCopyHandle(Args&&... args) : Kernel::KScopedAutoObject(std::forward(args)...) {} + ~OutCopyHandle() = default; + + OutCopyHandle& operator=(OutCopyHandle&& rhs) { + Kernel::KScopedAutoObject::operator=(std::move(rhs)); + return *this; + } +}; + +template +class OutMoveHandle : public Kernel::KScopedAutoObject { +public: + using Type = T; + + template + /* implicit */ OutMoveHandle(Args&&... args) : Kernel::KScopedAutoObject(std::forward(args)...) {} + ~OutMoveHandle() = default; + + OutMoveHandle& operator=(OutMoveHandle&& rhs) { + Kernel::KScopedAutoObject::operator=(std::move(rhs)); + return *this; + } +}; + +enum BufferAttr : int { + BufferAttr_In = (1U << 0), + BufferAttr_Out = (1U << 1), + BufferAttr_HipcMapAlias = (1U << 2), + BufferAttr_HipcPointer = (1U << 3), + BufferAttr_FixedSize = (1U << 4), + BufferAttr_HipcAutoSelect = (1U << 5), + BufferAttr_HipcMapTransferAllowsNonSecure = (1U << 6), + BufferAttr_HipcMapTransferAllowsNonDevice = (1U << 7), +}; + +template +struct Buffer : public std::span { + static_assert(std::is_trivial_v, "Buffer type must be trivial"); + static_assert((A & BufferAttr_FixedSize) == 0, "Buffer attr must not contain FixedSize"); + static_assert(((A & BufferAttr_In) == 0) ^ ((A & BufferAttr_Out) == 0), "Buffer attr must be In or Out"); + static constexpr BufferAttr Attr = static_cast(A); + using Type = T; + + Buffer& operator=(const std::span& rhs) { + std::span::operator=(rhs); + return *this; + } + + T& operator*() const { + return *this->data(); + } + + explicit operator bool() const { + return this->size() > 0; + } +}; + +template +using InBuffer = Buffer; + +template +using InArray = Buffer; + +template +using OutBuffer = Buffer; + +template +using OutArray = Buffer; + +template +struct LargeData : public T { + static_assert(std::is_trivial_v, "LargeData type must be trivial"); + static_assert((A & BufferAttr_FixedSize) != 0, "LargeData attr must contain FixedSize"); + static_assert(((A & BufferAttr_In) == 0) ^ ((A & BufferAttr_Out) == 0), "LargeData attr must be In or Out"); + static constexpr BufferAttr Attr = static_cast(A); + using Type = T; +}; + +template +using InLargeData = LargeData; + +template +using OutLargeData = LargeData; + +template +struct RemoveOut { + using Type = std::remove_reference_t; +}; + +template +struct RemoveOut> { + using Type = T; +}; + +enum class ArgumentType { + InProcessId, + InData, + InInterface, + InCopyHandle, + OutData, + OutInterface, + OutCopyHandle, + OutMoveHandle, + InBuffer, + InLargeData, + OutBuffer, + OutLargeData, +}; + +template +struct ArgumentTraits; + +template <> +struct ArgumentTraits { + static constexpr ArgumentType Type = ArgumentType::InProcessId; +}; + +template +struct ArgumentTraits> { + static constexpr ArgumentType Type = ArgumentType::InInterface; +}; + +template +struct ArgumentTraits> { + static constexpr ArgumentType Type = ArgumentType::InCopyHandle; +}; + +template +struct ArgumentTraits>> { + static constexpr ArgumentType Type = ArgumentType::OutInterface; +}; + +template +struct ArgumentTraits> { + static constexpr ArgumentType Type = ArgumentType::OutData; +}; + +template +struct ArgumentTraits> { + static constexpr ArgumentType Type = ArgumentType::OutCopyHandle; +}; + +template +struct ArgumentTraits> { + static constexpr ArgumentType Type = ArgumentType::OutMoveHandle; +}; + +template +struct ArgumentTraits> { + static constexpr ArgumentType Type = (A & BufferAttr_In) == 0 ? ArgumentType::OutBuffer : ArgumentType::InBuffer; +}; + +template +struct ArgumentTraits> { + static constexpr ArgumentType Type = (A & BufferAttr_In) == 0 ? ArgumentType::OutLargeData : ArgumentType::InLargeData; +}; + +template +struct ArgumentTraits { + static constexpr ArgumentType Type = ArgumentType::InData; +}; +// clang-format on + +} // namespace Service diff --git a/src/core/hle/service/fatal/fatal.cpp b/src/core/hle/service/fatal/fatal.cpp index f545456ca..19376d266 100755 --- a/src/core/hle/service/fatal/fatal.cpp +++ b/src/core/hle/service/fatal/fatal.cpp @@ -73,8 +73,8 @@ static void GenerateErrorReport(Core::System& system, Result error_code, const F "Program entry point: 0x{:16X}\n" "\n", Common::g_scm_branch, Common::g_scm_desc, title_id, error_code.raw, - 2000 + static_cast(error_code.module.Value()), - static_cast(error_code.description.Value()), info.set_flags, info.program_entry_point); + 2000 + static_cast(error_code.GetModule()), + static_cast(error_code.GetDescription()), info.set_flags, info.program_entry_point); if (info.backtrace_size != 0x0) { crash_report += "Registers:\n"; for (size_t i = 0; i < info.registers.size(); i++) { diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp index f78fbe669..322957e5e 100755 --- a/src/core/hle/service/filesystem/filesystem.cpp +++ b/src/core/hle/service/filesystem/filesystem.cpp @@ -12,18 +12,17 @@ #include "core/file_sys/card_image.h" #include "core/file_sys/control_metadata.h" #include "core/file_sys/errors.h" -#include "core/file_sys/mode.h" #include "core/file_sys/patch_manager.h" #include "core/file_sys/registered_cache.h" #include "core/file_sys/romfs_factory.h" #include "core/file_sys/savedata_factory.h" #include "core/file_sys/sdmc_factory.h" -#include "core/file_sys/vfs.h" -#include "core/file_sys/vfs_offset.h" +#include "core/file_sys/vfs/vfs.h" +#include "core/file_sys/vfs/vfs_offset.h" #include "core/hle/service/filesystem/filesystem.h" -#include "core/hle/service/filesystem/fsp_ldr.h" -#include "core/hle/service/filesystem/fsp_pr.h" -#include "core/hle/service/filesystem/fsp_srv.h" +#include "core/hle/service/filesystem/fsp/fsp_ldr.h" +#include "core/hle/service/filesystem/fsp/fsp_pr.h" +#include "core/hle/service/filesystem/fsp/fsp_srv.h" #include "core/hle/service/filesystem/romfs_controller.h" #include "core/hle/service/filesystem/save_data_controller.h" #include "core/hle/service/server_manager.h" @@ -53,12 +52,12 @@ Result VfsDirectoryServiceWrapper::CreateFile(const std::string& path_, u64 size std::string path(Common::FS::SanitizePath(path_)); auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(path)); if (dir == nullptr) { - return FileSys::ERROR_PATH_NOT_FOUND; + return FileSys::ResultPathNotFound; } - FileSys::EntryType entry_type{}; + FileSys::DirectoryEntryType entry_type{}; if (GetEntryType(&entry_type, path) == ResultSuccess) { - return FileSys::ERROR_PATH_ALREADY_EXISTS; + return FileSys::ResultPathAlreadyExists; } auto file = dir->CreateFile(Common::FS::GetFilename(path)); @@ -82,7 +81,7 @@ Result VfsDirectoryServiceWrapper::DeleteFile(const std::string& path_) const { auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(path)); if (dir == nullptr || dir->GetFile(Common::FS::GetFilename(path)) == nullptr) { - return FileSys::ERROR_PATH_NOT_FOUND; + return FileSys::ResultPathNotFound; } if (!dir->DeleteFile(Common::FS::GetFilename(path))) { // TODO(DarkLordZach): Find a better error code for this @@ -153,12 +152,12 @@ Result VfsDirectoryServiceWrapper::RenameFile(const std::string& src_path_, if (Common::FS::GetParentPath(src_path) == Common::FS::GetParentPath(dest_path)) { // Use more-optimized vfs implementation rename. if (src == nullptr) { - return FileSys::ERROR_PATH_NOT_FOUND; + return FileSys::ResultPathNotFound; } if (dst && Common::FS::Exists(dst->GetFullPath())) { LOG_ERROR(Service_FS, "File at new_path={} already exists", dst->GetFullPath()); - return FileSys::ERROR_PATH_ALREADY_EXISTS; + return FileSys::ResultPathAlreadyExists; } if (!src->Rename(Common::FS::GetFilename(dest_path))) { @@ -195,7 +194,7 @@ Result VfsDirectoryServiceWrapper::RenameDirectory(const std::string& src_path_, if (Common::FS::GetParentPath(src_path) == Common::FS::GetParentPath(dest_path)) { // Use more-optimized vfs implementation rename. if (src == nullptr) - return FileSys::ERROR_PATH_NOT_FOUND; + return FileSys::ResultPathNotFound; if (!src->Rename(Common::FS::GetFilename(dest_path))) { // TODO(DarkLordZach): Find a better error code for this return ResultUnknown; @@ -214,7 +213,8 @@ Result VfsDirectoryServiceWrapper::RenameDirectory(const std::string& src_path_, } Result VfsDirectoryServiceWrapper::OpenFile(FileSys::VirtualFile* out_file, - const std::string& path_, FileSys::Mode mode) const { + const std::string& path_, + FileSys::OpenMode mode) const { const std::string path(Common::FS::SanitizePath(path_)); std::string_view npath = path; while (!npath.empty() && (npath[0] == '/' || npath[0] == '\\')) { @@ -223,10 +223,10 @@ Result VfsDirectoryServiceWrapper::OpenFile(FileSys::VirtualFile* out_file, auto file = backing->GetFileRelative(npath); if (file == nullptr) { - return FileSys::ERROR_PATH_NOT_FOUND; + return FileSys::ResultPathNotFound; } - if (mode == FileSys::Mode::Append) { + if (mode == FileSys::OpenMode::AllowAppend) { *out_file = std::make_shared(file, 0, file->GetSize()); } else { *out_file = file; @@ -241,50 +241,50 @@ Result VfsDirectoryServiceWrapper::OpenDirectory(FileSys::VirtualDir* out_direct auto dir = GetDirectoryRelativeWrapped(backing, path); if (dir == nullptr) { // TODO(DarkLordZach): Find a better error code for this - return FileSys::ERROR_PATH_NOT_FOUND; + return FileSys::ResultPathNotFound; } *out_directory = dir; return ResultSuccess; } -Result VfsDirectoryServiceWrapper::GetEntryType(FileSys::EntryType* out_entry_type, +Result VfsDirectoryServiceWrapper::GetEntryType(FileSys::DirectoryEntryType* out_entry_type, const std::string& path_) const { std::string path(Common::FS::SanitizePath(path_)); auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(path)); if (dir == nullptr) { - return FileSys::ERROR_PATH_NOT_FOUND; + return FileSys::ResultPathNotFound; } auto filename = Common::FS::GetFilename(path); // TODO(Subv): Some games use the '/' path, find out what this means. if (filename.empty()) { - *out_entry_type = FileSys::EntryType::Directory; + *out_entry_type = FileSys::DirectoryEntryType::Directory; return ResultSuccess; } if (dir->GetFile(filename) != nullptr) { - *out_entry_type = FileSys::EntryType::File; + *out_entry_type = FileSys::DirectoryEntryType::File; return ResultSuccess; } if (dir->GetSubdirectory(filename) != nullptr) { - *out_entry_type = FileSys::EntryType::Directory; + *out_entry_type = FileSys::DirectoryEntryType::Directory; return ResultSuccess; } - return FileSys::ERROR_PATH_NOT_FOUND; + return FileSys::ResultPathNotFound; } Result VfsDirectoryServiceWrapper::GetFileTimeStampRaw( FileSys::FileTimeStampRaw* out_file_time_stamp_raw, const std::string& path) const { auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(path)); if (dir == nullptr) { - return FileSys::ERROR_PATH_NOT_FOUND; + return FileSys::ResultPathNotFound; } - FileSys::EntryType entry_type; + FileSys::DirectoryEntryType entry_type; if (GetEntryType(&entry_type, path) != ResultSuccess) { - return FileSys::ERROR_PATH_NOT_FOUND; + return FileSys::ResultPathNotFound; } *out_file_time_stamp_raw = dir->GetFileTimeStamp(Common::FS::GetFilename(path)); @@ -317,7 +317,7 @@ Result FileSystemController::OpenProcess( const auto it = registrations.find(process_id); if (it == registrations.end()) { - return FileSys::ERROR_ENTITY_NOT_FOUND; + return FileSys::ResultTargetNotFound; } *out_program_id = it->second.program_id; @@ -347,7 +347,7 @@ std::shared_ptr FileSystemController::OpenSaveDataController std::shared_ptr FileSystemController::CreateSaveDataFactory( ProgramId program_id) { using YuzuPath = Common::FS::YuzuPath; - const auto rw_mode = FileSys::Mode::ReadWrite; + const auto rw_mode = FileSys::OpenMode::ReadWrite; auto vfs = system.GetFilesystem(); const auto nand_directory = @@ -360,12 +360,12 @@ Result FileSystemController::OpenSDMC(FileSys::VirtualDir* out_sdmc) const { LOG_TRACE(Service_FS, "Opening SDMC"); if (sdmc_factory == nullptr) { - return FileSys::ERROR_SD_CARD_NOT_FOUND; + return FileSys::ResultPortSdCardNoDevice; } auto sdmc = sdmc_factory->Open(); if (sdmc == nullptr) { - return FileSys::ERROR_SD_CARD_NOT_FOUND; + return FileSys::ResultPortSdCardNoDevice; } *out_sdmc = sdmc; @@ -377,12 +377,12 @@ Result FileSystemController::OpenBISPartition(FileSys::VirtualDir* out_bis_parti LOG_TRACE(Service_FS, "Opening BIS Partition with id={:08X}", id); if (bis_factory == nullptr) { - return FileSys::ERROR_ENTITY_NOT_FOUND; + return FileSys::ResultTargetNotFound; } auto part = bis_factory->OpenPartition(id); if (part == nullptr) { - return FileSys::ERROR_INVALID_ARGUMENT; + return FileSys::ResultInvalidArgument; } *out_bis_partition = part; @@ -394,12 +394,12 @@ Result FileSystemController::OpenBISPartitionStorage( LOG_TRACE(Service_FS, "Opening BIS Partition Storage with id={:08X}", id); if (bis_factory == nullptr) { - return FileSys::ERROR_ENTITY_NOT_FOUND; + return FileSys::ResultTargetNotFound; } auto part = bis_factory->OpenPartitionStorage(id, system.GetFilesystem()); if (part == nullptr) { - return FileSys::ERROR_INVALID_ARGUMENT; + return FileSys::ResultInvalidArgument; } *out_bis_partition_storage = part; @@ -686,15 +686,15 @@ void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool ove using YuzuPath = Common::FS::YuzuPath; const auto sdmc_dir_path = Common::FS::GetYuzuPath(YuzuPath::SDMCDir); const auto sdmc_load_dir_path = sdmc_dir_path / "atmosphere/contents"; - const auto rw_mode = FileSys::Mode::ReadWrite; + const auto rw_mode = FileSys::OpenMode::ReadWrite; auto nand_directory = vfs.OpenDirectory(Common::FS::GetYuzuPathString(YuzuPath::NANDDir), rw_mode); auto sd_directory = vfs.OpenDirectory(Common::FS::PathToUTF8String(sdmc_dir_path), rw_mode); - auto load_directory = - vfs.OpenDirectory(Common::FS::GetYuzuPathString(YuzuPath::LoadDir), FileSys::Mode::Read); - auto sd_load_directory = - vfs.OpenDirectory(Common::FS::PathToUTF8String(sdmc_load_dir_path), FileSys::Mode::Read); + auto load_directory = vfs.OpenDirectory(Common::FS::GetYuzuPathString(YuzuPath::LoadDir), + FileSys::OpenMode::Read); + auto sd_load_directory = vfs.OpenDirectory(Common::FS::PathToUTF8String(sdmc_load_dir_path), + FileSys::OpenMode::Read); auto dump_directory = vfs.OpenDirectory(Common::FS::GetYuzuPathString(YuzuPath::DumpDir), rw_mode); diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h index 6b1ba2b49..c7fd5e2db 100755 --- a/src/core/hle/service/filesystem/filesystem.h +++ b/src/core/hle/service/filesystem/filesystem.h @@ -4,9 +4,11 @@ #pragma once #include +#include #include "common/common_types.h" -#include "core/file_sys/directory.h" -#include "core/file_sys/vfs.h" +#include "core/file_sys/fs_directory.h" +#include "core/file_sys/fs_filesystem.h" +#include "core/file_sys/vfs/vfs.h" #include "core/hle/result.h" namespace Core { @@ -26,7 +28,6 @@ class XCI; enum class BisPartitionId : u32; enum class ContentRecordType : u8; -enum class Mode : u32; enum class SaveDataSpaceId : u8; enum class SaveDataType : u8; enum class StorageId : u8; @@ -57,13 +58,6 @@ enum class ImageDirectoryId : u32 { SdCard, }; -enum class OpenDirectoryMode : u64 { - Directory = (1 << 0), - File = (1 << 1), - All = Directory | File -}; -DECLARE_ENUM_FLAG_OPERATORS(OpenDirectoryMode); - using ProcessId = u64; using ProgramId = u64; @@ -237,7 +231,7 @@ public: * @return Opened file, or error code */ Result OpenFile(FileSys::VirtualFile* out_file, const std::string& path, - FileSys::Mode mode) const; + FileSys::OpenMode mode) const; /** * Open a directory specified by its path @@ -250,7 +244,7 @@ public: * Get the type of the specified path * @return The type of the specified path or error code */ - Result GetEntryType(FileSys::EntryType* out_entry_type, const std::string& path) const; + Result GetEntryType(FileSys::DirectoryEntryType* out_entry_type, const std::string& path) const; /** * Get the timestamp of the specified path diff --git a/src/core/hle/service/filesystem/fsp/fs_i_directory.cpp b/src/core/hle/service/filesystem/fsp/fs_i_directory.cpp new file mode 100755 index 000000000..39690018b --- /dev/null +++ b/src/core/hle/service/filesystem/fsp/fs_i_directory.cpp @@ -0,0 +1,84 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/file_sys/fs_filesystem.h" +#include "core/file_sys/savedata_factory.h" +#include "core/hle/service/filesystem/fsp/fs_i_directory.h" +#include "core/hle/service/ipc_helpers.h" + +namespace Service::FileSystem { + +template +static void BuildEntryIndex(std::vector& entries, + const std::vector& new_data, FileSys::DirectoryEntryType type) { + entries.reserve(entries.size() + new_data.size()); + + for (const auto& new_entry : new_data) { + auto name = new_entry->GetName(); + + if (type == FileSys::DirectoryEntryType::File && + name == FileSys::GetSaveDataSizeFileName()) { + continue; + } + + entries.emplace_back(name, static_cast(type), + type == FileSys::DirectoryEntryType::Directory ? 0 + : new_entry->GetSize()); + } +} + +IDirectory::IDirectory(Core::System& system_, FileSys::VirtualDir backend_, + FileSys::OpenDirectoryMode mode) + : ServiceFramework{system_, "IDirectory"}, backend(std::move(backend_)) { + static const FunctionInfo functions[] = { + {0, &IDirectory::Read, "Read"}, + {1, &IDirectory::GetEntryCount, "GetEntryCount"}, + }; + RegisterHandlers(functions); + + // TODO(DarkLordZach): Verify that this is the correct behavior. + // Build entry index now to save time later. + if (True(mode & FileSys::OpenDirectoryMode::Directory)) { + BuildEntryIndex(entries, backend->GetSubdirectories(), + FileSys::DirectoryEntryType::Directory); + } + if (True(mode & FileSys::OpenDirectoryMode::File)) { + BuildEntryIndex(entries, backend->GetFiles(), FileSys::DirectoryEntryType::File); + } +} + +void IDirectory::Read(HLERequestContext& ctx) { + LOG_DEBUG(Service_FS, "called."); + + // Calculate how many entries we can fit in the output buffer + const u64 count_entries = ctx.GetWriteBufferNumElements(); + + // Cap at total number of entries. + const u64 actual_entries = std::min(count_entries, entries.size() - next_entry_index); + + // Determine data start and end + const auto* begin = reinterpret_cast(entries.data() + next_entry_index); + const auto* end = reinterpret_cast(entries.data() + next_entry_index + actual_entries); + const auto range_size = static_cast(std::distance(begin, end)); + + next_entry_index += actual_entries; + + // Write the data to memory + ctx.WriteBuffer(begin, range_size); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(ResultSuccess); + rb.Push(actual_entries); +} + +void IDirectory::GetEntryCount(HLERequestContext& ctx) { + LOG_DEBUG(Service_FS, "called"); + + u64 count = entries.size() - next_entry_index; + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(ResultSuccess); + rb.Push(count); +} + +} // namespace Service::FileSystem diff --git a/src/core/hle/service/filesystem/fsp/fs_i_directory.h b/src/core/hle/service/filesystem/fsp/fs_i_directory.h new file mode 100755 index 000000000..793ecfcd7 --- /dev/null +++ b/src/core/hle/service/filesystem/fsp/fs_i_directory.h @@ -0,0 +1,30 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "core/file_sys/vfs/vfs.h" +#include "core/hle/service/filesystem/filesystem.h" +#include "core/hle/service/service.h" + +namespace FileSys { +struct DirectoryEntry; +} + +namespace Service::FileSystem { + +class IDirectory final : public ServiceFramework { +public: + explicit IDirectory(Core::System& system_, FileSys::VirtualDir backend_, + FileSys::OpenDirectoryMode mode); + +private: + FileSys::VirtualDir backend; + std::vector entries; + u64 next_entry_index = 0; + + void Read(HLERequestContext& ctx); + void GetEntryCount(HLERequestContext& ctx); +}; + +} // namespace Service::FileSystem diff --git a/src/core/hle/service/filesystem/fsp/fs_i_file.cpp b/src/core/hle/service/filesystem/fsp/fs_i_file.cpp new file mode 100755 index 000000000..9a18f6ec5 --- /dev/null +++ b/src/core/hle/service/filesystem/fsp/fs_i_file.cpp @@ -0,0 +1,127 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/file_sys/errors.h" +#include "core/hle/service/filesystem/fsp/fs_i_file.h" +#include "core/hle/service/ipc_helpers.h" + +namespace Service::FileSystem { + +IFile::IFile(Core::System& system_, FileSys::VirtualFile backend_) + : ServiceFramework{system_, "IFile"}, backend(std::move(backend_)) { + static const FunctionInfo functions[] = { + {0, &IFile::Read, "Read"}, + {1, &IFile::Write, "Write"}, + {2, &IFile::Flush, "Flush"}, + {3, &IFile::SetSize, "SetSize"}, + {4, &IFile::GetSize, "GetSize"}, + {5, nullptr, "OperateRange"}, + {6, nullptr, "OperateRangeWithBuffer"}, + }; + RegisterHandlers(functions); +} + +void IFile::Read(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const u64 option = rp.Pop(); + const s64 offset = rp.Pop(); + const s64 length = rp.Pop(); + + LOG_DEBUG(Service_FS, "called, option={}, offset=0x{:X}, length={}", option, offset, length); + + // Error checking + if (length < 0) { + LOG_ERROR(Service_FS, "Length is less than 0, length={}", length); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(FileSys::ResultInvalidSize); + return; + } + if (offset < 0) { + LOG_ERROR(Service_FS, "Offset is less than 0, offset={}", offset); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(FileSys::ResultInvalidOffset); + return; + } + + // Read the data from the Storage backend + std::vector output = backend->ReadBytes(length, offset); + + // Write the data to memory + ctx.WriteBuffer(output); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(ResultSuccess); + rb.Push(static_cast(output.size())); +} + +void IFile::Write(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const u64 option = rp.Pop(); + const s64 offset = rp.Pop(); + const s64 length = rp.Pop(); + + LOG_DEBUG(Service_FS, "called, option={}, offset=0x{:X}, length={}", option, offset, length); + + // Error checking + if (length < 0) { + LOG_ERROR(Service_FS, "Length is less than 0, length={}", length); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(FileSys::ResultInvalidSize); + return; + } + if (offset < 0) { + LOG_ERROR(Service_FS, "Offset is less than 0, offset={}", offset); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(FileSys::ResultInvalidOffset); + return; + } + + const auto data = ctx.ReadBuffer(); + + ASSERT_MSG(static_cast(data.size()) <= length, + "Attempting to write more data than requested (requested={:016X}, actual={:016X}).", + length, data.size()); + + // Write the data to the Storage backend + const auto write_size = + static_cast(std::distance(data.begin(), data.begin() + length)); + const std::size_t written = backend->Write(data.data(), write_size, offset); + + ASSERT_MSG(static_cast(written) == length, + "Could not write all bytes to file (requested={:016X}, actual={:016X}).", length, + written); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); +} + +void IFile::Flush(HLERequestContext& ctx) { + LOG_DEBUG(Service_FS, "called"); + + // Exists for SDK compatibiltity -- No need to flush file. + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); +} + +void IFile::SetSize(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const u64 size = rp.Pop(); + LOG_DEBUG(Service_FS, "called, size={}", size); + + backend->Resize(size); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); +} + +void IFile::GetSize(HLERequestContext& ctx) { + const u64 size = backend->GetSize(); + LOG_DEBUG(Service_FS, "called, size={}", size); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(ResultSuccess); + rb.Push(size); +} + +} // namespace Service::FileSystem diff --git a/src/core/hle/service/filesystem/fsp/fs_i_file.h b/src/core/hle/service/filesystem/fsp/fs_i_file.h new file mode 100755 index 000000000..5e5430c67 --- /dev/null +++ b/src/core/hle/service/filesystem/fsp/fs_i_file.h @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "core/hle/service/filesystem/filesystem.h" +#include "core/hle/service/service.h" + +namespace Service::FileSystem { + +class IFile final : public ServiceFramework { +public: + explicit IFile(Core::System& system_, FileSys::VirtualFile backend_); + +private: + FileSys::VirtualFile backend; + + void Read(HLERequestContext& ctx); + void Write(HLERequestContext& ctx); + void Flush(HLERequestContext& ctx); + void SetSize(HLERequestContext& ctx); + void GetSize(HLERequestContext& ctx); +}; + +} // namespace Service::FileSystem diff --git a/src/core/hle/service/filesystem/fsp/fs_i_filesystem.cpp b/src/core/hle/service/filesystem/fsp/fs_i_filesystem.cpp new file mode 100755 index 000000000..efa394dd1 --- /dev/null +++ b/src/core/hle/service/filesystem/fsp/fs_i_filesystem.cpp @@ -0,0 +1,262 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/string_util.h" +#include "core/hle/service/filesystem/fsp/fs_i_directory.h" +#include "core/hle/service/filesystem/fsp/fs_i_file.h" +#include "core/hle/service/filesystem/fsp/fs_i_filesystem.h" +#include "core/hle/service/ipc_helpers.h" + +namespace Service::FileSystem { + +IFileSystem::IFileSystem(Core::System& system_, FileSys::VirtualDir backend_, SizeGetter size_) + : ServiceFramework{system_, "IFileSystem"}, backend{std::move(backend_)}, size{std::move( + size_)} { + static const FunctionInfo functions[] = { + {0, &IFileSystem::CreateFile, "CreateFile"}, + {1, &IFileSystem::DeleteFile, "DeleteFile"}, + {2, &IFileSystem::CreateDirectory, "CreateDirectory"}, + {3, &IFileSystem::DeleteDirectory, "DeleteDirectory"}, + {4, &IFileSystem::DeleteDirectoryRecursively, "DeleteDirectoryRecursively"}, + {5, &IFileSystem::RenameFile, "RenameFile"}, + {6, nullptr, "RenameDirectory"}, + {7, &IFileSystem::GetEntryType, "GetEntryType"}, + {8, &IFileSystem::OpenFile, "OpenFile"}, + {9, &IFileSystem::OpenDirectory, "OpenDirectory"}, + {10, &IFileSystem::Commit, "Commit"}, + {11, &IFileSystem::GetFreeSpaceSize, "GetFreeSpaceSize"}, + {12, &IFileSystem::GetTotalSpaceSize, "GetTotalSpaceSize"}, + {13, &IFileSystem::CleanDirectoryRecursively, "CleanDirectoryRecursively"}, + {14, &IFileSystem::GetFileTimeStampRaw, "GetFileTimeStampRaw"}, + {15, nullptr, "QueryEntry"}, + {16, &IFileSystem::GetFileSystemAttribute, "GetFileSystemAttribute"}, + }; + RegisterHandlers(functions); +} + +void IFileSystem::CreateFile(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + const auto file_buffer = ctx.ReadBuffer(); + const std::string name = Common::StringFromBuffer(file_buffer); + + const u64 file_mode = rp.Pop(); + const u32 file_size = rp.Pop(); + + LOG_DEBUG(Service_FS, "called. file={}, mode=0x{:X}, size=0x{:08X}", name, file_mode, + file_size); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(backend.CreateFile(name, file_size)); +} + +void IFileSystem::DeleteFile(HLERequestContext& ctx) { + const auto file_buffer = ctx.ReadBuffer(); + const std::string name = Common::StringFromBuffer(file_buffer); + + LOG_DEBUG(Service_FS, "called. file={}", name); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(backend.DeleteFile(name)); +} + +void IFileSystem::CreateDirectory(HLERequestContext& ctx) { + const auto file_buffer = ctx.ReadBuffer(); + const std::string name = Common::StringFromBuffer(file_buffer); + + LOG_DEBUG(Service_FS, "called. directory={}", name); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(backend.CreateDirectory(name)); +} + +void IFileSystem::DeleteDirectory(HLERequestContext& ctx) { + const auto file_buffer = ctx.ReadBuffer(); + const std::string name = Common::StringFromBuffer(file_buffer); + + LOG_DEBUG(Service_FS, "called. directory={}", name); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(backend.DeleteDirectory(name)); +} + +void IFileSystem::DeleteDirectoryRecursively(HLERequestContext& ctx) { + const auto file_buffer = ctx.ReadBuffer(); + const std::string name = Common::StringFromBuffer(file_buffer); + + LOG_DEBUG(Service_FS, "called. directory={}", name); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(backend.DeleteDirectoryRecursively(name)); +} + +void IFileSystem::CleanDirectoryRecursively(HLERequestContext& ctx) { + const auto file_buffer = ctx.ReadBuffer(); + const std::string name = Common::StringFromBuffer(file_buffer); + + LOG_DEBUG(Service_FS, "called. Directory: {}", name); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(backend.CleanDirectoryRecursively(name)); +} + +void IFileSystem::RenameFile(HLERequestContext& ctx) { + const std::string src_name = Common::StringFromBuffer(ctx.ReadBuffer(0)); + const std::string dst_name = Common::StringFromBuffer(ctx.ReadBuffer(1)); + + LOG_DEBUG(Service_FS, "called. file '{}' to file '{}'", src_name, dst_name); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(backend.RenameFile(src_name, dst_name)); +} + +void IFileSystem::OpenFile(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + const auto file_buffer = ctx.ReadBuffer(); + const std::string name = Common::StringFromBuffer(file_buffer); + + const auto mode = static_cast(rp.Pop()); + + LOG_DEBUG(Service_FS, "called. file={}, mode={}", name, mode); + + FileSys::VirtualFile vfs_file{}; + auto result = backend.OpenFile(&vfs_file, name, mode); + if (result != ResultSuccess) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + return; + } + + auto file = std::make_shared(system, vfs_file); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(ResultSuccess); + rb.PushIpcInterface(std::move(file)); +} + +void IFileSystem::OpenDirectory(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + const auto file_buffer = ctx.ReadBuffer(); + const std::string name = Common::StringFromBuffer(file_buffer); + const auto mode = rp.PopRaw(); + + LOG_DEBUG(Service_FS, "called. directory={}, mode={}", name, mode); + + FileSys::VirtualDir vfs_dir{}; + auto result = backend.OpenDirectory(&vfs_dir, name); + if (result != ResultSuccess) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + return; + } + + auto directory = std::make_shared(system, vfs_dir, mode); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(ResultSuccess); + rb.PushIpcInterface(std::move(directory)); +} + +void IFileSystem::GetEntryType(HLERequestContext& ctx) { + const auto file_buffer = ctx.ReadBuffer(); + const std::string name = Common::StringFromBuffer(file_buffer); + + LOG_DEBUG(Service_FS, "called. file={}", name); + + FileSys::DirectoryEntryType vfs_entry_type{}; + auto result = backend.GetEntryType(&vfs_entry_type, name); + if (result != ResultSuccess) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + return; + } + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(static_cast(vfs_entry_type)); +} + +void IFileSystem::Commit(HLERequestContext& ctx) { + LOG_WARNING(Service_FS, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); +} + +void IFileSystem::GetFreeSpaceSize(HLERequestContext& ctx) { + LOG_DEBUG(Service_FS, "called"); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(ResultSuccess); + rb.Push(size.get_free_size()); +} + +void IFileSystem::GetTotalSpaceSize(HLERequestContext& ctx) { + LOG_DEBUG(Service_FS, "called"); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(ResultSuccess); + rb.Push(size.get_total_size()); +} + +void IFileSystem::GetFileTimeStampRaw(HLERequestContext& ctx) { + const auto file_buffer = ctx.ReadBuffer(); + const std::string name = Common::StringFromBuffer(file_buffer); + + LOG_WARNING(Service_FS, "(Partial Implementation) called. file={}", name); + + FileSys::FileTimeStampRaw vfs_timestamp{}; + auto result = backend.GetFileTimeStampRaw(&vfs_timestamp, name); + if (result != ResultSuccess) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + return; + } + + IPC::ResponseBuilder rb{ctx, 10}; + rb.Push(ResultSuccess); + rb.PushRaw(vfs_timestamp); +} + +void IFileSystem::GetFileSystemAttribute(HLERequestContext& ctx) { + LOG_WARNING(Service_FS, "(STUBBED) called"); + + struct FileSystemAttribute { + u8 dir_entry_name_length_max_defined; + u8 file_entry_name_length_max_defined; + u8 dir_path_name_length_max_defined; + u8 file_path_name_length_max_defined; + INSERT_PADDING_BYTES_NOINIT(0x5); + u8 utf16_dir_entry_name_length_max_defined; + u8 utf16_file_entry_name_length_max_defined; + u8 utf16_dir_path_name_length_max_defined; + u8 utf16_file_path_name_length_max_defined; + INSERT_PADDING_BYTES_NOINIT(0x18); + s32 dir_entry_name_length_max; + s32 file_entry_name_length_max; + s32 dir_path_name_length_max; + s32 file_path_name_length_max; + INSERT_PADDING_WORDS_NOINIT(0x5); + s32 utf16_dir_entry_name_length_max; + s32 utf16_file_entry_name_length_max; + s32 utf16_dir_path_name_length_max; + s32 utf16_file_path_name_length_max; + INSERT_PADDING_WORDS_NOINIT(0x18); + INSERT_PADDING_WORDS_NOINIT(0x1); + }; + static_assert(sizeof(FileSystemAttribute) == 0xc0, "FileSystemAttribute has incorrect size"); + + FileSystemAttribute savedata_attribute{}; + savedata_attribute.dir_entry_name_length_max_defined = true; + savedata_attribute.file_entry_name_length_max_defined = true; + savedata_attribute.dir_entry_name_length_max = 0x40; + savedata_attribute.file_entry_name_length_max = 0x40; + + IPC::ResponseBuilder rb{ctx, 50}; + rb.Push(ResultSuccess); + rb.PushRaw(savedata_attribute); +} + +} // namespace Service::FileSystem diff --git a/src/core/hle/service/filesystem/fsp/fs_i_filesystem.h b/src/core/hle/service/filesystem/fsp/fs_i_filesystem.h new file mode 100755 index 000000000..b06b3ef0e --- /dev/null +++ b/src/core/hle/service/filesystem/fsp/fs_i_filesystem.h @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "core/file_sys/vfs/vfs.h" +#include "core/hle/service/filesystem/filesystem.h" +#include "core/hle/service/filesystem/fsp/fsp_util.h" +#include "core/hle/service/service.h" + +namespace Service::FileSystem { + +class IFileSystem final : public ServiceFramework { +public: + explicit IFileSystem(Core::System& system_, FileSys::VirtualDir backend_, SizeGetter size_); + + void CreateFile(HLERequestContext& ctx); + void DeleteFile(HLERequestContext& ctx); + void CreateDirectory(HLERequestContext& ctx); + void DeleteDirectory(HLERequestContext& ctx); + void DeleteDirectoryRecursively(HLERequestContext& ctx); + void CleanDirectoryRecursively(HLERequestContext& ctx); + void RenameFile(HLERequestContext& ctx); + void OpenFile(HLERequestContext& ctx); + void OpenDirectory(HLERequestContext& ctx); + void GetEntryType(HLERequestContext& ctx); + void Commit(HLERequestContext& ctx); + void GetFreeSpaceSize(HLERequestContext& ctx); + void GetTotalSpaceSize(HLERequestContext& ctx); + void GetFileTimeStampRaw(HLERequestContext& ctx); + void GetFileSystemAttribute(HLERequestContext& ctx); + +private: + VfsDirectoryServiceWrapper backend; + SizeGetter size; +}; + +} // namespace Service::FileSystem diff --git a/src/core/hle/service/filesystem/fsp/fs_i_storage.cpp b/src/core/hle/service/filesystem/fsp/fs_i_storage.cpp new file mode 100755 index 000000000..98223c1f9 --- /dev/null +++ b/src/core/hle/service/filesystem/fsp/fs_i_storage.cpp @@ -0,0 +1,62 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/file_sys/errors.h" +#include "core/hle/service/filesystem/fsp/fs_i_storage.h" +#include "core/hle/service/ipc_helpers.h" + +namespace Service::FileSystem { + +IStorage::IStorage(Core::System& system_, FileSys::VirtualFile backend_) + : ServiceFramework{system_, "IStorage"}, backend(std::move(backend_)) { + static const FunctionInfo functions[] = { + {0, &IStorage::Read, "Read"}, + {1, nullptr, "Write"}, + {2, nullptr, "Flush"}, + {3, nullptr, "SetSize"}, + {4, &IStorage::GetSize, "GetSize"}, + {5, nullptr, "OperateRange"}, + }; + RegisterHandlers(functions); +} + +void IStorage::Read(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const s64 offset = rp.Pop(); + const s64 length = rp.Pop(); + + LOG_DEBUG(Service_FS, "called, offset=0x{:X}, length={}", offset, length); + + // Error checking + if (length < 0) { + LOG_ERROR(Service_FS, "Length is less than 0, length={}", length); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(FileSys::ResultInvalidSize); + return; + } + if (offset < 0) { + LOG_ERROR(Service_FS, "Offset is less than 0, offset={}", offset); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(FileSys::ResultInvalidOffset); + return; + } + + // Read the data from the Storage backend + std::vector output = backend->ReadBytes(length, offset); + // Write the data to memory + ctx.WriteBuffer(output); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); +} + +void IStorage::GetSize(HLERequestContext& ctx) { + const u64 size = backend->GetSize(); + LOG_DEBUG(Service_FS, "called, size={}", size); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(ResultSuccess); + rb.Push(size); +} + +} // namespace Service::FileSystem diff --git a/src/core/hle/service/filesystem/fsp/fs_i_storage.h b/src/core/hle/service/filesystem/fsp/fs_i_storage.h new file mode 100755 index 000000000..cb5bebcc9 --- /dev/null +++ b/src/core/hle/service/filesystem/fsp/fs_i_storage.h @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "core/file_sys/vfs/vfs.h" +#include "core/hle/service/filesystem/filesystem.h" +#include "core/hle/service/service.h" + +namespace Service::FileSystem { + +class IStorage final : public ServiceFramework { +public: + explicit IStorage(Core::System& system_, FileSys::VirtualFile backend_); + +private: + FileSys::VirtualFile backend; + + void Read(HLERequestContext& ctx); + void GetSize(HLERequestContext& ctx); +}; + +} // namespace Service::FileSystem diff --git a/src/core/hle/service/filesystem/fsp/fsp_ldr.cpp b/src/core/hle/service/filesystem/fsp/fsp_ldr.cpp new file mode 100755 index 000000000..8ee733f47 --- /dev/null +++ b/src/core/hle/service/filesystem/fsp/fsp_ldr.cpp @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/hle/service/filesystem/fsp/fsp_ldr.h" + +namespace Service::FileSystem { + +FSP_LDR::FSP_LDR(Core::System& system_) : ServiceFramework{system_, "fsp:ldr"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, nullptr, "OpenCodeFileSystem"}, + {1, nullptr, "IsArchivedProgram"}, + {2, nullptr, "SetCurrentProcess"}, + }; + // clang-format on + + RegisterHandlers(functions); +} + +FSP_LDR::~FSP_LDR() = default; + +} // namespace Service::FileSystem diff --git a/src/core/hle/service/filesystem/fsp/fsp_ldr.h b/src/core/hle/service/filesystem/fsp/fsp_ldr.h new file mode 100755 index 000000000..358739a87 --- /dev/null +++ b/src/core/hle/service/filesystem/fsp/fsp_ldr.h @@ -0,0 +1,20 @@ +// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "core/hle/service/service.h" + +namespace Core { +class System; +} + +namespace Service::FileSystem { + +class FSP_LDR final : public ServiceFramework { +public: + explicit FSP_LDR(Core::System& system_); + ~FSP_LDR() override; +}; + +} // namespace Service::FileSystem diff --git a/src/core/hle/service/filesystem/fsp/fsp_pr.cpp b/src/core/hle/service/filesystem/fsp/fsp_pr.cpp new file mode 100755 index 000000000..7c03ebaea --- /dev/null +++ b/src/core/hle/service/filesystem/fsp/fsp_pr.cpp @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/hle/service/filesystem/fsp/fsp_pr.h" + +namespace Service::FileSystem { + +FSP_PR::FSP_PR(Core::System& system_) : ServiceFramework{system_, "fsp:pr"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, nullptr, "RegisterProgram"}, + {1, nullptr, "UnregisterProgram"}, + {2, nullptr, "SetCurrentProcess"}, + {256, nullptr, "SetEnabledProgramVerification"}, + }; + // clang-format on + + RegisterHandlers(functions); +} + +FSP_PR::~FSP_PR() = default; + +} // namespace Service::FileSystem diff --git a/src/core/hle/service/filesystem/fsp/fsp_pr.h b/src/core/hle/service/filesystem/fsp/fsp_pr.h new file mode 100755 index 000000000..bd4e0a730 --- /dev/null +++ b/src/core/hle/service/filesystem/fsp/fsp_pr.h @@ -0,0 +1,20 @@ +// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "core/hle/service/service.h" + +namespace Core { +class System; +} + +namespace Service::FileSystem { + +class FSP_PR final : public ServiceFramework { +public: + explicit FSP_PR(Core::System& system_); + ~FSP_PR() override; +}; + +} // namespace Service::FileSystem diff --git a/src/core/hle/service/filesystem/fsp/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp/fsp_srv.cpp new file mode 100755 index 000000000..2be72b021 --- /dev/null +++ b/src/core/hle/service/filesystem/fsp/fsp_srv.cpp @@ -0,0 +1,727 @@ +// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include +#include +#include + +#include "common/assert.h" +#include "common/common_types.h" +#include "common/hex_util.h" +#include "common/logging/log.h" +#include "common/settings.h" +#include "common/string_util.h" +#include "core/core.h" +#include "core/file_sys/errors.h" +#include "core/file_sys/fs_directory.h" +#include "core/file_sys/fs_filesystem.h" +#include "core/file_sys/nca_metadata.h" +#include "core/file_sys/patch_manager.h" +#include "core/file_sys/romfs_factory.h" +#include "core/file_sys/savedata_factory.h" +#include "core/file_sys/system_archive/system_archive.h" +#include "core/file_sys/vfs/vfs.h" +#include "core/hle/result.h" +#include "core/hle/service/filesystem/filesystem.h" +#include "core/hle/service/filesystem/fsp/fs_i_filesystem.h" +#include "core/hle/service/filesystem/fsp/fs_i_storage.h" +#include "core/hle/service/filesystem/fsp/fsp_srv.h" +#include "core/hle/service/filesystem/romfs_controller.h" +#include "core/hle/service/filesystem/save_data_controller.h" +#include "core/hle/service/hle_ipc.h" +#include "core/hle/service/ipc_helpers.h" +#include "core/reporter.h" + +namespace Service::FileSystem { +enum class FileSystemType : u8 { + Invalid0 = 0, + Invalid1 = 1, + Logo = 2, + ContentControl = 3, + ContentManual = 4, + ContentMeta = 5, + ContentData = 6, + ApplicationPackage = 7, +}; + +class ISaveDataInfoReader final : public ServiceFramework { +public: + explicit ISaveDataInfoReader(Core::System& system_, + std::shared_ptr save_data_controller_, + FileSys::SaveDataSpaceId space) + : ServiceFramework{system_, "ISaveDataInfoReader"}, save_data_controller{ + save_data_controller_} { + static const FunctionInfo functions[] = { + {0, &ISaveDataInfoReader::ReadSaveDataInfo, "ReadSaveDataInfo"}, + }; + RegisterHandlers(functions); + + FindAllSaves(space); + } + + void ReadSaveDataInfo(HLERequestContext& ctx) { + LOG_DEBUG(Service_FS, "called"); + + // Calculate how many entries we can fit in the output buffer + const u64 count_entries = ctx.GetWriteBufferNumElements(); + + // Cap at total number of entries. + const u64 actual_entries = std::min(count_entries, info.size() - next_entry_index); + + // Determine data start and end + const auto* begin = reinterpret_cast(info.data() + next_entry_index); + const auto* end = reinterpret_cast(info.data() + next_entry_index + actual_entries); + const auto range_size = static_cast(std::distance(begin, end)); + + next_entry_index += actual_entries; + + // Write the data to memory + ctx.WriteBuffer(begin, range_size); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(ResultSuccess); + rb.Push(actual_entries); + } + +private: + static u64 stoull_be(std::string_view str) { + if (str.size() != 16) + return 0; + + const auto bytes = Common::HexStringToArray<0x8>(str); + u64 out{}; + std::memcpy(&out, bytes.data(), sizeof(u64)); + + return Common::swap64(out); + } + + void FindAllSaves(FileSys::SaveDataSpaceId space) { + FileSys::VirtualDir save_root{}; + const auto result = save_data_controller->OpenSaveDataSpace(&save_root, space); + + if (result != ResultSuccess || save_root == nullptr) { + LOG_ERROR(Service_FS, "The save root for the space_id={:02X} was invalid!", space); + return; + } + + for (const auto& type : save_root->GetSubdirectories()) { + if (type->GetName() == "save") { + for (const auto& save_id : type->GetSubdirectories()) { + for (const auto& user_id : save_id->GetSubdirectories()) { + const auto save_id_numeric = stoull_be(save_id->GetName()); + auto user_id_numeric = Common::HexStringToArray<0x10>(user_id->GetName()); + std::reverse(user_id_numeric.begin(), user_id_numeric.end()); + + if (save_id_numeric != 0) { + // System Save Data + info.emplace_back(SaveDataInfo{ + 0, + space, + FileSys::SaveDataType::SystemSaveData, + {}, + user_id_numeric, + save_id_numeric, + 0, + user_id->GetSize(), + {}, + {}, + }); + + continue; + } + + for (const auto& title_id : user_id->GetSubdirectories()) { + const auto device = + std::all_of(user_id_numeric.begin(), user_id_numeric.end(), + [](u8 val) { return val == 0; }); + info.emplace_back(SaveDataInfo{ + 0, + space, + device ? FileSys::SaveDataType::DeviceSaveData + : FileSys::SaveDataType::SaveData, + {}, + user_id_numeric, + save_id_numeric, + stoull_be(title_id->GetName()), + title_id->GetSize(), + {}, + {}, + }); + } + } + } + } else if (space == FileSys::SaveDataSpaceId::TemporaryStorage) { + // Temporary Storage + for (const auto& user_id : type->GetSubdirectories()) { + for (const auto& title_id : user_id->GetSubdirectories()) { + if (!title_id->GetFiles().empty() || + !title_id->GetSubdirectories().empty()) { + auto user_id_numeric = + Common::HexStringToArray<0x10>(user_id->GetName()); + std::reverse(user_id_numeric.begin(), user_id_numeric.end()); + + info.emplace_back(SaveDataInfo{ + 0, + space, + FileSys::SaveDataType::TemporaryStorage, + {}, + user_id_numeric, + stoull_be(type->GetName()), + stoull_be(title_id->GetName()), + title_id->GetSize(), + {}, + {}, + }); + } + } + } + } + } + } + + struct SaveDataInfo { + u64_le save_id_unknown; + FileSys::SaveDataSpaceId space; + FileSys::SaveDataType type; + INSERT_PADDING_BYTES(0x6); + std::array user_id; + u64_le save_id; + u64_le title_id; + u64_le save_image_size; + u16_le index; + FileSys::SaveDataRank rank; + INSERT_PADDING_BYTES(0x25); + }; + static_assert(sizeof(SaveDataInfo) == 0x60, "SaveDataInfo has incorrect size."); + + ProcessId process_id = 0; + std::shared_ptr save_data_controller; + std::vector info; + u64 next_entry_index = 0; +}; + +FSP_SRV::FSP_SRV(Core::System& system_) + : ServiceFramework{system_, "fsp-srv"}, fsc{system.GetFileSystemController()}, + content_provider{system.GetContentProvider()}, reporter{system.GetReporter()} { + // clang-format off + static const FunctionInfo functions[] = { + {0, nullptr, "OpenFileSystem"}, + {1, &FSP_SRV::SetCurrentProcess, "SetCurrentProcess"}, + {2, nullptr, "OpenDataFileSystemByCurrentProcess"}, + {7, &FSP_SRV::OpenFileSystemWithPatch, "OpenFileSystemWithPatch"}, + {8, nullptr, "OpenFileSystemWithId"}, + {9, nullptr, "OpenDataFileSystemByApplicationId"}, + {11, nullptr, "OpenBisFileSystem"}, + {12, nullptr, "OpenBisStorage"}, + {13, nullptr, "InvalidateBisCache"}, + {17, nullptr, "OpenHostFileSystem"}, + {18, &FSP_SRV::OpenSdCardFileSystem, "OpenSdCardFileSystem"}, + {19, nullptr, "FormatSdCardFileSystem"}, + {21, nullptr, "DeleteSaveDataFileSystem"}, + {22, &FSP_SRV::CreateSaveDataFileSystem, "CreateSaveDataFileSystem"}, + {23, &FSP_SRV::CreateSaveDataFileSystemBySystemSaveDataId, "CreateSaveDataFileSystemBySystemSaveDataId"}, + {24, nullptr, "RegisterSaveDataFileSystemAtomicDeletion"}, + {25, nullptr, "DeleteSaveDataFileSystemBySaveDataSpaceId"}, + {26, nullptr, "FormatSdCardDryRun"}, + {27, nullptr, "IsExFatSupported"}, + {28, nullptr, "DeleteSaveDataFileSystemBySaveDataAttribute"}, + {30, nullptr, "OpenGameCardStorage"}, + {31, nullptr, "OpenGameCardFileSystem"}, + {32, nullptr, "ExtendSaveDataFileSystem"}, + {33, nullptr, "DeleteCacheStorage"}, + {34, &FSP_SRV::GetCacheStorageSize, "GetCacheStorageSize"}, + {35, nullptr, "CreateSaveDataFileSystemByHashSalt"}, + {36, nullptr, "OpenHostFileSystemWithOption"}, + {51, &FSP_SRV::OpenSaveDataFileSystem, "OpenSaveDataFileSystem"}, + {52, &FSP_SRV::OpenSaveDataFileSystemBySystemSaveDataId, "OpenSaveDataFileSystemBySystemSaveDataId"}, + {53, &FSP_SRV::OpenReadOnlySaveDataFileSystem, "OpenReadOnlySaveDataFileSystem"}, + {57, nullptr, "ReadSaveDataFileSystemExtraDataBySaveDataSpaceId"}, + {58, nullptr, "ReadSaveDataFileSystemExtraData"}, + {59, nullptr, "WriteSaveDataFileSystemExtraData"}, + {60, nullptr, "OpenSaveDataInfoReader"}, + {61, &FSP_SRV::OpenSaveDataInfoReaderBySaveDataSpaceId, "OpenSaveDataInfoReaderBySaveDataSpaceId"}, + {62, &FSP_SRV::OpenSaveDataInfoReaderOnlyCacheStorage, "OpenSaveDataInfoReaderOnlyCacheStorage"}, + {64, nullptr, "OpenSaveDataInternalStorageFileSystem"}, + {65, nullptr, "UpdateSaveDataMacForDebug"}, + {66, nullptr, "WriteSaveDataFileSystemExtraData2"}, + {67, nullptr, "FindSaveDataWithFilter"}, + {68, nullptr, "OpenSaveDataInfoReaderBySaveDataFilter"}, + {69, nullptr, "ReadSaveDataFileSystemExtraDataBySaveDataAttribute"}, + {70, &FSP_SRV::WriteSaveDataFileSystemExtraDataBySaveDataAttribute, "WriteSaveDataFileSystemExtraDataBySaveDataAttribute"}, + {71, &FSP_SRV::ReadSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute, "ReadSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute"}, + {80, nullptr, "OpenSaveDataMetaFile"}, + {81, nullptr, "OpenSaveDataTransferManager"}, + {82, nullptr, "OpenSaveDataTransferManagerVersion2"}, + {83, nullptr, "OpenSaveDataTransferProhibiterForCloudBackUp"}, + {84, nullptr, "ListApplicationAccessibleSaveDataOwnerId"}, + {85, nullptr, "OpenSaveDataTransferManagerForSaveDataRepair"}, + {86, nullptr, "OpenSaveDataMover"}, + {87, nullptr, "OpenSaveDataTransferManagerForRepair"}, + {100, nullptr, "OpenImageDirectoryFileSystem"}, + {101, nullptr, "OpenBaseFileSystem"}, + {102, nullptr, "FormatBaseFileSystem"}, + {110, nullptr, "OpenContentStorageFileSystem"}, + {120, nullptr, "OpenCloudBackupWorkStorageFileSystem"}, + {130, nullptr, "OpenCustomStorageFileSystem"}, + {200, &FSP_SRV::OpenDataStorageByCurrentProcess, "OpenDataStorageByCurrentProcess"}, + {201, nullptr, "OpenDataStorageByProgramId"}, + {202, &FSP_SRV::OpenDataStorageByDataId, "OpenDataStorageByDataId"}, + {203, &FSP_SRV::OpenPatchDataStorageByCurrentProcess, "OpenPatchDataStorageByCurrentProcess"}, + {204, nullptr, "OpenDataFileSystemByProgramIndex"}, + {205, &FSP_SRV::OpenDataStorageWithProgramIndex, "OpenDataStorageWithProgramIndex"}, + {206, nullptr, "OpenDataStorageByPath"}, + {400, nullptr, "OpenDeviceOperator"}, + {500, nullptr, "OpenSdCardDetectionEventNotifier"}, + {501, nullptr, "OpenGameCardDetectionEventNotifier"}, + {510, nullptr, "OpenSystemDataUpdateEventNotifier"}, + {511, nullptr, "NotifySystemDataUpdateEvent"}, + {520, nullptr, "SimulateGameCardDetectionEvent"}, + {600, nullptr, "SetCurrentPosixTime"}, + {601, nullptr, "QuerySaveDataTotalSize"}, + {602, nullptr, "VerifySaveDataFileSystem"}, + {603, nullptr, "CorruptSaveDataFileSystem"}, + {604, nullptr, "CreatePaddingFile"}, + {605, nullptr, "DeleteAllPaddingFiles"}, + {606, nullptr, "GetRightsId"}, + {607, nullptr, "RegisterExternalKey"}, + {608, nullptr, "UnregisterAllExternalKey"}, + {609, nullptr, "GetRightsIdByPath"}, + {610, nullptr, "GetRightsIdAndKeyGenerationByPath"}, + {611, nullptr, "SetCurrentPosixTimeWithTimeDifference"}, + {612, nullptr, "GetFreeSpaceSizeForSaveData"}, + {613, nullptr, "VerifySaveDataFileSystemBySaveDataSpaceId"}, + {614, nullptr, "CorruptSaveDataFileSystemBySaveDataSpaceId"}, + {615, nullptr, "QuerySaveDataInternalStorageTotalSize"}, + {616, nullptr, "GetSaveDataCommitId"}, + {617, nullptr, "UnregisterExternalKey"}, + {620, nullptr, "SetSdCardEncryptionSeed"}, + {630, nullptr, "SetSdCardAccessibility"}, + {631, nullptr, "IsSdCardAccessible"}, + {640, nullptr, "IsSignedSystemPartitionOnSdCardValid"}, + {700, nullptr, "OpenAccessFailureResolver"}, + {701, nullptr, "GetAccessFailureDetectionEvent"}, + {702, nullptr, "IsAccessFailureDetected"}, + {710, nullptr, "ResolveAccessFailure"}, + {720, nullptr, "AbandonAccessFailure"}, + {800, nullptr, "GetAndClearFileSystemProxyErrorInfo"}, + {810, nullptr, "RegisterProgramIndexMapInfo"}, + {1000, nullptr, "SetBisRootForHost"}, + {1001, nullptr, "SetSaveDataSize"}, + {1002, nullptr, "SetSaveDataRootPath"}, + {1003, &FSP_SRV::DisableAutoSaveDataCreation, "DisableAutoSaveDataCreation"}, + {1004, &FSP_SRV::SetGlobalAccessLogMode, "SetGlobalAccessLogMode"}, + {1005, &FSP_SRV::GetGlobalAccessLogMode, "GetGlobalAccessLogMode"}, + {1006, &FSP_SRV::OutputAccessLogToSdCard, "OutputAccessLogToSdCard"}, + {1007, nullptr, "RegisterUpdatePartition"}, + {1008, nullptr, "OpenRegisteredUpdatePartition"}, + {1009, nullptr, "GetAndClearMemoryReportInfo"}, + {1010, nullptr, "SetDataStorageRedirectTarget"}, + {1011, &FSP_SRV::GetProgramIndexForAccessLog, "GetProgramIndexForAccessLog"}, + {1012, nullptr, "GetFsStackUsage"}, + {1013, nullptr, "UnsetSaveDataRootPath"}, + {1014, nullptr, "OutputMultiProgramTagAccessLog"}, + {1016, nullptr, "FlushAccessLogOnSdCard"}, + {1017, nullptr, "OutputApplicationInfoAccessLog"}, + {1018, nullptr, "SetDebugOption"}, + {1019, nullptr, "UnsetDebugOption"}, + {1100, nullptr, "OverrideSaveDataTransferTokenSignVerificationKey"}, + {1110, nullptr, "CorruptSaveDataFileSystemBySaveDataSpaceId2"}, + {1200, &FSP_SRV::OpenMultiCommitManager, "OpenMultiCommitManager"}, + {1300, nullptr, "OpenBisWiper"}, + }; + // clang-format on + RegisterHandlers(functions); + + if (Settings::values.enable_fs_access_log) { + access_log_mode = AccessLogMode::SdCard; + } +} + +FSP_SRV::~FSP_SRV() = default; + +void FSP_SRV::SetCurrentProcess(HLERequestContext& ctx) { + current_process_id = ctx.GetPID(); + + LOG_DEBUG(Service_FS, "called. current_process_id=0x{:016X}", current_process_id); + + const auto res = + fsc.OpenProcess(&program_id, &save_data_controller, &romfs_controller, current_process_id); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(res); +} + +void FSP_SRV::OpenFileSystemWithPatch(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + const auto type = rp.PopRaw(); + const auto title_id = rp.PopRaw(); + LOG_WARNING(Service_FS, "(STUBBED) called with type={}, title_id={:016X}", type, title_id); + + IPC::ResponseBuilder rb{ctx, 2, 0, 0}; + rb.Push(ResultUnknown); +} + +void FSP_SRV::OpenSdCardFileSystem(HLERequestContext& ctx) { + LOG_DEBUG(Service_FS, "called"); + + FileSys::VirtualDir sdmc_dir{}; + fsc.OpenSDMC(&sdmc_dir); + + auto filesystem = std::make_shared( + system, sdmc_dir, SizeGetter::FromStorageId(fsc, FileSys::StorageId::SdCard)); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(ResultSuccess); + rb.PushIpcInterface(std::move(filesystem)); +} + +void FSP_SRV::CreateSaveDataFileSystem(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + auto save_struct = rp.PopRaw(); + [[maybe_unused]] auto save_create_struct = rp.PopRaw>(); + u128 uid = rp.PopRaw(); + + LOG_DEBUG(Service_FS, "called save_struct = {}, uid = {:016X}{:016X}", save_struct.DebugInfo(), + uid[1], uid[0]); + + FileSys::VirtualDir save_data_dir{}; + save_data_controller->CreateSaveData(&save_data_dir, FileSys::SaveDataSpaceId::NandUser, + save_struct); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); +} + +void FSP_SRV::CreateSaveDataFileSystemBySystemSaveDataId(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + auto save_struct = rp.PopRaw(); + [[maybe_unused]] auto save_create_struct = rp.PopRaw>(); + + LOG_DEBUG(Service_FS, "called save_struct = {}", save_struct.DebugInfo()); + + FileSys::VirtualDir save_data_dir{}; + save_data_controller->CreateSaveData(&save_data_dir, FileSys::SaveDataSpaceId::NandSystem, + save_struct); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); +} + +void FSP_SRV::OpenSaveDataFileSystem(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + struct Parameters { + FileSys::SaveDataSpaceId space_id; + FileSys::SaveDataAttribute attribute; + }; + + const auto parameters = rp.PopRaw(); + + LOG_INFO(Service_FS, "called."); + + FileSys::VirtualDir dir{}; + auto result = + save_data_controller->OpenSaveData(&dir, parameters.space_id, parameters.attribute); + if (result != ResultSuccess) { + IPC::ResponseBuilder rb{ctx, 2, 0, 0}; + rb.Push(FileSys::ResultTargetNotFound); + return; + } + + FileSys::StorageId id{}; + switch (parameters.space_id) { + case FileSys::SaveDataSpaceId::NandUser: + id = FileSys::StorageId::NandUser; + break; + case FileSys::SaveDataSpaceId::SdCardSystem: + case FileSys::SaveDataSpaceId::SdCardUser: + id = FileSys::StorageId::SdCard; + break; + case FileSys::SaveDataSpaceId::NandSystem: + id = FileSys::StorageId::NandSystem; + break; + case FileSys::SaveDataSpaceId::TemporaryStorage: + case FileSys::SaveDataSpaceId::ProperSystem: + case FileSys::SaveDataSpaceId::SafeMode: + ASSERT(false); + } + + auto filesystem = + std::make_shared(system, std::move(dir), SizeGetter::FromStorageId(fsc, id)); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(ResultSuccess); + rb.PushIpcInterface(std::move(filesystem)); +} + +void FSP_SRV::OpenSaveDataFileSystemBySystemSaveDataId(HLERequestContext& ctx) { + LOG_WARNING(Service_FS, "(STUBBED) called, delegating to 51 OpenSaveDataFilesystem"); + OpenSaveDataFileSystem(ctx); +} + +void FSP_SRV::OpenReadOnlySaveDataFileSystem(HLERequestContext& ctx) { + LOG_WARNING(Service_FS, "(STUBBED) called, delegating to 51 OpenSaveDataFilesystem"); + OpenSaveDataFileSystem(ctx); +} + +void FSP_SRV::OpenSaveDataInfoReaderBySaveDataSpaceId(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto space = rp.PopRaw(); + LOG_INFO(Service_FS, "called, space={}", space); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(ResultSuccess); + rb.PushIpcInterface( + std::make_shared(system, save_data_controller, space)); +} + +void FSP_SRV::OpenSaveDataInfoReaderOnlyCacheStorage(HLERequestContext& ctx) { + LOG_WARNING(Service_FS, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(ResultSuccess); + rb.PushIpcInterface(system, save_data_controller, + FileSys::SaveDataSpaceId::TemporaryStorage); +} + +void FSP_SRV::WriteSaveDataFileSystemExtraDataBySaveDataAttribute(HLERequestContext& ctx) { + LOG_WARNING(Service_FS, "(STUBBED) called."); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); +} + +void FSP_SRV::ReadSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + struct Parameters { + FileSys::SaveDataSpaceId space_id; + FileSys::SaveDataAttribute attribute; + }; + + const auto parameters = rp.PopRaw(); + // Stub this to None for now, backend needs an impl to read/write the SaveDataExtraData + constexpr auto flags = static_cast(FileSys::SaveDataFlags::None); + + LOG_WARNING(Service_FS, + "(STUBBED) called, flags={}, space_id={}, attribute.title_id={:016X}\n" + "attribute.user_id={:016X}{:016X}, attribute.save_id={:016X}\n" + "attribute.type={}, attribute.rank={}, attribute.index={}", + flags, parameters.space_id, parameters.attribute.title_id, + parameters.attribute.user_id[1], parameters.attribute.user_id[0], + parameters.attribute.save_id, parameters.attribute.type, parameters.attribute.rank, + parameters.attribute.index); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(flags); +} + +void FSP_SRV::OpenDataStorageByCurrentProcess(HLERequestContext& ctx) { + LOG_DEBUG(Service_FS, "called"); + + if (!romfs) { + auto current_romfs = romfs_controller->OpenRomFSCurrentProcess(); + if (!current_romfs) { + // TODO (bunnei): Find the right error code to use here + LOG_CRITICAL(Service_FS, "no file system interface available!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultUnknown); + return; + } + + romfs = current_romfs; + } + + auto storage = std::make_shared(system, romfs); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(ResultSuccess); + rb.PushIpcInterface(std::move(storage)); +} + +void FSP_SRV::OpenDataStorageByDataId(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto storage_id = rp.PopRaw(); + const auto unknown = rp.PopRaw(); + const auto title_id = rp.PopRaw(); + + LOG_DEBUG(Service_FS, "called with storage_id={:02X}, unknown={:08X}, title_id={:016X}", + storage_id, unknown, title_id); + + auto data = romfs_controller->OpenRomFS(title_id, storage_id, FileSys::ContentRecordType::Data); + + if (!data) { + const auto archive = FileSys::SystemArchive::SynthesizeSystemArchive(title_id); + + if (archive != nullptr) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(ResultSuccess); + rb.PushIpcInterface(std::make_shared(system, archive)); + return; + } + + // TODO(DarkLordZach): Find the right error code to use here + LOG_ERROR(Service_FS, + "could not open data storage with title_id={:016X}, storage_id={:02X}", title_id, + storage_id); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultUnknown); + return; + } + + const FileSys::PatchManager pm{title_id, fsc, content_provider}; + + auto base = + romfs_controller->OpenBaseNca(title_id, storage_id, FileSys::ContentRecordType::Data); + auto storage = std::make_shared( + system, pm.PatchRomFS(base.get(), std::move(data), FileSys::ContentRecordType::Data)); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(ResultSuccess); + rb.PushIpcInterface(std::move(storage)); +} + +void FSP_SRV::OpenPatchDataStorageByCurrentProcess(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + const auto storage_id = rp.PopRaw(); + const auto title_id = rp.PopRaw(); + + LOG_DEBUG(Service_FS, "called with storage_id={:02X}, title_id={:016X}", storage_id, title_id); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(FileSys::ResultTargetNotFound); +} + +void FSP_SRV::OpenDataStorageWithProgramIndex(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + const auto program_index = rp.PopRaw(); + + LOG_DEBUG(Service_FS, "called, program_index={}", program_index); + + auto patched_romfs = romfs_controller->OpenPatchedRomFSWithProgramIndex( + program_id, program_index, FileSys::ContentRecordType::Program); + + if (!patched_romfs) { + // TODO: Find the right error code to use here + LOG_ERROR(Service_FS, "could not open storage with program_index={}", program_index); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultUnknown); + return; + } + + auto storage = std::make_shared(system, std::move(patched_romfs)); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(ResultSuccess); + rb.PushIpcInterface(std::move(storage)); +} + +void FSP_SRV::DisableAutoSaveDataCreation(HLERequestContext& ctx) { + LOG_DEBUG(Service_FS, "called"); + + save_data_controller->SetAutoCreate(false); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); +} + +void FSP_SRV::SetGlobalAccessLogMode(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + access_log_mode = rp.PopEnum(); + + LOG_DEBUG(Service_FS, "called, access_log_mode={}", access_log_mode); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); +} + +void FSP_SRV::GetGlobalAccessLogMode(HLERequestContext& ctx) { + LOG_DEBUG(Service_FS, "called"); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.PushEnum(access_log_mode); +} + +void FSP_SRV::OutputAccessLogToSdCard(HLERequestContext& ctx) { + const auto raw = ctx.ReadBufferCopy(); + auto log = Common::StringFromFixedZeroTerminatedBuffer( + reinterpret_cast(raw.data()), raw.size()); + + LOG_DEBUG(Service_FS, "called"); + + reporter.SaveFSAccessLog(log); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); +} + +void FSP_SRV::GetProgramIndexForAccessLog(HLERequestContext& ctx) { + LOG_DEBUG(Service_FS, "called"); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(ResultSuccess); + rb.PushEnum(AccessLogVersion::Latest); + rb.Push(access_log_program_index); +} + +void FSP_SRV::GetCacheStorageSize(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto index{rp.Pop()}; + + LOG_WARNING(Service_FS, "(STUBBED) called with index={}", index); + + IPC::ResponseBuilder rb{ctx, 6}; + rb.Push(ResultSuccess); + rb.Push(s64{0}); + rb.Push(s64{0}); +} + +class IMultiCommitManager final : public ServiceFramework { +public: + explicit IMultiCommitManager(Core::System& system_) + : ServiceFramework{system_, "IMultiCommitManager"} { + static const FunctionInfo functions[] = { + {1, &IMultiCommitManager::Add, "Add"}, + {2, &IMultiCommitManager::Commit, "Commit"}, + }; + RegisterHandlers(functions); + } + +private: + FileSys::VirtualFile backend; + + void Add(HLERequestContext& ctx) { + LOG_WARNING(Service_FS, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } + + void Commit(HLERequestContext& ctx) { + LOG_WARNING(Service_FS, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } +}; + +void FSP_SRV::OpenMultiCommitManager(HLERequestContext& ctx) { + LOG_DEBUG(Service_FS, "called"); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(ResultSuccess); + rb.PushIpcInterface(std::make_shared(system)); +} + +} // namespace Service::FileSystem diff --git a/src/core/hle/service/filesystem/fsp/fsp_srv.h b/src/core/hle/service/filesystem/fsp/fsp_srv.h new file mode 100755 index 000000000..26980af99 --- /dev/null +++ b/src/core/hle/service/filesystem/fsp/fsp_srv.h @@ -0,0 +1,78 @@ +// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include "core/hle/service/service.h" + +namespace Core { +class Reporter; +} + +namespace FileSys { +class ContentProvider; +class FileSystemBackend; +} // namespace FileSys + +namespace Service::FileSystem { + +class RomFsController; +class SaveDataController; + +enum class AccessLogVersion : u32 { + V7_0_0 = 2, + + Latest = V7_0_0, +}; + +enum class AccessLogMode : u32 { + None, + Log, + SdCard, +}; + +class FSP_SRV final : public ServiceFramework { +public: + explicit FSP_SRV(Core::System& system_); + ~FSP_SRV() override; + +private: + void SetCurrentProcess(HLERequestContext& ctx); + void OpenFileSystemWithPatch(HLERequestContext& ctx); + void OpenSdCardFileSystem(HLERequestContext& ctx); + void CreateSaveDataFileSystem(HLERequestContext& ctx); + void CreateSaveDataFileSystemBySystemSaveDataId(HLERequestContext& ctx); + void OpenSaveDataFileSystem(HLERequestContext& ctx); + void OpenSaveDataFileSystemBySystemSaveDataId(HLERequestContext& ctx); + void OpenReadOnlySaveDataFileSystem(HLERequestContext& ctx); + void OpenSaveDataInfoReaderBySaveDataSpaceId(HLERequestContext& ctx); + void OpenSaveDataInfoReaderOnlyCacheStorage(HLERequestContext& ctx); + void WriteSaveDataFileSystemExtraDataBySaveDataAttribute(HLERequestContext& ctx); + void ReadSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute(HLERequestContext& ctx); + void OpenDataStorageByCurrentProcess(HLERequestContext& ctx); + void OpenDataStorageByDataId(HLERequestContext& ctx); + void OpenPatchDataStorageByCurrentProcess(HLERequestContext& ctx); + void OpenDataStorageWithProgramIndex(HLERequestContext& ctx); + void DisableAutoSaveDataCreation(HLERequestContext& ctx); + void SetGlobalAccessLogMode(HLERequestContext& ctx); + void GetGlobalAccessLogMode(HLERequestContext& ctx); + void OutputAccessLogToSdCard(HLERequestContext& ctx); + void GetProgramIndexForAccessLog(HLERequestContext& ctx); + void OpenMultiCommitManager(HLERequestContext& ctx); + void GetCacheStorageSize(HLERequestContext& ctx); + + FileSystemController& fsc; + const FileSys::ContentProvider& content_provider; + const Core::Reporter& reporter; + + FileSys::VirtualFile romfs; + u64 current_process_id = 0; + u32 access_log_program_index = 0; + AccessLogMode access_log_mode = AccessLogMode::None; + u64 program_id = 0; + std::shared_ptr save_data_controller; + std::shared_ptr romfs_controller; +}; + +} // namespace Service::FileSystem diff --git a/src/core/hle/service/filesystem/fsp/fsp_util.h b/src/core/hle/service/filesystem/fsp/fsp_util.h new file mode 100755 index 000000000..253f866db --- /dev/null +++ b/src/core/hle/service/filesystem/fsp/fsp_util.h @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "core/hle/service/filesystem/filesystem.h" + +namespace Service::FileSystem { + +struct SizeGetter { + std::function get_free_size; + std::function get_total_size; + + static SizeGetter FromStorageId(const FileSystemController& fsc, FileSys::StorageId id) { + return { + [&fsc, id] { return fsc.GetFreeSpaceSize(id); }, + [&fsc, id] { return fsc.GetTotalSpaceSize(id); }, + }; + } +}; + +} // namespace Service::FileSystem diff --git a/src/core/hle/service/filesystem/romfs_controller.h b/src/core/hle/service/filesystem/romfs_controller.h index 9a478f71d..3c3ead344 100755 --- a/src/core/hle/service/filesystem/romfs_controller.h +++ b/src/core/hle/service/filesystem/romfs_controller.h @@ -5,7 +5,7 @@ #include "core/file_sys/nca_metadata.h" #include "core/file_sys/romfs_factory.h" -#include "core/file_sys/vfs_types.h" +#include "core/file_sys/vfs/vfs_types.h" namespace Service::FileSystem { diff --git a/src/core/hle/service/filesystem/save_data_controller.cpp b/src/core/hle/service/filesystem/save_data_controller.cpp index d19b3ea1e..03e45f7f9 100755 --- a/src/core/hle/service/filesystem/save_data_controller.cpp +++ b/src/core/hle/service/filesystem/save_data_controller.cpp @@ -44,7 +44,7 @@ Result SaveDataController::CreateSaveData(FileSys::VirtualDir* out_save_data, auto save_data = factory->Create(space, attribute); if (save_data == nullptr) { - return FileSys::ERROR_ENTITY_NOT_FOUND; + return FileSys::ResultTargetNotFound; } *out_save_data = save_data; @@ -56,7 +56,7 @@ Result SaveDataController::OpenSaveData(FileSys::VirtualDir* out_save_data, const FileSys::SaveDataAttribute& attribute) { auto save_data = factory->Open(space, attribute); if (save_data == nullptr) { - return FileSys::ERROR_ENTITY_NOT_FOUND; + return FileSys::ResultTargetNotFound; } *out_save_data = save_data; @@ -67,7 +67,7 @@ Result SaveDataController::OpenSaveDataSpace(FileSys::VirtualDir* out_save_data_ FileSys::SaveDataSpaceId space) { auto save_data_space = factory->GetSaveDataSpaceDirectory(space); if (save_data_space == nullptr) { - return FileSys::ERROR_ENTITY_NOT_FOUND; + return FileSys::ResultTargetNotFound; } *out_save_data_space = save_data_space; diff --git a/src/core/hle/service/filesystem/save_data_controller.h b/src/core/hle/service/filesystem/save_data_controller.h index 863188e4c..dc9d713df 100755 --- a/src/core/hle/service/filesystem/save_data_controller.h +++ b/src/core/hle/service/filesystem/save_data_controller.h @@ -5,7 +5,7 @@ #include "core/file_sys/nca_metadata.h" #include "core/file_sys/savedata_factory.h" -#include "core/file_sys/vfs_types.h" +#include "core/file_sys/vfs/vfs_types.h" namespace Service::FileSystem { diff --git a/src/core/hle/service/glue/time/manager.cpp b/src/core/hle/service/glue/time/manager.cpp index 6423e5089..b56762941 100755 --- a/src/core/hle/service/glue/time/manager.cpp +++ b/src/core/hle/service/glue/time/manager.cpp @@ -8,7 +8,7 @@ #include "common/settings.h" #include "common/time_zone.h" -#include "core/file_sys/vfs.h" +#include "core/file_sys/vfs/vfs.h" #include "core/hle/kernel/svc.h" #include "core/hle/service/glue/time/manager.h" #include "core/hle/service/glue/time/time_zone_binary.h" diff --git a/src/core/hle/service/glue/time/manager.h b/src/core/hle/service/glue/time/manager.h index a46ec6364..1de93f8f9 100755 --- a/src/core/hle/service/glue/time/manager.h +++ b/src/core/hle/service/glue/time/manager.h @@ -7,7 +7,7 @@ #include #include "common/common_types.h" -#include "core/file_sys/vfs_types.h" +#include "core/file_sys/vfs/vfs_types.h" #include "core/hle/service/glue/time/file_timestamp_worker.h" #include "core/hle/service/glue/time/standard_steady_clock_resource.h" #include "core/hle/service/glue/time/worker.h" diff --git a/src/core/hle/service/glue/time/time_zone_binary.cpp b/src/core/hle/service/glue/time/time_zone_binary.cpp index 67969aa3f..d33f784c0 100755 --- a/src/core/hle/service/glue/time/time_zone_binary.cpp +++ b/src/core/hle/service/glue/time/time_zone_binary.cpp @@ -7,7 +7,7 @@ #include "core/file_sys/registered_cache.h" #include "core/file_sys/romfs.h" #include "core/file_sys/system_archive/system_archive.h" -#include "core/file_sys/vfs.h" +#include "core/file_sys/vfs/vfs.h" #include "core/hle/service/filesystem/filesystem.h" #include "core/hle/service/glue/time/time_zone_binary.h" diff --git a/src/core/hle/service/hle_ipc.cpp b/src/core/hle/service/hle_ipc.cpp index e491dd260..50e1ed756 100755 --- a/src/core/hle/service/hle_ipc.cpp +++ b/src/core/hle/service/hle_ipc.cpp @@ -501,6 +501,22 @@ bool HLERequestContext::CanWriteBuffer(std::size_t buffer_index) const { } } +void HLERequestContext::AddMoveInterface(SessionRequestHandlerPtr s) { + ASSERT(Kernel::GetCurrentProcess(kernel).GetResourceLimit()->Reserve( + Kernel::LimitableResource::SessionCountMax, 1)); + + auto* session = Kernel::KSession::Create(kernel); + session->Initialize(nullptr, 0); + Kernel::KSession::Register(kernel, session); + + auto& server = manager.lock()->GetServerManager(); + auto next_manager = std::make_shared(kernel, server); + next_manager->SetSessionHandler(std::move(s)); + server.RegisterSession(&session->GetServerSession(), next_manager); + + AddMoveObject(&session->GetClientSession()); +} + std::string HLERequestContext::Description() const { if (!command_header) { return "No command header available"; diff --git a/src/core/hle/service/hle_ipc.h b/src/core/hle/service/hle_ipc.h index 8329d7265..c2e0e5e8c 100755 --- a/src/core/hle/service/hle_ipc.h +++ b/src/core/hle/service/hle_ipc.h @@ -339,6 +339,8 @@ public: outgoing_move_objects.emplace_back(object); } + void AddMoveInterface(SessionRequestHandlerPtr s); + void AddCopyObject(Kernel::KAutoObject* object) { outgoing_copy_objects.emplace_back(object); } diff --git a/src/core/hle/service/jit/jit.cpp b/src/core/hle/service/jit/jit.cpp index 771563d4d..eef02ca96 100755 --- a/src/core/hle/service/jit/jit.cpp +++ b/src/core/hle/service/jit/jit.cpp @@ -6,12 +6,12 @@ #include "core/core.h" #include "core/hle/kernel/k_transfer_memory.h" #include "core/hle/result.h" +#include "core/hle/service/cmif_serialization.h" #include "core/hle/service/ipc_helpers.h" #include "core/hle/service/jit/jit.h" #include "core/hle/service/jit/jit_code_memory.h" #include "core/hle/service/jit/jit_context.h" #include "core/hle/service/server_manager.h" -#include "core/hle/service/service.h" #include "core/memory.h" namespace Service::JIT { @@ -21,6 +21,9 @@ struct CodeRange { u64 size; }; +using Struct32 = std::array; +static_assert(sizeof(Struct32) == 32, "Struct32 has wrong size"); + class IJitEnvironment final : public ServiceFramework { public: explicit IJitEnvironment(Core::System& system_, @@ -29,12 +32,13 @@ public: : ServiceFramework{system_, "IJitEnvironment"}, process{std::move(process_)}, user_rx{std::move(user_rx_)}, user_ro{std::move(user_ro_)}, context{system_.ApplicationMemory()} { + // clang-format off static const FunctionInfo functions[] = { - {0, &IJitEnvironment::GenerateCode, "GenerateCode"}, - {1, &IJitEnvironment::Control, "Control"}, - {1000, &IJitEnvironment::LoadPlugin, "LoadPlugin"}, - {1001, &IJitEnvironment::GetCodeAddress, "GetCodeAddress"}, + {0, C<&IJitEnvironment::GenerateCode>, "GenerateCode"}, + {1, C<&IJitEnvironment::Control>, "Control"}, + {1000, C<&IJitEnvironment::LoadPlugin>, "LoadPlugin"}, + {1001, C<&IJitEnvironment::GetCodeAddress>, "GetCodeAddress"}, }; // clang-format on @@ -50,28 +54,10 @@ public: configuration.sys_ro_memory = configuration.user_ro_memory; } - void GenerateCode(HLERequestContext& ctx) { - LOG_DEBUG(Service_JIT, "called"); - - struct InputParameters { - u32 data_size; - u64 command; - std::array ranges; - Struct32 data; - }; - - struct OutputParameters { - s32 return_value; - std::array ranges; - }; - - IPC::RequestParser rp{ctx}; - const auto parameters{rp.PopRaw()}; - - // Optional input/output buffers - const auto input_buffer{ctx.CanReadBuffer() ? ctx.ReadBuffer() : std::span()}; - std::vector output_buffer(ctx.CanWriteBuffer() ? ctx.GetWriteBufferSize() : 0); - + Result GenerateCode(Out out_return_value, Out out_range0, + Out out_range1, OutBuffer out_buffer, + u32 data_size, u64 command, CodeRange range0, CodeRange range1, + Struct32 data, InBuffer buffer) { // Function call prototype: // void GenerateCode(s32* ret, CodeRange* c0_out, CodeRange* c1_out, JITConfiguration* cfg, // u64 cmd, u8* input_buf, size_t input_size, CodeRange* c0_in, @@ -83,66 +69,36 @@ public: // other arguments are used to transfer state between the game and the plugin. const VAddr ret_ptr{context.AddHeap(0u)}; - const VAddr c0_in_ptr{context.AddHeap(parameters.ranges[0])}; - const VAddr c1_in_ptr{context.AddHeap(parameters.ranges[1])}; - const VAddr c0_out_ptr{context.AddHeap(ClearSize(parameters.ranges[0]))}; - const VAddr c1_out_ptr{context.AddHeap(ClearSize(parameters.ranges[1]))}; + const VAddr c0_in_ptr{context.AddHeap(range0)}; + const VAddr c1_in_ptr{context.AddHeap(range1)}; + const VAddr c0_out_ptr{context.AddHeap(ClearSize(range0))}; + const VAddr c1_out_ptr{context.AddHeap(ClearSize(range1))}; - const VAddr input_ptr{context.AddHeap(input_buffer.data(), input_buffer.size())}; - const VAddr output_ptr{context.AddHeap(output_buffer.data(), output_buffer.size())}; - const VAddr data_ptr{context.AddHeap(parameters.data)}; + const VAddr input_ptr{context.AddHeap(buffer.data(), buffer.size())}; + const VAddr output_ptr{context.AddHeap(out_buffer.data(), out_buffer.size())}; + const VAddr data_ptr{context.AddHeap(data)}; const VAddr configuration_ptr{context.AddHeap(configuration)}; // The callback does not directly return a value, it only writes to the output pointer context.CallFunction(callbacks.GenerateCode, ret_ptr, c0_out_ptr, c1_out_ptr, - configuration_ptr, parameters.command, input_ptr, input_buffer.size(), - c0_in_ptr, c1_in_ptr, data_ptr, parameters.data_size, output_ptr, - output_buffer.size()); + configuration_ptr, command, input_ptr, buffer.size(), c0_in_ptr, + c1_in_ptr, data_ptr, data_size, output_ptr, out_buffer.size()); - const s32 return_value{context.GetHeap(ret_ptr)}; + *out_return_value = context.GetHeap(ret_ptr); + *out_range0 = context.GetHeap(c0_out_ptr); + *out_range1 = context.GetHeap(c1_out_ptr); + context.GetHeap(output_ptr, out_buffer.data(), out_buffer.size()); - if (return_value == 0) { - // The callback has written to the output executable code range, - // requiring an instruction cache invalidation - Core::InvalidateInstructionCacheRange(process.GetPointerUnsafe(), - configuration.user_rx_memory.offset, - configuration.user_rx_memory.size); - - // Write back to the IPC output buffer, if provided - if (ctx.CanWriteBuffer()) { - context.GetHeap(output_ptr, output_buffer.data(), output_buffer.size()); - ctx.WriteBuffer(output_buffer.data(), output_buffer.size()); - } - - const OutputParameters out{ - .return_value = return_value, - .ranges = - { - context.GetHeap(c0_out_ptr), - context.GetHeap(c1_out_ptr), - }, - }; - - IPC::ResponseBuilder rb{ctx, 8}; - rb.Push(ResultSuccess); - rb.PushRaw(out); - } else { + if (*out_return_value != 0) { LOG_WARNING(Service_JIT, "plugin GenerateCode callback failed"); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultUnknown); + R_THROW(ResultUnknown); } - }; - void Control(HLERequestContext& ctx) { - LOG_DEBUG(Service_JIT, "called"); - - IPC::RequestParser rp{ctx}; - const auto command{rp.PopRaw()}; - - // Optional input/output buffers - const auto input_buffer{ctx.CanReadBuffer() ? ctx.ReadBuffer() : std::span()}; - std::vector output_buffer(ctx.CanWriteBuffer() ? ctx.GetWriteBufferSize() : 0); + R_SUCCEED(); + } + Result Control(Out out_return_value, InBuffer in_data, + OutBuffer out_data, u64 command) { // Function call prototype: // u64 Control(s32* ret, JITConfiguration* cfg, u64 cmd, u8* input_buf, size_t input_size, // u8* output_buf, size_t output_size); @@ -152,53 +108,30 @@ public: const VAddr ret_ptr{context.AddHeap(0u)}; const VAddr configuration_ptr{context.AddHeap(configuration)}; - const VAddr input_ptr{context.AddHeap(input_buffer.data(), input_buffer.size())}; - const VAddr output_ptr{context.AddHeap(output_buffer.data(), output_buffer.size())}; + const VAddr input_ptr{context.AddHeap(in_data.data(), in_data.size())}; + const VAddr output_ptr{context.AddHeap(out_data.data(), out_data.size())}; const u64 wrapper_value{context.CallFunction(callbacks.Control, ret_ptr, configuration_ptr, - command, input_ptr, input_buffer.size(), - output_ptr, output_buffer.size())}; + command, input_ptr, in_data.size(), output_ptr, + out_data.size())}; - const s32 return_value{context.GetHeap(ret_ptr)}; + *out_return_value = context.GetHeap(ret_ptr); + context.GetHeap(output_ptr, out_data.data(), out_data.size()); - if (wrapper_value == 0 && return_value == 0) { - // Write back to the IPC output buffer, if provided - if (ctx.CanWriteBuffer()) { - context.GetHeap(output_ptr, output_buffer.data(), output_buffer.size()); - ctx.WriteBuffer(output_buffer.data(), output_buffer.size()); - } - - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(ResultSuccess); - rb.Push(return_value); - } else { - LOG_WARNING(Service_JIT, "plugin Control callback failed"); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultUnknown); + if (wrapper_value == 0 && *out_return_value == 0) { + R_SUCCEED(); } + + LOG_WARNING(Service_JIT, "plugin Control callback failed"); + R_THROW(ResultUnknown); } - void LoadPlugin(HLERequestContext& ctx) { - LOG_DEBUG(Service_JIT, "called"); - - IPC::RequestParser rp{ctx}; - const auto tmem_size{rp.PopRaw()}; - const auto tmem_handle{ctx.GetCopyHandle(0)}; - const auto nro_plugin{ctx.ReadBuffer(1)}; - - if (tmem_size == 0) { - LOG_ERROR(Service_JIT, "attempted to load plugin with empty transfer memory"); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultUnknown); - return; - } - - auto tmem{ctx.GetObjectFromHandle(tmem_handle)}; + Result LoadPlugin(u64 tmem_size, InCopyHandle& tmem, + InBuffer nrr, + InBuffer nro) { if (tmem.IsNull()) { - LOG_ERROR(Service_JIT, "attempted to load plugin with invalid transfer memory handle"); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultUnknown); - return; + LOG_ERROR(Service_JIT, "Invalid transfer memory handle!"); + R_THROW(ResultUnknown); } // Set up the configuration with the required TransferMemory address @@ -206,7 +139,7 @@ public: configuration.transfer_memory.size = tmem_size; // Gather up all the callbacks from the loaded plugin - auto symbols{Core::Symbols::GetSymbols(nro_plugin, true)}; + auto symbols{Core::Symbols::GetSymbols(nro, true)}; const auto GetSymbol{[&](const std::string& name) { return symbols[name].first; }}; callbacks.rtld_fini = GetSymbol("_fini"); @@ -223,16 +156,12 @@ public: if (callbacks.GetVersion == 0 || callbacks.Configure == 0 || callbacks.GenerateCode == 0 || callbacks.OnPrepared == 0) { LOG_ERROR(Service_JIT, "plugin does not implement all necessary functionality"); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultUnknown); - return; + R_THROW(ResultUnknown); } - if (!context.LoadNRO(nro_plugin)) { + if (!context.LoadNRO(nro)) { LOG_ERROR(Service_JIT, "failed to load plugin"); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultUnknown); - return; + R_THROW(ResultUnknown); } context.MapProcessMemory(configuration.sys_ro_memory.offset, @@ -252,9 +181,7 @@ public: const auto version{context.CallFunction(callbacks.GetVersion)}; if (version != 1) { LOG_ERROR(Service_JIT, "unknown plugin version {}", version); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultUnknown); - return; + R_THROW(ResultUnknown); } // Function prototype: @@ -280,22 +207,19 @@ public: const auto configuration_ptr{context.AddHeap(configuration)}; context.CallFunction(callbacks.OnPrepared, configuration_ptr); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + R_SUCCEED(); } - void GetCodeAddress(HLERequestContext& ctx) { + Result GetCodeAddress(Out rx_offset, Out ro_offset) { LOG_DEBUG(Service_JIT, "called"); - IPC::ResponseBuilder rb{ctx, 6}; - rb.Push(ResultSuccess); - rb.Push(configuration.user_rx_memory.offset); - rb.Push(configuration.user_ro_memory.offset); + *rx_offset = configuration.user_rx_memory.offset; + *ro_offset = configuration.user_ro_memory.offset; + + R_SUCCEED(); } private: - using Struct32 = std::array; - struct GuestCallbacks { VAddr rtld_fini; VAddr rtld_init; @@ -335,7 +259,7 @@ public: explicit JITU(Core::System& system_) : ServiceFramework{system_, "jit:u"} { // clang-format off static const FunctionInfo functions[] = { - {0, &JITU::CreateJitEnvironment, "CreateJitEnvironment"}, + {0, C<&JITU::CreateJitEnvironment>, "CreateJitEnvironment"}, }; // clang-format on @@ -343,76 +267,33 @@ public: } private: - void CreateJitEnvironment(HLERequestContext& ctx) { - LOG_DEBUG(Service_JIT, "called"); - - struct Parameters { - u64 rx_size; - u64 ro_size; - }; - - IPC::RequestParser rp{ctx}; - const auto parameters{rp.PopRaw()}; - const auto process_handle{ctx.GetCopyHandle(0)}; - const auto rx_mem_handle{ctx.GetCopyHandle(1)}; - const auto ro_mem_handle{ctx.GetCopyHandle(2)}; - - if (parameters.rx_size == 0 || parameters.ro_size == 0) { - LOG_ERROR(Service_JIT, "attempted to init with empty code regions"); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultUnknown); - return; - } - - auto process{ctx.GetObjectFromHandle(process_handle)}; + Result CreateJitEnvironment(Out> out_jit_environment, + u64 rx_size, u64 ro_size, InCopyHandle& process, + InCopyHandle& rx_mem, + InCopyHandle& ro_mem) { if (process.IsNull()) { - LOG_ERROR(Service_JIT, "process is null for handle=0x{:08X}", process_handle); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultUnknown); - return; + LOG_ERROR(Service_JIT, "process is null"); + R_THROW(ResultUnknown); } - - auto rx_mem{ctx.GetObjectFromHandle(rx_mem_handle)}; if (rx_mem.IsNull()) { - LOG_ERROR(Service_JIT, "rx_mem is null for handle=0x{:08X}", rx_mem_handle); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultUnknown); - return; + LOG_ERROR(Service_JIT, "rx_mem is null"); + R_THROW(ResultUnknown); } - - auto ro_mem{ctx.GetObjectFromHandle(ro_mem_handle)}; - if (ro_mem.IsNull()) { - LOG_ERROR(Service_JIT, "ro_mem is null for handle=0x{:08X}", ro_mem_handle); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultUnknown); - return; + if (rx_mem.IsNull()) { + LOG_ERROR(Service_JIT, "ro_mem is null"); + R_THROW(ResultUnknown); } CodeMemory rx, ro; - Result res; - res = rx.Initialize(*process, *rx_mem, parameters.rx_size, - Kernel::Svc::MemoryPermission::ReadExecute, generate_random); - if (R_FAILED(res)) { - LOG_ERROR(Service_JIT, "rx_mem could not be mapped for handle=0x{:08X}", rx_mem_handle); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(res); - return; - } + R_TRY(rx.Initialize(*process, *rx_mem, rx_size, Kernel::Svc::MemoryPermission::ReadExecute, + generate_random)); + R_TRY(ro.Initialize(*process, *ro_mem, ro_size, Kernel::Svc::MemoryPermission::Read, + generate_random)); - res = ro.Initialize(*process, *ro_mem, parameters.ro_size, - Kernel::Svc::MemoryPermission::Read, generate_random); - if (R_FAILED(res)) { - LOG_ERROR(Service_JIT, "ro_mem could not be mapped for handle=0x{:08X}", ro_mem_handle); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(res); - return; - } - - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - rb.Push(ResultSuccess); - rb.PushIpcInterface(system, std::move(process), std::move(rx), - std::move(ro)); + *out_jit_environment = std::make_shared(system, std::move(process), + std::move(rx), std::move(ro)); + R_SUCCEED(); } private: diff --git a/src/core/hle/service/nfc/nfc_interface.cpp b/src/core/hle/service/nfc/nfc_interface.cpp index 207ac4efe..3e2c7deab 100755 --- a/src/core/hle/service/nfc/nfc_interface.cpp +++ b/src/core/hle/service/nfc/nfc_interface.cpp @@ -301,7 +301,7 @@ Result NfcInterface::TranslateResultToServiceError(Result result) const { return result; } - if (result.module != ErrorModule::NFC) { + if (result.GetModule() != ErrorModule::NFC) { return result; } diff --git a/src/core/hle/service/ns/ns.cpp b/src/core/hle/service/ns/ns.cpp index f868f9f3e..20ea7aa43 100755 --- a/src/core/hle/service/ns/ns.cpp +++ b/src/core/hle/service/ns/ns.cpp @@ -6,7 +6,7 @@ #include "core/core.h" #include "core/file_sys/control_metadata.h" #include "core/file_sys/patch_manager.h" -#include "core/file_sys/vfs.h" +#include "core/file_sys/vfs/vfs.h" #include "core/hle/service/filesystem/filesystem.h" #include "core/hle/service/glue/glue_manager.h" #include "core/hle/service/ipc_helpers.h" diff --git a/src/core/hle/service/ro/ro.cpp b/src/core/hle/service/ro/ro.cpp index f0658bb5d..ae62c430e 100755 --- a/src/core/hle/service/ro/ro.cpp +++ b/src/core/hle/service/ro/ro.cpp @@ -6,13 +6,13 @@ #include "common/scope_exit.h" #include "core/hle/kernel/k_process.h" +#include "core/hle/service/cmif_serialization.h" #include "core/hle/service/ipc_helpers.h" #include "core/hle/service/ro/ro.h" #include "core/hle/service/ro/ro_nro_utils.h" #include "core/hle/service/ro/ro_results.h" #include "core/hle/service/ro/ro_types.h" #include "core/hle/service/server_manager.h" -#include "core/hle/service/service.h" namespace Service::RO { @@ -500,46 +500,65 @@ private: } }; -class RoInterface { +class RoInterface : public ServiceFramework { public: - explicit RoInterface(std::shared_ptr ro, NrrKind nrr_kind) - : m_ro(ro), m_context_id(InvalidContextId), m_nrr_kind(nrr_kind) {} + explicit RoInterface(Core::System& system_, const char* name_, std::shared_ptr ro, + NrrKind nrr_kind) + : ServiceFramework{system_, name_}, m_ro(ro), m_context_id(InvalidContextId), + m_nrr_kind(nrr_kind) { + + // clang-format off + static const FunctionInfo functions[] = { + {0, C<&RoInterface::MapManualLoadModuleMemory>, "MapManualLoadModuleMemory"}, + {1, C<&RoInterface::UnmapManualLoadModuleMemory>, "UnmapManualLoadModuleMemory"}, + {2, C<&RoInterface::RegisterModuleInfo>, "RegisterModuleInfo"}, + {3, C<&RoInterface::UnregisterModuleInfo>, "UnregisterModuleInfo"}, + {4, C<&RoInterface::RegisterProcessHandle>, "RegisterProcessHandle"}, + {10, C<&RoInterface::RegisterProcessModuleInfo>, "RegisterProcessModuleInfo"}, + }; + // clang-format on + + RegisterHandlers(functions); + } + ~RoInterface() { m_ro->UnregisterProcess(m_context_id); } - Result MapManualLoadModuleMemory(u64* out_load_address, u64 client_pid, u64 nro_address, - u64 nro_size, u64 bss_address, u64 bss_size) { - R_TRY(m_ro->ValidateProcess(m_context_id, client_pid)); - R_RETURN(m_ro->MapManualLoadModuleMemory(out_load_address, m_context_id, nro_address, + Result MapManualLoadModuleMemory(Out out_load_address, ClientProcessId client_pid, + u64 nro_address, u64 nro_size, u64 bss_address, u64 bss_size) { + R_TRY(m_ro->ValidateProcess(m_context_id, *client_pid)); + R_RETURN(m_ro->MapManualLoadModuleMemory(out_load_address.Get(), m_context_id, nro_address, nro_size, bss_address, bss_size)); } - Result UnmapManualLoadModuleMemory(u64 client_pid, u64 nro_address) { - R_TRY(m_ro->ValidateProcess(m_context_id, client_pid)); + Result UnmapManualLoadModuleMemory(ClientProcessId client_pid, u64 nro_address) { + R_TRY(m_ro->ValidateProcess(m_context_id, *client_pid)); R_RETURN(m_ro->UnmapManualLoadModuleMemory(m_context_id, nro_address)); } - Result RegisterModuleInfo(u64 client_pid, u64 nrr_address, u64 nrr_size) { - R_TRY(m_ro->ValidateProcess(m_context_id, client_pid)); + Result RegisterModuleInfo(ClientProcessId client_pid, u64 nrr_address, u64 nrr_size) { + R_TRY(m_ro->ValidateProcess(m_context_id, *client_pid)); R_RETURN( m_ro->RegisterModuleInfo(m_context_id, nrr_address, nrr_size, NrrKind::User, true)); } - Result UnregisterModuleInfo(u64 client_pid, u64 nrr_address) { - R_TRY(m_ro->ValidateProcess(m_context_id, client_pid)); + Result UnregisterModuleInfo(ClientProcessId client_pid, u64 nrr_address) { + R_TRY(m_ro->ValidateProcess(m_context_id, *client_pid)); R_RETURN(m_ro->UnregisterModuleInfo(m_context_id, nrr_address)); } - Result RegisterProcessHandle(u64 client_pid, Kernel::KProcess* process) { + Result RegisterProcessHandle(ClientProcessId client_pid, + InCopyHandle& process) { // Register the process. - R_RETURN(m_ro->RegisterProcess(std::addressof(m_context_id), process, client_pid)); + R_RETURN(m_ro->RegisterProcess(std::addressof(m_context_id), process.GetPointerUnsafe(), + *client_pid)); } - Result RegisterProcessModuleInfo(u64 client_pid, u64 nrr_address, u64 nrr_size, - Kernel::KProcess* process) { + Result RegisterProcessModuleInfo(ClientProcessId client_pid, u64 nrr_address, u64 nrr_size, + InCopyHandle& process) { // Validate the process. - R_TRY(m_ro->ValidateProcess(m_context_id, client_pid)); + R_TRY(m_ro->ValidateProcess(m_context_id, *client_pid)); // Register the module. R_RETURN(m_ro->RegisterModuleInfo(m_context_id, nrr_address, nrr_size, m_nrr_kind, @@ -552,137 +571,6 @@ private: NrrKind m_nrr_kind{}; }; -class IRoInterface : public ServiceFramework { -public: - explicit IRoInterface(Core::System& system_, const char* name_, std::shared_ptr ro, - NrrKind nrr_kind) - : ServiceFramework{system_, name_}, interface { - ro, nrr_kind - } { - // clang-format off - static const FunctionInfo functions[] = { - {0, &IRoInterface::MapManualLoadModuleMemory, "MapManualLoadModuleMemory"}, - {1, &IRoInterface::UnmapManualLoadModuleMemory, "UnmapManualLoadModuleMemory"}, - {2, &IRoInterface::RegisterModuleInfo, "RegisterModuleInfo"}, - {3, &IRoInterface::UnregisterModuleInfo, "UnregisterModuleInfo"}, - {4, &IRoInterface::RegisterProcessHandle, "RegisterProcessHandle"}, - {10, &IRoInterface::RegisterProcessModuleInfo, "RegisterProcessModuleInfo"}, - }; - // clang-format on - - RegisterHandlers(functions); - } - -private: - void MapManualLoadModuleMemory(HLERequestContext& ctx) { - LOG_DEBUG(Service_LDR, "(called)"); - - struct InputParameters { - u64 client_pid; - u64 nro_address; - u64 nro_size; - u64 bss_address; - u64 bss_size; - }; - - IPC::RequestParser rp{ctx}; - auto params = rp.PopRaw(); - - u64 load_address = 0; - auto result = interface.MapManualLoadModuleMemory(&load_address, ctx.GetPID(), - params.nro_address, params.nro_size, - params.bss_address, params.bss_size); - - IPC::ResponseBuilder rb{ctx, 4}; - rb.Push(result); - rb.Push(load_address); - } - - void UnmapManualLoadModuleMemory(HLERequestContext& ctx) { - LOG_DEBUG(Service_LDR, "(called)"); - - struct InputParameters { - u64 client_pid; - u64 nro_address; - }; - - IPC::RequestParser rp{ctx}; - auto params = rp.PopRaw(); - auto result = interface.UnmapManualLoadModuleMemory(ctx.GetPID(), params.nro_address); - - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(result); - } - - void RegisterModuleInfo(HLERequestContext& ctx) { - LOG_DEBUG(Service_LDR, "(called)"); - - struct InputParameters { - u64 client_pid; - u64 nrr_address; - u64 nrr_size; - }; - - IPC::RequestParser rp{ctx}; - auto params = rp.PopRaw(); - auto result = - interface.RegisterModuleInfo(ctx.GetPID(), params.nrr_address, params.nrr_size); - - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(result); - } - - void UnregisterModuleInfo(HLERequestContext& ctx) { - LOG_DEBUG(Service_LDR, "(called)"); - - struct InputParameters { - u64 client_pid; - u64 nrr_address; - }; - - IPC::RequestParser rp{ctx}; - auto params = rp.PopRaw(); - auto result = interface.UnregisterModuleInfo(ctx.GetPID(), params.nrr_address); - - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(result); - } - - void RegisterProcessHandle(HLERequestContext& ctx) { - LOG_DEBUG(Service_LDR, "(called)"); - - auto process = ctx.GetObjectFromHandle(ctx.GetCopyHandle(0)); - auto client_pid = ctx.GetPID(); - auto result = interface.RegisterProcessHandle(client_pid, process.GetPointerUnsafe()); - - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(result); - } - - void RegisterProcessModuleInfo(HLERequestContext& ctx) { - LOG_DEBUG(Service_LDR, "(called)"); - - struct InputParameters { - u64 client_pid; - u64 nrr_address; - u64 nrr_size; - }; - - IPC::RequestParser rp{ctx}; - auto params = rp.PopRaw(); - auto process = ctx.GetObjectFromHandle(ctx.GetCopyHandle(0)); - - auto client_pid = ctx.GetPID(); - auto result = interface.RegisterProcessModuleInfo( - client_pid, params.nrr_address, params.nrr_size, process.GetPointerUnsafe()); - - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(result); - } - - RoInterface interface; -}; - } // namespace void LoopProcess(Core::System& system) { @@ -691,11 +579,11 @@ void LoopProcess(Core::System& system) { auto ro = std::make_shared(); const auto RoInterfaceFactoryForUser = [&, ro] { - return std::make_shared(system, "ldr:ro", ro, NrrKind::User); + return std::make_shared(system, "ldr:ro", ro, NrrKind::User); }; const auto RoInterfaceFactoryForJitPlugin = [&, ro] { - return std::make_shared(system, "ro:1", ro, NrrKind::JitPlugin); + return std::make_shared(system, "ro:1", ro, NrrKind::JitPlugin); }; server_manager->RegisterNamedService("ldr:ro", std::move(RoInterfaceFactoryForUser)); diff --git a/src/core/hle/service/service.h b/src/core/hle/service/service.h index 335e58614..f5ee433f8 100755 --- a/src/core/hle/service/service.h +++ b/src/core/hle/service/service.h @@ -206,6 +206,22 @@ protected: RegisterHandlersBaseTipc(functions, n); } +protected: + template + void CmifReplyWrap(HLERequestContext& ctx); + + /** + * Wraps the template pointer-to-member function for use in a domain session. + */ + template + static constexpr HandlerFnP D = &Self::template CmifReplyWrap; + + /** + * Wraps the template pointer-to-member function for use in a non-domain session. + */ + template + static constexpr HandlerFnP C = &Self::template CmifReplyWrap; + private: /** * This function is used to allow invocation of pointers to handlers stored in the base class diff --git a/src/core/hle/service/set/system_settings_server.cpp b/src/core/hle/service/set/system_settings_server.cpp index f40a1c8f3..b527c39a9 100755 --- a/src/core/hle/service/set/system_settings_server.cpp +++ b/src/core/hle/service/set/system_settings_server.cpp @@ -67,13 +67,13 @@ Result GetFirmwareVersionImpl(FirmwareVersionFormat& out_firmware, Core::System& const auto ver_file = romfs->GetFile("file"); if (ver_file == nullptr) { return early_exit_failure("The system version archive didn't contain the file 'file'.", - FileSys::ERROR_INVALID_ARGUMENT); + FileSys::ResultInvalidArgument); } auto data = ver_file->ReadAllBytes(); if (data.size() != sizeof(FirmwareVersionFormat)) { return early_exit_failure("The system version file 'file' was not the correct size.", - FileSys::ERROR_OUT_OF_BOUNDS); + FileSys::ResultOutOfRange); } std::memcpy(&out_firmware, data.data(), sizeof(FirmwareVersionFormat)); diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h index af3fc8f9a..028830a62 100755 --- a/src/core/loader/loader.h +++ b/src/core/loader/loader.h @@ -14,7 +14,7 @@ #include "common/common_funcs.h" #include "common/common_types.h" #include "core/file_sys/control_metadata.h" -#include "core/file_sys/vfs.h" +#include "core/file_sys/vfs/vfs.h" namespace Core { class System; diff --git a/src/core/loader/nro.cpp b/src/core/loader/nro.cpp index 969c0fc9f..70ad079a3 100755 --- a/src/core/loader/nro.cpp +++ b/src/core/loader/nro.cpp @@ -12,7 +12,7 @@ #include "core/core.h" #include "core/file_sys/control_metadata.h" #include "core/file_sys/romfs_factory.h" -#include "core/file_sys/vfs_offset.h" +#include "core/file_sys/vfs/vfs_offset.h" #include "core/hle/kernel/code_set.h" #include "core/hle/kernel/k_page_table.h" #include "core/hle/kernel/k_process.h" diff --git a/src/core/reporter.cpp b/src/core/reporter.cpp index bfffbe2dc..a6ac1edf2 100755 --- a/src/core/reporter.cpp +++ b/src/core/reporter.cpp @@ -68,8 +68,8 @@ json GetReportCommonData(u64 title_id, Result result, const std::string& timesta auto out = json{ {"title_id", fmt::format("{:016X}", title_id)}, {"result_raw", fmt::format("{:08X}", result.raw)}, - {"result_module", fmt::format("{:08X}", static_cast(result.module.Value()))}, - {"result_description", fmt::format("{:08X}", result.description.Value())}, + {"result_module", fmt::format("{:08X}", static_cast(result.GetModule()))}, + {"result_description", fmt::format("{:08X}", result.GetDescription())}, {"timestamp", timestamp}, }; diff --git a/src/frontend_common/content_manager.h b/src/frontend_common/content_manager.h index 0b0fee73e..f3efe3465 100755 --- a/src/frontend_common/content_manager.h +++ b/src/frontend_common/content_manager.h @@ -9,7 +9,7 @@ #include "core/core.h" #include "core/file_sys/common_funcs.h" #include "core/file_sys/content_archive.h" -#include "core/file_sys/mode.h" +#include "core/file_sys/fs_filesystem.h" #include "core/file_sys/nca_metadata.h" #include "core/file_sys/patch_manager.h" #include "core/file_sys/registered_cache.h" @@ -47,14 +47,14 @@ inline bool RemoveDLC(const Service::FileSystem::FileSystemController& fs_contro /** * \brief Removes all DLC for a game - * \param system Raw pointer to the system instance + * \param system Reference to the system instance * \param program_id Program ID for the game that will have all of its DLC removed * \return Number of DLC removed */ -inline size_t RemoveAllDLC(Core::System* system, const u64 program_id) { +inline size_t RemoveAllDLC(Core::System& system, const u64 program_id) { size_t count{}; - const auto& fs_controller = system->GetFileSystemController(); - const auto dlc_entries = system->GetContentProvider().ListEntriesFilter( + const auto& fs_controller = system.GetFileSystemController(); + const auto dlc_entries = system.GetContentProvider().ListEntriesFilter( FileSys::TitleType::AOC, FileSys::ContentRecordType::Data); std::vector program_dlc_entries; @@ -124,15 +124,15 @@ inline bool RemoveMod(const Service::FileSystem::FileSystemController& fs_contro /** * \brief Installs an NSP - * \param system Raw pointer to the system instance - * \param vfs Raw pointer to the VfsFilesystem instance in Core::System + * \param system Reference to the system instance + * \param vfs Reference to the VfsFilesystem instance in Core::System * \param filename Path to the NSP file * \param callback Callback to report the progress of the installation. The first size_t * parameter is the total size of the virtual file and the second is the current progress. If you * return true to the callback, it will cancel the installation as soon as possible. * \return [InstallResult] representing how the installation finished */ -inline InstallResult InstallNSP(Core::System* system, FileSys::VfsFilesystem* vfs, +inline InstallResult InstallNSP(Core::System& system, FileSys::VfsFilesystem& vfs, const std::string& filename, const std::function& callback) { const auto copy = [callback](const FileSys::VirtualFile& src, const FileSys::VirtualFile& dest, @@ -159,7 +159,7 @@ inline InstallResult InstallNSP(Core::System* system, FileSys::VfsFilesystem* vf }; std::shared_ptr nsp; - FileSys::VirtualFile file = vfs->OpenFile(filename, FileSys::Mode::Read); + FileSys::VirtualFile file = vfs.OpenFile(filename, FileSys::OpenMode::Read); if (boost::to_lower_copy(file->GetName()).ends_with(std::string("nsp"))) { nsp = std::make_shared(file); if (nsp->IsExtractedType()) { @@ -173,7 +173,7 @@ inline InstallResult InstallNSP(Core::System* system, FileSys::VfsFilesystem* vf return InstallResult::Failure; } const auto res = - system->GetFileSystemController().GetUserNANDContents()->InstallEntry(*nsp, true, copy); + system.GetFileSystemController().GetUserNANDContents()->InstallEntry(*nsp, true, copy); switch (res) { case FileSys::InstallResult::Success: return InstallResult::Success; @@ -188,17 +188,17 @@ inline InstallResult InstallNSP(Core::System* system, FileSys::VfsFilesystem* vf /** * \brief Installs an NCA - * \param vfs Raw pointer to the VfsFilesystem instance in Core::System + * \param vfs Reference to the VfsFilesystem instance in Core::System * \param filename Path to the NCA file - * \param registered_cache Raw pointer to the registered cache that the NCA will be installed to + * \param registered_cache Reference to the registered cache that the NCA will be installed to * \param title_type Type of NCA package to install * \param callback Callback to report the progress of the installation. The first size_t * parameter is the total size of the virtual file and the second is the current progress. If you * return true to the callback, it will cancel the installation as soon as possible. * \return [InstallResult] representing how the installation finished */ -inline InstallResult InstallNCA(FileSys::VfsFilesystem* vfs, const std::string& filename, - FileSys::RegisteredCache* registered_cache, +inline InstallResult InstallNCA(FileSys::VfsFilesystem& vfs, const std::string& filename, + FileSys::RegisteredCache& registered_cache, const FileSys::TitleType title_type, const std::function& callback) { const auto copy = [callback](const FileSys::VirtualFile& src, const FileSys::VirtualFile& dest, @@ -224,7 +224,8 @@ inline InstallResult InstallNCA(FileSys::VfsFilesystem* vfs, const std::string& return true; }; - const auto nca = std::make_shared(vfs->OpenFile(filename, FileSys::Mode::Read)); + const auto nca = + std::make_shared(vfs.OpenFile(filename, FileSys::OpenMode::Read)); const auto id = nca->GetStatus(); // Game updates necessary are missing base RomFS @@ -233,7 +234,7 @@ inline InstallResult InstallNCA(FileSys::VfsFilesystem* vfs, const std::string& return InstallResult::Failure; } - const auto res = registered_cache->InstallEntry(*nca, title_type, true, copy); + const auto res = registered_cache.InstallEntry(*nca, title_type, true, copy); if (res == FileSys::InstallResult::Success) { return InstallResult::Success; } else if (res == FileSys::InstallResult::OverwriteExisting) { @@ -245,19 +246,19 @@ inline InstallResult InstallNCA(FileSys::VfsFilesystem* vfs, const std::string& /** * \brief Verifies the installed contents for a given ManualContentProvider - * \param system Raw pointer to the system instance - * \param provider Raw pointer to the content provider that's tracking indexed games + * \param system Reference to the system instance + * \param provider Reference to the content provider that's tracking indexed games * \param callback Callback to report the progress of the installation. The first size_t * parameter is the total size of the installed contents and the second is the current progress. If * you return true to the callback, it will cancel the installation as soon as possible. * \return A list of entries that failed to install. Returns an empty vector if successful. */ inline std::vector VerifyInstalledContents( - Core::System* system, FileSys::ManualContentProvider* provider, + Core::System& system, FileSys::ManualContentProvider& provider, const std::function& callback) { // Get content registries. - auto bis_contents = system->GetFileSystemController().GetSystemNANDContents(); - auto user_contents = system->GetFileSystemController().GetUserNANDContents(); + auto bis_contents = system.GetFileSystemController().GetSystemNANDContents(); + auto user_contents = system.GetFileSystemController().GetUserNANDContents(); std::vector content_providers; if (bis_contents) { @@ -309,11 +310,11 @@ inline std::vector VerifyInstalledContents( const auto title_id = nca.GetTitleId(); std::string title_name = "unknown"; - const auto control = provider->GetEntry(FileSys::GetBaseTitleID(title_id), - FileSys::ContentRecordType::Control); + const auto control = provider.GetEntry(FileSys::GetBaseTitleID(title_id), + FileSys::ContentRecordType::Control); if (control && control->GetStatus() == Loader::ResultStatus::Success) { - const FileSys::PatchManager pm{title_id, system->GetFileSystemController(), - *provider}; + const FileSys::PatchManager pm{title_id, system.GetFileSystemController(), + provider}; const auto [nacp, logo] = pm.ParseControlNCA(*control); if (nacp) { title_name = nacp->GetApplicationName(); @@ -335,7 +336,7 @@ inline std::vector VerifyInstalledContents( /** * \brief Verifies the contents of a given game - * \param system Raw pointer to the system instance + * \param system Reference to the system instance * \param game_path Patch to the game file * \param callback Callback to report the progress of the installation. The first size_t * parameter is the total size of the installed contents and the second is the current progress. If @@ -343,10 +344,10 @@ inline std::vector VerifyInstalledContents( * \return GameVerificationResult representing how the verification process finished */ inline GameVerificationResult VerifyGameContents( - Core::System* system, const std::string& game_path, + Core::System& system, const std::string& game_path, const std::function& callback) { const auto loader = Loader::GetLoader( - *system, system->GetFilesystem()->OpenFile(game_path, FileSys::Mode::Read)); + system, system.GetFilesystem()->OpenFile(game_path, FileSys::OpenMode::Read)); if (loader == nullptr) { return GameVerificationResult::NotImplemented; } @@ -368,4 +369,11 @@ inline GameVerificationResult VerifyGameContents( return GameVerificationResult::Success; } +/** + * Checks if the keys required for decrypting firmware and games are available + */ +inline bool AreKeysPresent() { + return !Core::Crypto::KeyManager::Instance().BaseDeriveNecessary(); +} + } // namespace ContentManager diff --git a/src/yuzu/applets/qt_error.cpp b/src/yuzu/applets/qt_error.cpp index 7907ee68b..85bbb114e 100755 --- a/src/yuzu/applets/qt_error.cpp +++ b/src/yuzu/applets/qt_error.cpp @@ -25,8 +25,8 @@ void QtErrorDisplay::ShowError(Result error, FinishedCallback finished) const { callback = std::move(finished); emit MainWindowDisplayError( tr("Error Code: %1-%2 (0x%3)") - .arg(static_cast(error.module.Value()) + 2000, 4, 10, QChar::fromLatin1('0')) - .arg(error.description, 4, 10, QChar::fromLatin1('0')) + .arg(static_cast(error.GetModule()) + 2000, 4, 10, QChar::fromLatin1('0')) + .arg(error.GetDescription(), 4, 10, QChar::fromLatin1('0')) .arg(error.raw, 8, 16, QChar::fromLatin1('0')), tr("An error has occurred.\nPlease try again or contact the developer of the software.")); } @@ -38,8 +38,8 @@ void QtErrorDisplay::ShowErrorWithTimestamp(Result error, std::chrono::seconds t const QDateTime date_time = QDateTime::fromSecsSinceEpoch(time.count()); emit MainWindowDisplayError( tr("Error Code: %1-%2 (0x%3)") - .arg(static_cast(error.module.Value()) + 2000, 4, 10, QChar::fromLatin1('0')) - .arg(error.description, 4, 10, QChar::fromLatin1('0')) + .arg(static_cast(error.GetModule()) + 2000, 4, 10, QChar::fromLatin1('0')) + .arg(error.GetDescription(), 4, 10, QChar::fromLatin1('0')) .arg(error.raw, 8, 16, QChar::fromLatin1('0')), tr("An error occurred on %1 at %2.\nPlease try again or contact the developer of the " "software.") @@ -53,8 +53,8 @@ void QtErrorDisplay::ShowCustomErrorText(Result error, std::string dialog_text, callback = std::move(finished); emit MainWindowDisplayError( tr("Error Code: %1-%2 (0x%3)") - .arg(static_cast(error.module.Value()) + 2000, 4, 10, QChar::fromLatin1('0')) - .arg(error.description, 4, 10, QChar::fromLatin1('0')) + .arg(static_cast(error.GetModule()) + 2000, 4, 10, QChar::fromLatin1('0')) + .arg(error.GetDescription(), 4, 10, QChar::fromLatin1('0')) .arg(error.raw, 8, 16, QChar::fromLatin1('0')), tr("An error has occurred.\n\n%1\n\n%2") .arg(QString::fromStdString(dialog_text)) diff --git a/src/yuzu/configuration/configure_per_game.h b/src/yuzu/configuration/configure_per_game.h index 84bc82ae8..201b6b5be 100755 --- a/src/yuzu/configuration/configure_per_game.h +++ b/src/yuzu/configuration/configure_per_game.h @@ -11,7 +11,7 @@ #include #include "configuration/shared_widget.h" -#include "core/file_sys/vfs_types.h" +#include "core/file_sys/vfs/vfs_types.h" #include "frontend_common/config.h" #include "vk_device_info.h" #include "yuzu/configuration/configuration_shared.h" diff --git a/src/yuzu/configuration/configure_per_game_addons.h b/src/yuzu/configuration/configure_per_game_addons.h index a6e422072..ccfe16c4d 100755 --- a/src/yuzu/configuration/configure_per_game_addons.h +++ b/src/yuzu/configuration/configure_per_game_addons.h @@ -8,7 +8,7 @@ #include -#include "core/file_sys/vfs_types.h" +#include "core/file_sys/vfs/vfs_types.h" namespace Core { class System; diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp index a525ffd3f..16c5c2fff 100755 --- a/src/yuzu/game_list_worker.cpp +++ b/src/yuzu/game_list_worker.cpp @@ -17,7 +17,7 @@ #include "core/file_sys/card_image.h" #include "core/file_sys/content_archive.h" #include "core/file_sys/control_metadata.h" -#include "core/file_sys/mode.h" +#include "core/file_sys/fs_filesystem.h" #include "core/file_sys/nca_metadata.h" #include "core/file_sys/patch_manager.h" #include "core/file_sys/registered_cache.h" @@ -347,7 +347,7 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa if (!is_dir && (HasSupportedFileExtension(physical_name) || IsExtractedNCAMain(physical_name))) { - const auto file = vfs->OpenFile(physical_name, FileSys::Mode::Read); + const auto file = vfs->OpenFile(physical_name, FileSys::OpenMode::Read); if (!file) { return true; } diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 2dc3e6886..62ece7369 100755 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -35,8 +35,8 @@ #include "configuration/configure_per_game.h" #include "configuration/configure_tas.h" #include "core/file_sys/romfs_factory.h" -#include "core/file_sys/vfs.h" -#include "core/file_sys/vfs_real.h" +#include "core/file_sys/vfs/vfs.h" +#include "core/file_sys/vfs/vfs_real.h" #include "core/frontend/applets/cabinet.h" #include "core/frontend/applets/controller.h" #include "core/frontend/applets/general_frontend.h" @@ -56,7 +56,7 @@ // These are wrappers to avoid the calls to CreateDirectory and CreateFile because of the Windows // defines. static FileSys::VirtualDir VfsFilesystemCreateDirectoryWrapper( - const FileSys::VirtualFilesystem& vfs, const std::string& path, FileSys::Mode mode) { + const FileSys::VirtualFilesystem& vfs, const std::string& path, FileSys::OpenMode mode) { return vfs->CreateDirectory(path, mode); } @@ -423,7 +423,7 @@ GMainWindow::GMainWindow(std::unique_ptr config_, bool has_broken_vulk RemoveCachedContents(); // Gen keys if necessary - OnReinitializeKeys(ReinitializeKeyBehavior::NoWarning); + OnCheckFirmwareDecryption(); game_list->LoadCompatibilityList(); game_list->PopulateAsync(UISettings::values.game_dirs); @@ -1574,8 +1574,6 @@ void GMainWindow::ConnectMenuEvents() { connect(multiplayer_state, &MultiplayerState::SaveConfig, this, &GMainWindow::OnSaveConfig); // Tools - connect_menu(ui->action_Rederive, std::bind(&GMainWindow::OnReinitializeKeys, this, - ReinitializeKeyBehavior::Warning)); connect_menu(ui->action_Load_Album, &GMainWindow::OnAlbum); connect_menu(ui->action_Load_Cabinet_Nickname_Owner, [this]() { OnCabinet(Service::NFP::CabinetMode::StartNicknameAndOwnerSettings); }); @@ -1882,7 +1880,7 @@ bool GMainWindow::SelectAndSetCurrentUser( void GMainWindow::ConfigureFilesystemProvider(const std::string& filepath) { // Ensure all NCAs are registered before launching the game - const auto file = vfs->OpenFile(filepath, FileSys::Mode::Read); + const auto file = vfs->OpenFile(filepath, FileSys::OpenMode::Read); if (!file) { return; } @@ -2276,7 +2274,7 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target open_target = tr("Save Data"); const auto nand_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir); auto vfs_nand_dir = - vfs->OpenDirectory(Common::FS::PathToUTF8String(nand_dir), FileSys::Mode::Read); + vfs->OpenDirectory(Common::FS::PathToUTF8String(nand_dir), FileSys::OpenMode::Read); if (has_user_save) { // User save data @@ -2501,7 +2499,7 @@ void GMainWindow::RemoveUpdateContent(u64 program_id, InstalledEntryType type) { } void GMainWindow::RemoveAddOnContent(u64 program_id, InstalledEntryType type) { - const size_t count = ContentManager::RemoveAllDLC(system.get(), program_id); + const size_t count = ContentManager::RemoveAllDLC(*system, program_id); if (count == 0) { QMessageBox::warning(this, GetGameListErrorRemoving(type), tr("There are no DLC installed for this title.")); @@ -2655,7 +2653,7 @@ void GMainWindow::RemoveCustomConfiguration(u64 program_id, const std::string& g void GMainWindow::RemoveCacheStorage(u64 program_id) { const auto nand_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir); auto vfs_nand_dir = - vfs->OpenDirectory(Common::FS::PathToUTF8String(nand_dir), FileSys::Mode::Read); + vfs->OpenDirectory(Common::FS::PathToUTF8String(nand_dir), FileSys::OpenMode::Read); const auto cache_storage_path = FileSys::SaveDataFactory::GetFullPath( {}, vfs_nand_dir, FileSys::SaveDataSpaceId::NandUser, FileSys::SaveDataType::CacheStorage, @@ -2675,7 +2673,8 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa "cancelled the operation.")); }; - const auto loader = Loader::GetLoader(*system, vfs->OpenFile(game_path, FileSys::Mode::Read)); + const auto loader = + Loader::GetLoader(*system, vfs->OpenFile(game_path, FileSys::OpenMode::Read)); if (loader == nullptr) { failed(); return; @@ -2719,7 +2718,7 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa const FileSys::PatchManager pm{title_id, system->GetFileSystemController(), installed}; auto romfs = pm.PatchRomFS(base_nca.get(), base_romfs, type, packed_update_raw, false); - const auto out = VfsFilesystemCreateDirectoryWrapper(vfs, path, FileSys::Mode::ReadWrite); + const auto out = VfsFilesystemCreateDirectoryWrapper(vfs, path, FileSys::OpenMode::ReadWrite); if (out == nullptr) { failed(); @@ -2798,8 +2797,7 @@ void GMainWindow::OnGameListVerifyIntegrity(const std::string& game_path) { return progress.wasCanceled(); }; - const auto result = - ContentManager::VerifyGameContents(system.get(), game_path, QtProgressCallback); + const auto result = ContentManager::VerifyGameContents(*system, game_path, QtProgressCallback); progress.close(); switch (result) { case ContentManager::GameVerificationResult::Success: @@ -3018,7 +3016,7 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga system->GetContentProvider()}; const auto control = pm.GetControlMetadata(); const auto loader = - Loader::GetLoader(*system, vfs->OpenFile(game_path, FileSys::Mode::Read)); + Loader::GetLoader(*system, vfs->OpenFile(game_path, FileSys::OpenMode::Read)); game_title = fmt::format("{:016X}", program_id); if (control.first != nullptr) { game_title = control.first->GetApplicationName(); @@ -3268,7 +3266,7 @@ void GMainWindow::OnMenuInstallToNAND() { return false; }; future = QtConcurrent::run([this, &file, progress_callback] { - return ContentManager::InstallNSP(system.get(), vfs.get(), file.toStdString(), + return ContentManager::InstallNSP(*system, *vfs, file.toStdString(), progress_callback); }); @@ -3371,7 +3369,7 @@ ContentManager::InstallResult GMainWindow::InstallNCA(const QString& filename) { } return false; }; - return ContentManager::InstallNCA(vfs.get(), filename.toStdString(), registered_cache, + return ContentManager::InstallNCA(*vfs, filename.toStdString(), *registered_cache, static_cast(index), progress_callback); } @@ -4121,7 +4119,7 @@ void GMainWindow::OnVerifyInstalledContents() { }; const std::vector result = - ContentManager::VerifyInstalledContents(system.get(), provider.get(), QtProgressCallback); + ContentManager::VerifyInstalledContents(*system, *provider, QtProgressCallback); progress.close(); if (result.empty()) { @@ -4551,122 +4549,20 @@ void GMainWindow::OnMouseActivity() { } } -void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) { - if (behavior == ReinitializeKeyBehavior::Warning) { - const auto res = QMessageBox::information( - this, tr("Confirm Key Rederivation"), - tr("You are about to force rederive all of your keys. \nIf you do not know what " - "this " - "means or what you are doing, \nthis is a potentially destructive action. " - "\nPlease " - "make sure this is what you want \nand optionally make backups.\n\nThis will " - "delete " - "your autogenerated key files and re-run the key derivation module."), - QMessageBox::StandardButtons{QMessageBox::Ok, QMessageBox::Cancel}); - - if (res == QMessageBox::Cancel) - return; - - const auto keys_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::KeysDir); - - Common::FS::RemoveFile(keys_dir / "prod.keys_autogenerated"); - Common::FS::RemoveFile(keys_dir / "console.keys_autogenerated"); - Common::FS::RemoveFile(keys_dir / "title.keys_autogenerated"); - } - - Core::Crypto::KeyManager& keys = Core::Crypto::KeyManager::Instance(); - bool all_keys_present{true}; - - if (keys.BaseDeriveNecessary()) { - Core::Crypto::PartitionDataManager pdm{vfs->OpenDirectory("", FileSys::Mode::Read)}; - - const auto function = [this, &keys, &pdm] { - keys.PopulateFromPartitionData(pdm); - - system->GetFileSystemController().CreateFactories(*vfs); - keys.DeriveETicket(pdm, system->GetContentProvider()); - }; - - QString errors; - if (!pdm.HasFuses()) { - errors += tr("Missing fuses"); - } - if (!pdm.HasBoot0()) { - errors += tr(" - Missing BOOT0"); - } - if (!pdm.HasPackage2()) { - errors += tr(" - Missing BCPKG2-1-Normal-Main"); - } - if (!pdm.HasProdInfo()) { - errors += tr(" - Missing PRODINFO"); - } - if (!errors.isEmpty()) { - all_keys_present = false; - QMessageBox::warning( - this, tr("Derivation Components Missing"), - tr("Encryption keys are missing. " - "
Please follow the yuzu " - "quickstart guide to get all your keys, firmware and " - "games.

(%1)") - .arg(errors)); - } - - QProgressDialog prog(this); - prog.setRange(0, 0); - prog.setLabelText(tr("Deriving keys...\nThis may take up to a minute depending \non your " - "system's performance.")); - prog.setWindowTitle(tr("Deriving Keys")); - - prog.show(); - - auto future = QtConcurrent::run(function); - while (!future.isFinished()) { - QCoreApplication::processEvents(); - } - - prog.close(); - } - +void GMainWindow::OnCheckFirmwareDecryption() { system->GetFileSystemController().CreateFactories(*vfs); - - if (all_keys_present && !this->CheckSystemArchiveDecryption()) { - LOG_WARNING(Frontend, "Mii model decryption failed"); + if (!ContentManager::AreKeysPresent()) { QMessageBox::warning( - this, tr("System Archive Decryption Failed"), - tr("Encryption keys failed to decrypt firmware. " + this, tr("Derivation Components Missing"), + tr("Encryption keys are missing. " "
Please follow the yuzu " "quickstart guide to get all your keys, firmware and " "games.")); } - SetFirmwareVersion(); - - if (behavior == ReinitializeKeyBehavior::Warning) { - game_list->PopulateAsync(UISettings::values.game_dirs); - } - UpdateMenuState(); } -bool GMainWindow::CheckSystemArchiveDecryption() { - constexpr u64 MiiModelId = 0x0100000000000802; - - auto bis_system = system->GetFileSystemController().GetSystemNANDContents(); - if (!bis_system) { - // Not having system BIS files is not an error. - return true; - } - - auto mii_nca = bis_system->GetEntry(MiiModelId, FileSys::ContentRecordType::Data); - if (!mii_nca) { - // Not having the Mii model is not an error. - return true; - } - - // Return whether we are able to decrypt the RomFS of the Mii model. - return mii_nca->GetRomFS().get() != nullptr; -} - bool GMainWindow::CheckFirmwarePresence() { constexpr u64 MiiEditId = static_cast(Service::AM::Applets::AppletProgramId::MiiEdit); diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 9a48c219d..106908013 100755 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -125,11 +125,6 @@ enum class EmulatedDirectoryTarget { SDMC, }; -enum class ReinitializeKeyBehavior { - NoWarning, - Warning, -}; - namespace VkDeviceInfo { class Record; } @@ -400,7 +395,7 @@ private slots: void OnMiiEdit(); void OnOpenControllerMenu(); void OnCaptureScreenshot(); - void OnReinitializeKeys(ReinitializeKeyBehavior behavior); + void OnCheckFirmwareDecryption(); void OnLanguageChanged(const QString& locale); void OnMouseActivity(); bool OnShutdownBegin(); @@ -441,7 +436,6 @@ private: void LoadTranslation(); void OpenPerGameConfiguration(u64 title_id, const std::string& file_name); bool CheckDarkMode(); - bool CheckSystemArchiveDecryption(); bool CheckFirmwarePresence(); void SetFirmwareVersion(); void ConfigureFilesystemProvider(const std::string& filepath); diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui index 77e9bd905..7a5d1e10d 100755 --- a/src/yuzu/main.ui +++ b/src/yuzu/main.ui @@ -224,11 +224,6 @@ &Stop - - - &Reinitialize keys... - - &Verify Installed Contents diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp index 648771f6c..e062d7d41 100755 --- a/src/yuzu_cmd/yuzu.cpp +++ b/src/yuzu_cmd/yuzu.cpp @@ -25,7 +25,7 @@ #include "core/cpu_manager.h" #include "core/crypto/key_manager.h" #include "core/file_sys/registered_cache.h" -#include "core/file_sys/vfs_real.h" +#include "core/file_sys/vfs/vfs_real.h" #include "core/hle/service/filesystem/filesystem.h" #include "core/loader/loader.h" #include "core/telemetry_session.h"