summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt17
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LongMessageDialogFragment.kt62
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt118
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/MemoryUtil.kt59
-rw-r--r--src/android/app/src/main/jni/native.cpp1
-rw-r--r--src/android/app/src/main/res/values/strings.xml24
-rw-r--r--src/common/fs/fs.cpp8
-rw-r--r--src/common/fs/fs_types.h2
-rw-r--r--src/core/file_sys/patch_manager.cpp9
-rw-r--r--src/core/file_sys/vfs_real.cpp46
-rw-r--r--src/core/file_sys/vfs_real.h11
11 files changed, 287 insertions, 70 deletions
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
index f0a6753a9..b1771b424 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
@@ -27,13 +27,13 @@ import android.view.MotionEvent
27import android.view.Surface 27import android.view.Surface
28import android.view.View 28import android.view.View
29import android.view.inputmethod.InputMethodManager 29import android.view.inputmethod.InputMethodManager
30import android.widget.Toast
30import androidx.activity.viewModels 31import androidx.activity.viewModels
31import androidx.appcompat.app.AppCompatActivity 32import androidx.appcompat.app.AppCompatActivity
32import androidx.core.view.WindowCompat 33import androidx.core.view.WindowCompat
33import androidx.core.view.WindowInsetsCompat 34import androidx.core.view.WindowInsetsCompat
34import androidx.core.view.WindowInsetsControllerCompat 35import androidx.core.view.WindowInsetsControllerCompat
35import androidx.navigation.fragment.NavHostFragment 36import androidx.navigation.fragment.NavHostFragment
36import kotlin.math.roundToInt
37import org.yuzu.yuzu_emu.NativeLibrary 37import org.yuzu.yuzu_emu.NativeLibrary
38import org.yuzu.yuzu_emu.R 38import org.yuzu.yuzu_emu.R
39import org.yuzu.yuzu_emu.databinding.ActivityEmulationBinding 39import org.yuzu.yuzu_emu.databinding.ActivityEmulationBinding
@@ -44,8 +44,10 @@ import org.yuzu.yuzu_emu.model.Game
44import org.yuzu.yuzu_emu.utils.ControllerMappingHelper 44import org.yuzu.yuzu_emu.utils.ControllerMappingHelper
45import org.yuzu.yuzu_emu.utils.ForegroundService 45import org.yuzu.yuzu_emu.utils.ForegroundService
46import org.yuzu.yuzu_emu.utils.InputHandler 46import org.yuzu.yuzu_emu.utils.InputHandler
47import org.yuzu.yuzu_emu.utils.MemoryUtil
47import org.yuzu.yuzu_emu.utils.NfcReader 48import org.yuzu.yuzu_emu.utils.NfcReader
48import org.yuzu.yuzu_emu.utils.ThemeHelper 49import org.yuzu.yuzu_emu.utils.ThemeHelper
50import kotlin.math.roundToInt
49 51
50class EmulationActivity : AppCompatActivity(), SensorEventListener { 52class EmulationActivity : AppCompatActivity(), SensorEventListener {
51 private lateinit var binding: ActivityEmulationBinding 53 private lateinit var binding: ActivityEmulationBinding
@@ -102,6 +104,19 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
102 inputHandler = InputHandler() 104 inputHandler = InputHandler()
103 inputHandler.initialize() 105 inputHandler.initialize()
104 106
107 val memoryUtil = MemoryUtil(this)
108 if (memoryUtil.isLessThan(8, MemoryUtil.Gb)) {
109 Toast.makeText(
110 this,
111 getString(
112 R.string.device_memory_inadequate,
113 memoryUtil.getDeviceRAM(),
114 "8 ${getString(R.string.memory_gigabyte)}"
115 ),
116 Toast.LENGTH_LONG
117 ).show()
118 }
119
105 // Start a foreground service to prevent the app from getting killed in the background 120 // Start a foreground service to prevent the app from getting killed in the background
106 val startIntent = Intent(this, ForegroundService::class.java) 121 val startIntent = Intent(this, ForegroundService::class.java)
107 startForegroundService(startIntent) 122 startForegroundService(startIntent)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LongMessageDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LongMessageDialogFragment.kt
new file mode 100644
index 000000000..b29b627e9
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LongMessageDialogFragment.kt
@@ -0,0 +1,62 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.fragments
5
6import android.app.Dialog
7import android.content.Intent
8import android.net.Uri
9import android.os.Bundle
10import androidx.fragment.app.DialogFragment
11import com.google.android.material.dialog.MaterialAlertDialogBuilder
12import org.yuzu.yuzu_emu.R
13
14class LongMessageDialogFragment : DialogFragment() {
15 override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
16 val titleId = requireArguments().getInt(TITLE)
17 val description = requireArguments().getString(DESCRIPTION)
18 val helpLinkId = requireArguments().getInt(HELP_LINK)
19
20 val dialog = MaterialAlertDialogBuilder(requireContext())
21 .setPositiveButton(R.string.close, null)
22 .setTitle(titleId)
23 .setMessage(description)
24
25 if (helpLinkId != 0) {
26 dialog.setNeutralButton(R.string.learn_more) { _, _ ->
27 openLink(getString(helpLinkId))
28 }
29 }
30
31 return dialog.show()
32 }
33
34 private fun openLink(link: String) {
35 val intent = Intent(Intent.ACTION_VIEW, Uri.parse(link))
36 startActivity(intent)
37 }
38
39 companion object {
40 const val TAG = "LongMessageDialogFragment"
41
42 private const val TITLE = "Title"
43 private const val DESCRIPTION = "Description"
44 private const val HELP_LINK = "Link"
45
46 fun newInstance(
47 titleId: Int,
48 description: String,
49 helpLinkId: Int = 0
50 ): LongMessageDialogFragment {
51 val dialog = LongMessageDialogFragment()
52 val bundle = Bundle()
53 bundle.apply {
54 putInt(TITLE, titleId)
55 putString(DESCRIPTION, description)
56 putInt(HELP_LINK, helpLinkId)
57 }
58 dialog.arguments = bundle
59 return dialog
60 }
61 }
62}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
index cc1d87f1b..3086cfad3 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
@@ -4,6 +4,7 @@
4package org.yuzu.yuzu_emu.ui.main 4package org.yuzu.yuzu_emu.ui.main
5 5
6import android.content.Intent 6import android.content.Intent
7import android.net.Uri
7import android.os.Bundle 8import android.os.Bundle
8import android.view.View 9import android.view.View
9import android.view.ViewGroup.MarginLayoutParams 10import android.view.ViewGroup.MarginLayoutParams
@@ -42,6 +43,7 @@ import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel
42import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity 43import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity
43import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile 44import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
44import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment 45import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment
46import org.yuzu.yuzu_emu.fragments.LongMessageDialogFragment
45import org.yuzu.yuzu_emu.fragments.MessageDialogFragment 47import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
46import org.yuzu.yuzu_emu.model.GamesViewModel 48import org.yuzu.yuzu_emu.model.GamesViewModel
47import org.yuzu.yuzu_emu.model.HomeViewModel 49import org.yuzu.yuzu_emu.model.HomeViewModel
@@ -481,62 +483,110 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
481 } 483 }
482 } 484 }
483 485
484 val installGameUpdate = 486 val installGameUpdate = registerForActivityResult(
485 registerForActivityResult(ActivityResultContracts.OpenDocument()) { 487 ActivityResultContracts.OpenMultipleDocuments()
486 if (it == null) { 488 ) { documents: List<Uri> ->
487 return@registerForActivityResult 489 if (documents.isNotEmpty()) {
488 }
489
490 IndeterminateProgressDialogFragment.newInstance( 490 IndeterminateProgressDialogFragment.newInstance(
491 this@MainActivity, 491 this@MainActivity,
492 R.string.install_game_content 492 R.string.install_game_content
493 ) { 493 ) {
494 val result = NativeLibrary.installFileToNand(it.toString()) 494 var installSuccess = 0
495 var installOverwrite = 0
496 var errorBaseGame = 0
497 var errorExtension = 0
498 var errorOther = 0
499 var errorTotal = 0
495 lifecycleScope.launch { 500 lifecycleScope.launch {
496 withContext(Dispatchers.Main) { 501 documents.forEach {
497 when (result) { 502 when (NativeLibrary.installFileToNand(it.toString())) {
498 NativeLibrary.InstallFileToNandResult.Success -> { 503 NativeLibrary.InstallFileToNandResult.Success -> {
499 Toast.makeText( 504 installSuccess += 1
500 applicationContext,
501 R.string.install_game_content_success,
502 Toast.LENGTH_SHORT
503 ).show()
504 } 505 }
505 506
506 NativeLibrary.InstallFileToNandResult.SuccessFileOverwritten -> { 507 NativeLibrary.InstallFileToNandResult.SuccessFileOverwritten -> {
507 Toast.makeText( 508 installOverwrite += 1
508 applicationContext,
509 R.string.install_game_content_success_overwrite,
510 Toast.LENGTH_SHORT
511 ).show()
512 } 509 }
513 510
514 NativeLibrary.InstallFileToNandResult.ErrorBaseGame -> { 511 NativeLibrary.InstallFileToNandResult.ErrorBaseGame -> {
515 MessageDialogFragment.newInstance( 512 errorBaseGame += 1
516 R.string.install_game_content_failure,
517 R.string.install_game_content_failure_base
518 ).show(supportFragmentManager, MessageDialogFragment.TAG)
519 } 513 }
520 514
521 NativeLibrary.InstallFileToNandResult.ErrorFilenameExtension -> { 515 NativeLibrary.InstallFileToNandResult.ErrorFilenameExtension -> {
522 MessageDialogFragment.newInstance( 516 errorExtension += 1
523 R.string.install_game_content_failure,
524 R.string.install_game_content_failure_file_extension,
525 R.string.install_game_content_help_link
526 ).show(supportFragmentManager, MessageDialogFragment.TAG)
527 } 517 }
528 518
529 else -> { 519 else -> {
530 MessageDialogFragment.newInstance( 520 errorOther += 1
531 R.string.install_game_content_failure,
532 R.string.install_game_content_failure_description,
533 R.string.install_game_content_help_link
534 ).show(supportFragmentManager, MessageDialogFragment.TAG)
535 } 521 }
536 } 522 }
537 } 523 }
524 withContext(Dispatchers.Main) {
525 val separator = System.getProperty("line.separator") ?: "\n"
526 val installResult = StringBuilder()
527 if (installSuccess > 0) {
528 installResult.append(
529 getString(
530 R.string.install_game_content_success_install,
531 installSuccess
532 )
533 )
534 installResult.append(separator)
535 }
536 if (installOverwrite > 0) {
537 installResult.append(
538 getString(
539 R.string.install_game_content_success_overwrite,
540 installOverwrite
541 )
542 )
543 installResult.append(separator)
544 }
545 errorTotal = errorBaseGame + errorExtension + errorOther
546 if (errorTotal > 0) {
547 installResult.append(separator)
548 installResult.append(
549 getString(
550 R.string.install_game_content_failed_count,
551 errorTotal
552 )
553 )
554 installResult.append(separator)
555 if (errorBaseGame > 0) {
556 installResult.append(separator)
557 installResult.append(
558 getString(R.string.install_game_content_failure_base)
559 )
560 installResult.append(separator)
561 }
562 if (errorExtension > 0) {
563 installResult.append(separator)
564 installResult.append(
565 getString(R.string.install_game_content_failure_file_extension)
566 )
567 installResult.append(separator)
568 }
569 if (errorOther > 0) {
570 installResult.append(
571 getString(R.string.install_game_content_failure_description)
572 )
573 installResult.append(separator)
574 }
575 LongMessageDialogFragment.newInstance(
576 R.string.install_game_content_failure,
577 installResult.toString().trim(),
578 R.string.install_game_content_help_link
579 ).show(supportFragmentManager, LongMessageDialogFragment.TAG)
580 } else {
581 LongMessageDialogFragment.newInstance(
582 R.string.install_game_content_success,
583 installResult.toString().trim()
584 ).show(supportFragmentManager, LongMessageDialogFragment.TAG)
585 }
586 }
538 } 587 }
539 return@newInstance result 588 return@newInstance installSuccess + installOverwrite + errorTotal
540 }.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG) 589 }.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
541 } 590 }
591 }
542} 592}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/MemoryUtil.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/MemoryUtil.kt
new file mode 100644
index 000000000..18e5fa0b0
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/MemoryUtil.kt
@@ -0,0 +1,59 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.utils
5
6import android.app.ActivityManager
7import android.content.Context
8import org.yuzu.yuzu_emu.R
9import java.util.Locale
10
11class MemoryUtil(val context: Context) {
12
13 private val Long.floatForm: String
14 get() = String.format(Locale.ROOT, "%.2f", this.toDouble())
15
16 private fun bytesToSizeUnit(size: Long): String {
17 return when {
18 size < Kb -> "${size.floatForm} ${context.getString(R.string.memory_byte)}"
19 size < Mb -> "${(size / Kb).floatForm} ${context.getString(R.string.memory_kilobyte)}"
20 size < Gb -> "${(size / Mb).floatForm} ${context.getString(R.string.memory_megabyte)}"
21 size < Tb -> "${(size / Gb).floatForm} ${context.getString(R.string.memory_gigabyte)}"
22 size < Pb -> "${(size / Tb).floatForm} ${context.getString(R.string.memory_terabyte)}"
23 size < Eb -> "${(size / Pb).floatForm} ${context.getString(R.string.memory_petabyte)}"
24 else -> "${(size / Eb).floatForm} ${context.getString(R.string.memory_exabyte)}"
25 }
26 }
27
28 private val totalMemory =
29 with(context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager) {
30 val memInfo = ActivityManager.MemoryInfo()
31 getMemoryInfo(memInfo)
32 memInfo.totalMem
33 }
34
35 fun isLessThan(minimum: Int, size: Long): Boolean {
36 return when (size) {
37 Kb -> totalMemory < Mb && totalMemory < minimum
38 Mb -> totalMemory < Gb && (totalMemory / Mb) < minimum
39 Gb -> totalMemory < Tb && (totalMemory / Gb) < minimum
40 Tb -> totalMemory < Pb && (totalMemory / Tb) < minimum
41 Pb -> totalMemory < Eb && (totalMemory / Pb) < minimum
42 Eb -> totalMemory / Eb < minimum
43 else -> totalMemory < Kb && totalMemory < minimum
44 }
45 }
46
47 fun getDeviceRAM(): String {
48 return bytesToSizeUnit(totalMemory)
49 }
50
51 companion object {
52 const val Kb: Long = 1024
53 const val Mb = Kb * 1024
54 const val Gb = Mb * 1024
55 const val Tb = Gb * 1024
56 const val Pb = Tb * 1024
57 const val Eb = Pb * 1024
58 }
59}
diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp
index 632aa50b3..f4fed0886 100644
--- a/src/android/app/src/main/jni/native.cpp
+++ b/src/android/app/src/main/jni/native.cpp
@@ -237,6 +237,7 @@ public:
237 m_software_keyboard = android_keyboard.get(); 237 m_software_keyboard = android_keyboard.get();
238 m_system.SetShuttingDown(false); 238 m_system.SetShuttingDown(false);
239 m_system.ApplySettings(); 239 m_system.ApplySettings();
240 Settings::LogSettings();
240 m_system.HIDCore().ReloadInputDevices(); 241 m_system.HIDCore().ReloadInputDevices();
241 m_system.SetAppletFrontendSet({ 242 m_system.SetAppletFrontendSet({
242 nullptr, // Amiibo Settings 243 nullptr, // Amiibo Settings
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml
index cc1d8c39d..21805d274 100644
--- a/src/android/app/src/main/res/values/strings.xml
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -104,12 +104,14 @@
104 <string name="share_log_missing">No log file found</string> 104 <string name="share_log_missing">No log file found</string>
105 <string name="install_game_content">Install game content</string> 105 <string name="install_game_content">Install game content</string>
106 <string name="install_game_content_description">Install game updates or DLC</string> 106 <string name="install_game_content_description">Install game updates or DLC</string>
107 <string name="install_game_content_failure">Error installing file to NAND</string> 107 <string name="install_game_content_failure">Error installing file(s) to NAND</string>
108 <string name="install_game_content_failure_description">Game content installation failed. Please ensure content is valid and that the prod.keys file is installed.</string> 108 <string name="install_game_content_failure_description">Please ensure content(s) are valid and that the prod.keys file is installed.</string>
109 <string name="install_game_content_failure_base">Installation of base games isn\'t permitted in order to avoid possible conflicts. Please select an update or DLC instead.</string> 109 <string name="install_game_content_failure_base">Installation of base games isn\'t permitted in order to avoid possible conflicts.</string>
110 <string name="install_game_content_failure_file_extension">The selected file type is not supported. Only NSP and XCI content is supported for this action. Please verify the game content is valid.</string> 110 <string name="install_game_content_failure_file_extension">Only NSP and XCI content is supported. Please verify the game content(s) are valid.</string>
111 <string name="install_game_content_success">Game content installed successfully</string> 111 <string name="install_game_content_failed_count">%1$d installation error(s)</string>
112 <string name="install_game_content_success_overwrite">Game content was overwritten successfully</string> 112 <string name="install_game_content_success">Game content(s) installed successfully</string>
113 <string name="install_game_content_success_install">%1$d installed successfully</string>
114 <string name="install_game_content_success_overwrite">%1$d overwritten successfully</string>
113 <string name="install_game_content_help_link">https://yuzu-emu.org/help/quickstart/#dumping-installed-updates</string> 115 <string name="install_game_content_help_link">https://yuzu-emu.org/help/quickstart/#dumping-installed-updates</string>
114 116
115 <!-- About screen strings --> 117 <!-- About screen strings -->
@@ -270,6 +272,7 @@
270 <string name="fatal_error">Fatal Error</string> 272 <string name="fatal_error">Fatal Error</string>
271 <string name="fatal_error_message">A fatal error occurred. Check the log for details.\nContinuing emulation may result in crashes and bugs.</string> 273 <string name="fatal_error_message">A fatal error occurred. Check the log for details.\nContinuing emulation may result in crashes and bugs.</string>
272 <string name="performance_warning">Turning off this setting will significantly reduce emulation performance! For the best experience, it is recommended that you leave this setting enabled.</string> 274 <string name="performance_warning">Turning off this setting will significantly reduce emulation performance! For the best experience, it is recommended that you leave this setting enabled.</string>
275 <string name="device_memory_inadequate">Device RAM: %1$s\nRecommended: %2$s</string>
273 276
274 <!-- Region Names --> 277 <!-- Region Names -->
275 <string name="region_japan">Japan</string> 278 <string name="region_japan">Japan</string>
@@ -300,6 +303,15 @@
300 <string name="language_traditional_chinese">Traditional Chinese (正體中文)</string> 303 <string name="language_traditional_chinese">Traditional Chinese (正體中文)</string>
301 <string name="language_brazilian_portuguese">Brazilian Portuguese (Português do Brasil)</string> 304 <string name="language_brazilian_portuguese">Brazilian Portuguese (Português do Brasil)</string>
302 305
306 <!-- Memory Sizes -->
307 <string name="memory_byte">Byte</string>
308 <string name="memory_kilobyte">KB</string>
309 <string name="memory_megabyte">MB</string>
310 <string name="memory_gigabyte">GB</string>
311 <string name="memory_terabyte">TB</string>
312 <string name="memory_petabyte">PB</string>
313 <string name="memory_exabyte">EB</string>
314
303 <!-- Renderer APIs --> 315 <!-- Renderer APIs -->
304 <string name="renderer_vulkan">Vulkan</string> 316 <string name="renderer_vulkan">Vulkan</string>
305 <string name="renderer_none">None</string> 317 <string name="renderer_none">None</string>
diff --git a/src/common/fs/fs.cpp b/src/common/fs/fs.cpp
index 6d66c926d..1baf6d746 100644
--- a/src/common/fs/fs.cpp
+++ b/src/common/fs/fs.cpp
@@ -436,7 +436,7 @@ void IterateDirEntries(const std::filesystem::path& path, const DirEntryCallable
436 436
437 if (True(filter & DirEntryFilter::File) && 437 if (True(filter & DirEntryFilter::File) &&
438 entry.status().type() == fs::file_type::regular) { 438 entry.status().type() == fs::file_type::regular) {
439 if (!callback(entry.path())) { 439 if (!callback(entry)) {
440 callback_error = true; 440 callback_error = true;
441 break; 441 break;
442 } 442 }
@@ -444,7 +444,7 @@ void IterateDirEntries(const std::filesystem::path& path, const DirEntryCallable
444 444
445 if (True(filter & DirEntryFilter::Directory) && 445 if (True(filter & DirEntryFilter::Directory) &&
446 entry.status().type() == fs::file_type::directory) { 446 entry.status().type() == fs::file_type::directory) {
447 if (!callback(entry.path())) { 447 if (!callback(entry)) {
448 callback_error = true; 448 callback_error = true;
449 break; 449 break;
450 } 450 }
@@ -493,7 +493,7 @@ void IterateDirEntriesRecursively(const std::filesystem::path& path,
493 493
494 if (True(filter & DirEntryFilter::File) && 494 if (True(filter & DirEntryFilter::File) &&
495 entry.status().type() == fs::file_type::regular) { 495 entry.status().type() == fs::file_type::regular) {
496 if (!callback(entry.path())) { 496 if (!callback(entry)) {
497 callback_error = true; 497 callback_error = true;
498 break; 498 break;
499 } 499 }
@@ -501,7 +501,7 @@ void IterateDirEntriesRecursively(const std::filesystem::path& path,
501 501
502 if (True(filter & DirEntryFilter::Directory) && 502 if (True(filter & DirEntryFilter::Directory) &&
503 entry.status().type() == fs::file_type::directory) { 503 entry.status().type() == fs::file_type::directory) {
504 if (!callback(entry.path())) { 504 if (!callback(entry)) {
505 callback_error = true; 505 callback_error = true;
506 break; 506 break;
507 } 507 }
diff --git a/src/common/fs/fs_types.h b/src/common/fs/fs_types.h
index 5a4090c19..900f85d24 100644
--- a/src/common/fs/fs_types.h
+++ b/src/common/fs/fs_types.h
@@ -66,6 +66,6 @@ DECLARE_ENUM_FLAG_OPERATORS(DirEntryFilter);
66 * @returns A boolean value. 66 * @returns A boolean value.
67 * Return true to indicate whether the callback is successful, false otherwise. 67 * Return true to indicate whether the callback is successful, false otherwise.
68 */ 68 */
69using DirEntryCallable = std::function<bool(const std::filesystem::path& path)>; 69using DirEntryCallable = std::function<bool(const std::filesystem::directory_entry& entry)>;
70 70
71} // namespace Common::FS 71} // namespace Common::FS
diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp
index 4e61d4335..d3286b352 100644
--- a/src/core/file_sys/patch_manager.cpp
+++ b/src/core/file_sys/patch_manager.cpp
@@ -153,7 +153,7 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
153 const auto sdmc_load_dir = fs_controller.GetSDMCModificationLoadRoot(title_id); 153 const auto sdmc_load_dir = fs_controller.GetSDMCModificationLoadRoot(title_id);
154 154
155 std::vector<VirtualDir> patch_dirs = {sdmc_load_dir}; 155 std::vector<VirtualDir> patch_dirs = {sdmc_load_dir};
156 if (load_dir != nullptr && load_dir->GetSize() > 0) { 156 if (load_dir != nullptr) {
157 const auto load_patch_dirs = load_dir->GetSubdirectories(); 157 const auto load_patch_dirs = load_dir->GetSubdirectories();
158 patch_dirs.insert(patch_dirs.end(), load_patch_dirs.begin(), load_patch_dirs.end()); 158 patch_dirs.insert(patch_dirs.end(), load_patch_dirs.begin(), load_patch_dirs.end());
159 } 159 }
@@ -354,8 +354,7 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t
354 const auto load_dir = fs_controller.GetModificationLoadRoot(title_id); 354 const auto load_dir = fs_controller.GetModificationLoadRoot(title_id);
355 const auto sdmc_load_dir = fs_controller.GetSDMCModificationLoadRoot(title_id); 355 const auto sdmc_load_dir = fs_controller.GetSDMCModificationLoadRoot(title_id);
356 if ((type != ContentRecordType::Program && type != ContentRecordType::Data) || 356 if ((type != ContentRecordType::Program && type != ContentRecordType::Data) ||
357 ((load_dir == nullptr || load_dir->GetSize() <= 0) && 357 (load_dir == nullptr && sdmc_load_dir == nullptr)) {
358 (sdmc_load_dir == nullptr || sdmc_load_dir->GetSize() <= 0))) {
359 return; 358 return;
360 } 359 }
361 360
@@ -496,7 +495,7 @@ PatchManager::PatchVersionNames PatchManager::GetPatchVersionNames(VirtualFile u
496 495
497 // General Mods (LayeredFS and IPS) 496 // General Mods (LayeredFS and IPS)
498 const auto mod_dir = fs_controller.GetModificationLoadRoot(title_id); 497 const auto mod_dir = fs_controller.GetModificationLoadRoot(title_id);
499 if (mod_dir != nullptr && mod_dir->GetSize() > 0) { 498 if (mod_dir != nullptr) {
500 for (const auto& mod : mod_dir->GetSubdirectories()) { 499 for (const auto& mod : mod_dir->GetSubdirectories()) {
501 std::string types; 500 std::string types;
502 501
@@ -540,7 +539,7 @@ PatchManager::PatchVersionNames PatchManager::GetPatchVersionNames(VirtualFile u
540 539
541 // SDMC mod directory (RomFS LayeredFS) 540 // SDMC mod directory (RomFS LayeredFS)
542 const auto sdmc_mod_dir = fs_controller.GetSDMCModificationLoadRoot(title_id); 541 const auto sdmc_mod_dir = fs_controller.GetSDMCModificationLoadRoot(title_id);
543 if (sdmc_mod_dir != nullptr && sdmc_mod_dir->GetSize() > 0) { 542 if (sdmc_mod_dir != nullptr) {
544 std::string types; 543 std::string types;
545 if (IsDirValidAndNonEmpty(FindSubdirectoryCaseless(sdmc_mod_dir, "exefs"))) { 544 if (IsDirValidAndNonEmpty(FindSubdirectoryCaseless(sdmc_mod_dir, "exefs"))) {
546 AppendCommaIfNotEmpty(types, "LayeredExeFS"); 545 AppendCommaIfNotEmpty(types, "LayeredExeFS");
diff --git a/src/core/file_sys/vfs_real.cpp b/src/core/file_sys/vfs_real.cpp
index 7a15d8438..fcc81a664 100644
--- a/src/core/file_sys/vfs_real.cpp
+++ b/src/core/file_sys/vfs_real.cpp
@@ -10,6 +10,7 @@
10#include "common/fs/fs.h" 10#include "common/fs/fs.h"
11#include "common/fs/path_util.h" 11#include "common/fs/path_util.h"
12#include "common/logging/log.h" 12#include "common/logging/log.h"
13#include "core/file_sys/vfs.h"
13#include "core/file_sys/vfs_real.h" 14#include "core/file_sys/vfs_real.h"
14 15
15// For FileTimeStampRaw 16// For FileTimeStampRaw
@@ -72,7 +73,8 @@ VfsEntryType RealVfsFilesystem::GetEntryType(std::string_view path_) const {
72 return VfsEntryType::File; 73 return VfsEntryType::File;
73} 74}
74 75
75VirtualFile RealVfsFilesystem::OpenFile(std::string_view path_, Mode perms) { 76VirtualFile RealVfsFilesystem::OpenFileFromEntry(std::string_view path_, std::optional<u64> size,
77 Mode perms) {
76 const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); 78 const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
77 79
78 if (auto it = cache.find(path); it != cache.end()) { 80 if (auto it = cache.find(path); it != cache.end()) {
@@ -81,20 +83,24 @@ VirtualFile RealVfsFilesystem::OpenFile(std::string_view path_, Mode perms) {
81 } 83 }
82 } 84 }
83 85
84 if (!FS::Exists(path) || !FS::IsFile(path)) { 86 if (!size && !FS::IsFile(path)) {
85 return nullptr; 87 return nullptr;
86 } 88 }
87 89
88 auto reference = std::make_unique<FileReference>(); 90 auto reference = std::make_unique<FileReference>();
89 this->InsertReferenceIntoList(*reference); 91 this->InsertReferenceIntoList(*reference);
90 92
91 auto file = 93 auto file = std::shared_ptr<RealVfsFile>(
92 std::shared_ptr<RealVfsFile>(new RealVfsFile(*this, std::move(reference), path, perms)); 94 new RealVfsFile(*this, std::move(reference), path, perms, size));
93 cache[path] = file; 95 cache[path] = file;
94 96
95 return file; 97 return file;
96} 98}
97 99
100VirtualFile RealVfsFilesystem::OpenFile(std::string_view path_, Mode perms) {
101 return OpenFileFromEntry(path_, {}, perms);
102}
103
98VirtualFile RealVfsFilesystem::CreateFile(std::string_view path_, Mode perms) { 104VirtualFile RealVfsFilesystem::CreateFile(std::string_view path_, Mode perms) {
99 const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); 105 const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
100 cache.erase(path); 106 cache.erase(path);
@@ -243,10 +249,10 @@ void RealVfsFilesystem::RemoveReferenceFromList(FileReference& reference) {
243} 249}
244 250
245RealVfsFile::RealVfsFile(RealVfsFilesystem& base_, std::unique_ptr<FileReference> reference_, 251RealVfsFile::RealVfsFile(RealVfsFilesystem& base_, std::unique_ptr<FileReference> reference_,
246 const std::string& path_, Mode perms_) 252 const std::string& path_, Mode perms_, std::optional<u64> size_)
247 : base(base_), reference(std::move(reference_)), path(path_), 253 : base(base_), reference(std::move(reference_)), path(path_),
248 parent_path(FS::GetParentPath(path_)), path_components(FS::SplitPathComponents(path_)), 254 parent_path(FS::GetParentPath(path_)), path_components(FS::SplitPathComponents(path_)),
249 perms(perms_) {} 255 size(size_), perms(perms_) {}
250 256
251RealVfsFile::~RealVfsFile() { 257RealVfsFile::~RealVfsFile() {
252 base.DropReference(std::move(reference)); 258 base.DropReference(std::move(reference));
@@ -257,11 +263,14 @@ std::string RealVfsFile::GetName() const {
257} 263}
258 264
259std::size_t RealVfsFile::GetSize() const { 265std::size_t RealVfsFile::GetSize() const {
260 base.RefreshReference(path, perms, *reference); 266 if (size) {
261 return reference->file ? reference->file->GetSize() : 0; 267 return *size;
268 }
269 return FS::GetSize(path);
262} 270}
263 271
264bool RealVfsFile::Resize(std::size_t new_size) { 272bool RealVfsFile::Resize(std::size_t new_size) {
273 size.reset();
265 base.RefreshReference(path, perms, *reference); 274 base.RefreshReference(path, perms, *reference);
266 return reference->file ? reference->file->SetSize(new_size) : false; 275 return reference->file ? reference->file->SetSize(new_size) : false;
267} 276}
@@ -287,6 +296,7 @@ std::size_t RealVfsFile::Read(u8* data, std::size_t length, std::size_t offset)
287} 296}
288 297
289std::size_t RealVfsFile::Write(const u8* data, std::size_t length, std::size_t offset) { 298std::size_t RealVfsFile::Write(const u8* data, std::size_t length, std::size_t offset) {
299 size.reset();
290 base.RefreshReference(path, perms, *reference); 300 base.RefreshReference(path, perms, *reference);
291 if (!reference->file || !reference->file->Seek(static_cast<s64>(offset))) { 301 if (!reference->file || !reference->file->Seek(static_cast<s64>(offset))) {
292 return 0; 302 return 0;
@@ -309,10 +319,11 @@ std::vector<VirtualFile> RealVfsDirectory::IterateEntries<RealVfsFile, VfsFile>(
309 319
310 std::vector<VirtualFile> out; 320 std::vector<VirtualFile> out;
311 321
312 const FS::DirEntryCallable callback = [this, &out](const std::filesystem::path& full_path) { 322 const FS::DirEntryCallable callback = [this,
313 const auto full_path_string = FS::PathToUTF8String(full_path); 323 &out](const std::filesystem::directory_entry& entry) {
324 const auto full_path_string = FS::PathToUTF8String(entry.path());
314 325
315 out.emplace_back(base.OpenFile(full_path_string, perms)); 326 out.emplace_back(base.OpenFileFromEntry(full_path_string, entry.file_size(), perms));
316 327
317 return true; 328 return true;
318 }; 329 };
@@ -330,8 +341,9 @@ std::vector<VirtualDir> RealVfsDirectory::IterateEntries<RealVfsDirectory, VfsDi
330 341
331 std::vector<VirtualDir> out; 342 std::vector<VirtualDir> out;
332 343
333 const FS::DirEntryCallable callback = [this, &out](const std::filesystem::path& full_path) { 344 const FS::DirEntryCallable callback = [this,
334 const auto full_path_string = FS::PathToUTF8String(full_path); 345 &out](const std::filesystem::directory_entry& entry) {
346 const auto full_path_string = FS::PathToUTF8String(entry.path());
335 347
336 out.emplace_back(base.OpenDirectory(full_path_string, perms)); 348 out.emplace_back(base.OpenDirectory(full_path_string, perms));
337 349
@@ -483,12 +495,10 @@ std::map<std::string, VfsEntryType, std::less<>> RealVfsDirectory::GetEntries()
483 495
484 std::map<std::string, VfsEntryType, std::less<>> out; 496 std::map<std::string, VfsEntryType, std::less<>> out;
485 497
486 const FS::DirEntryCallable callback = [&out](const std::filesystem::path& full_path) { 498 const FS::DirEntryCallable callback = [&out](const std::filesystem::directory_entry& entry) {
487 const auto filename = FS::PathToUTF8String(full_path.filename()); 499 const auto filename = FS::PathToUTF8String(entry.path().filename());
488
489 out.insert_or_assign(filename, 500 out.insert_or_assign(filename,
490 FS::IsDir(full_path) ? VfsEntryType::Directory : VfsEntryType::File); 501 entry.is_directory() ? VfsEntryType::Directory : VfsEntryType::File);
491
492 return true; 502 return true;
493 }; 503 };
494 504
diff --git a/src/core/file_sys/vfs_real.h b/src/core/file_sys/vfs_real.h
index d8c900e33..67f4c4422 100644
--- a/src/core/file_sys/vfs_real.h
+++ b/src/core/file_sys/vfs_real.h
@@ -4,6 +4,7 @@
4#pragma once 4#pragma once
5 5
6#include <map> 6#include <map>
7#include <optional>
7#include <string_view> 8#include <string_view>
8#include "common/intrusive_list.h" 9#include "common/intrusive_list.h"
9#include "core/file_sys/mode.h" 10#include "core/file_sys/mode.h"
@@ -20,6 +21,8 @@ struct FileReference : public Common::IntrusiveListBaseNode<FileReference> {
20}; 21};
21 22
22class RealVfsFile; 23class RealVfsFile;
24class RealVfsDirectory;
25
23class RealVfsFilesystem : public VfsFilesystem { 26class RealVfsFilesystem : public VfsFilesystem {
24public: 27public:
25 RealVfsFilesystem(); 28 RealVfsFilesystem();
@@ -56,6 +59,11 @@ private:
56private: 59private:
57 void InsertReferenceIntoList(FileReference& reference); 60 void InsertReferenceIntoList(FileReference& reference);
58 void RemoveReferenceFromList(FileReference& reference); 61 void RemoveReferenceFromList(FileReference& reference);
62
63private:
64 friend class RealVfsDirectory;
65 VirtualFile OpenFileFromEntry(std::string_view path, std::optional<u64> size,
66 Mode perms = Mode::Read);
59}; 67};
60 68
61// An implementation of VfsFile that represents a file on the user's computer. 69// An implementation of VfsFile that represents a file on the user's computer.
@@ -78,13 +86,14 @@ public:
78 86
79private: 87private:
80 RealVfsFile(RealVfsFilesystem& base, std::unique_ptr<FileReference> reference, 88 RealVfsFile(RealVfsFilesystem& base, std::unique_ptr<FileReference> reference,
81 const std::string& path, Mode perms = Mode::Read); 89 const std::string& path, Mode perms = Mode::Read, std::optional<u64> size = {});
82 90
83 RealVfsFilesystem& base; 91 RealVfsFilesystem& base;
84 std::unique_ptr<FileReference> reference; 92 std::unique_ptr<FileReference> reference;
85 std::string path; 93 std::string path;
86 std::string parent_path; 94 std::string parent_path;
87 std::vector<std::string> path_components; 95 std::vector<std::string> path_components;
96 std::optional<u64> size;
88 Mode perms; 97 Mode perms;
89}; 98};
90 99