diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt | 17 | ||||
| -rw-r--r-- | src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LongMessageDialogFragment.kt | 62 | ||||
| -rw-r--r-- | src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt | 118 | ||||
| -rw-r--r-- | src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/MemoryUtil.kt | 59 | ||||
| -rw-r--r-- | src/android/app/src/main/jni/native.cpp | 1 | ||||
| -rw-r--r-- | src/android/app/src/main/res/values/strings.xml | 24 | ||||
| -rw-r--r-- | src/common/fs/fs.cpp | 8 | ||||
| -rw-r--r-- | src/common/fs/fs_types.h | 2 | ||||
| -rw-r--r-- | src/core/file_sys/patch_manager.cpp | 9 | ||||
| -rw-r--r-- | src/core/file_sys/vfs_real.cpp | 46 | ||||
| -rw-r--r-- | src/core/file_sys/vfs_real.h | 11 |
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 | |||
| 27 | import android.view.Surface | 27 | import android.view.Surface |
| 28 | import android.view.View | 28 | import android.view.View |
| 29 | import android.view.inputmethod.InputMethodManager | 29 | import android.view.inputmethod.InputMethodManager |
| 30 | import android.widget.Toast | ||
| 30 | import androidx.activity.viewModels | 31 | import androidx.activity.viewModels |
| 31 | import androidx.appcompat.app.AppCompatActivity | 32 | import androidx.appcompat.app.AppCompatActivity |
| 32 | import androidx.core.view.WindowCompat | 33 | import androidx.core.view.WindowCompat |
| 33 | import androidx.core.view.WindowInsetsCompat | 34 | import androidx.core.view.WindowInsetsCompat |
| 34 | import androidx.core.view.WindowInsetsControllerCompat | 35 | import androidx.core.view.WindowInsetsControllerCompat |
| 35 | import androidx.navigation.fragment.NavHostFragment | 36 | import androidx.navigation.fragment.NavHostFragment |
| 36 | import kotlin.math.roundToInt | ||
| 37 | import org.yuzu.yuzu_emu.NativeLibrary | 37 | import org.yuzu.yuzu_emu.NativeLibrary |
| 38 | import org.yuzu.yuzu_emu.R | 38 | import org.yuzu.yuzu_emu.R |
| 39 | import org.yuzu.yuzu_emu.databinding.ActivityEmulationBinding | 39 | import org.yuzu.yuzu_emu.databinding.ActivityEmulationBinding |
| @@ -44,8 +44,10 @@ import org.yuzu.yuzu_emu.model.Game | |||
| 44 | import org.yuzu.yuzu_emu.utils.ControllerMappingHelper | 44 | import org.yuzu.yuzu_emu.utils.ControllerMappingHelper |
| 45 | import org.yuzu.yuzu_emu.utils.ForegroundService | 45 | import org.yuzu.yuzu_emu.utils.ForegroundService |
| 46 | import org.yuzu.yuzu_emu.utils.InputHandler | 46 | import org.yuzu.yuzu_emu.utils.InputHandler |
| 47 | import org.yuzu.yuzu_emu.utils.MemoryUtil | ||
| 47 | import org.yuzu.yuzu_emu.utils.NfcReader | 48 | import org.yuzu.yuzu_emu.utils.NfcReader |
| 48 | import org.yuzu.yuzu_emu.utils.ThemeHelper | 49 | import org.yuzu.yuzu_emu.utils.ThemeHelper |
| 50 | import kotlin.math.roundToInt | ||
| 49 | 51 | ||
| 50 | class EmulationActivity : AppCompatActivity(), SensorEventListener { | 52 | class 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 | |||
| 4 | package org.yuzu.yuzu_emu.fragments | ||
| 5 | |||
| 6 | import android.app.Dialog | ||
| 7 | import android.content.Intent | ||
| 8 | import android.net.Uri | ||
| 9 | import android.os.Bundle | ||
| 10 | import androidx.fragment.app.DialogFragment | ||
| 11 | import com.google.android.material.dialog.MaterialAlertDialogBuilder | ||
| 12 | import org.yuzu.yuzu_emu.R | ||
| 13 | |||
| 14 | class 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 @@ | |||
| 4 | package org.yuzu.yuzu_emu.ui.main | 4 | package org.yuzu.yuzu_emu.ui.main |
| 5 | 5 | ||
| 6 | import android.content.Intent | 6 | import android.content.Intent |
| 7 | import android.net.Uri | ||
| 7 | import android.os.Bundle | 8 | import android.os.Bundle |
| 8 | import android.view.View | 9 | import android.view.View |
| 9 | import android.view.ViewGroup.MarginLayoutParams | 10 | import android.view.ViewGroup.MarginLayoutParams |
| @@ -42,6 +43,7 @@ import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel | |||
| 42 | import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity | 43 | import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity |
| 43 | import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile | 44 | import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile |
| 44 | import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment | 45 | import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment |
| 46 | import org.yuzu.yuzu_emu.fragments.LongMessageDialogFragment | ||
| 45 | import org.yuzu.yuzu_emu.fragments.MessageDialogFragment | 47 | import org.yuzu.yuzu_emu.fragments.MessageDialogFragment |
| 46 | import org.yuzu.yuzu_emu.model.GamesViewModel | 48 | import org.yuzu.yuzu_emu.model.GamesViewModel |
| 47 | import org.yuzu.yuzu_emu.model.HomeViewModel | 49 | import 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 | |||
| 4 | package org.yuzu.yuzu_emu.utils | ||
| 5 | |||
| 6 | import android.app.ActivityManager | ||
| 7 | import android.content.Context | ||
| 8 | import org.yuzu.yuzu_emu.R | ||
| 9 | import java.util.Locale | ||
| 10 | |||
| 11 | class 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 | */ |
| 69 | using DirEntryCallable = std::function<bool(const std::filesystem::path& path)>; | 69 | using 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 | ||
| 75 | VirtualFile RealVfsFilesystem::OpenFile(std::string_view path_, Mode perms) { | 76 | VirtualFile 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 | ||
| 100 | VirtualFile RealVfsFilesystem::OpenFile(std::string_view path_, Mode perms) { | ||
| 101 | return OpenFileFromEntry(path_, {}, perms); | ||
| 102 | } | ||
| 103 | |||
| 98 | VirtualFile RealVfsFilesystem::CreateFile(std::string_view path_, Mode perms) { | 104 | VirtualFile 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 | ||
| 245 | RealVfsFile::RealVfsFile(RealVfsFilesystem& base_, std::unique_ptr<FileReference> reference_, | 251 | RealVfsFile::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 | ||
| 251 | RealVfsFile::~RealVfsFile() { | 257 | RealVfsFile::~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 | ||
| 259 | std::size_t RealVfsFile::GetSize() const { | 265 | std::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 | ||
| 264 | bool RealVfsFile::Resize(std::size_t new_size) { | 272 | bool 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 | ||
| 289 | std::size_t RealVfsFile::Write(const u8* data, std::size_t length, std::size_t offset) { | 298 | std::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 | ||
| 22 | class RealVfsFile; | 23 | class RealVfsFile; |
| 24 | class RealVfsDirectory; | ||
| 25 | |||
| 23 | class RealVfsFilesystem : public VfsFilesystem { | 26 | class RealVfsFilesystem : public VfsFilesystem { |
| 24 | public: | 27 | public: |
| 25 | RealVfsFilesystem(); | 28 | RealVfsFilesystem(); |
| @@ -56,6 +59,11 @@ private: | |||
| 56 | private: | 59 | private: |
| 57 | void InsertReferenceIntoList(FileReference& reference); | 60 | void InsertReferenceIntoList(FileReference& reference); |
| 58 | void RemoveReferenceFromList(FileReference& reference); | 61 | void RemoveReferenceFromList(FileReference& reference); |
| 62 | |||
| 63 | private: | ||
| 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 | ||
| 79 | private: | 87 | private: |
| 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 | ||