Browse Source

[android,addons] after a crash, launch button will wait for reloadGames to complete, and system will initialize after global config for proper firmware mounting (#3803)

This fixes two problems:

1. After a crash, it was possible to launch a game before external content gets mounted. Now the button will wait for it to complete.
2. Directory initialization was init system before init globalconfig, so after a crash firmware was not being remounted
(have you ever noticed fw version = N/A in device overlay, after fiddling with applets?)
(this had been fixed in 3755, which was not thoroughly tested by cocoon dev)

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3803
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Co-authored-by: xbzk <xbzk@eden-emu.dev>
Co-committed-by: xbzk <xbzk@eden-emu.dev>
pull/3806/head
xbzk 1 week ago
committed by crueter
parent
commit
81a344f3db
No known key found for this signature in database GPG Key ID: 425ACD2D4830EBC6
  1. 17
      src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GamePropertiesFragment.kt
  2. 65
      src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt
  3. 2
      src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt
  4. 99
      src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt

17
src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GamePropertiesFragment.kt

@ -36,6 +36,7 @@ import org.yuzu.yuzu_emu.databinding.FragmentGamePropertiesBinding
import org.yuzu.yuzu_emu.features.DocumentProvider
import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.features.settings.ui.SettingsSubscreen
import org.yuzu.yuzu_emu.model.AddonViewModel
import org.yuzu.yuzu_emu.model.DriverViewModel
import org.yuzu.yuzu_emu.model.GameProperty
import org.yuzu.yuzu_emu.model.GamesViewModel
@ -46,6 +47,7 @@ import org.yuzu.yuzu_emu.model.SubmenuProperty
import org.yuzu.yuzu_emu.model.TaskState
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
import org.yuzu.yuzu_emu.utils.FileUtil
import org.yuzu.yuzu_emu.utils.GameHelper
import org.yuzu.yuzu_emu.utils.GameIconUtils
import org.yuzu.yuzu_emu.utils.GpuDriverHelper
import org.yuzu.yuzu_emu.utils.MemoryUtil
@ -61,6 +63,7 @@ class GamePropertiesFragment : Fragment() {
private val homeViewModel: HomeViewModel by activityViewModels()
private val gamesViewModel: GamesViewModel by activityViewModels()
private val addonViewModel: AddonViewModel by activityViewModels()
private val driverViewModel: DriverViewModel by activityViewModels()
private val args by navArgs<GamePropertiesFragmentArgs>()
@ -118,6 +121,20 @@ class GamePropertiesFragment : Fragment() {
.show(childFragmentManager, LaunchGameDialogFragment.TAG)
}
if (GameHelper.cachedGameList.isEmpty()) {
binding.buttonStart.isEnabled = false
viewLifecycleOwner.lifecycleScope.launch {
withContext(Dispatchers.IO) {
GameHelper.restoreContentForGame(args.game)
}
if (_binding == null) {
return@launch
}
addonViewModel.onAddonsViewStarted(args.game)
binding.buttonStart.isEnabled = true
}
}
reloadList()
homeViewModel.openImportSaves.collect(

65
src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt

@ -100,42 +100,45 @@ class GamesViewModel : ViewModel() {
viewModelScope.launch {
withContext(Dispatchers.IO) {
if (firstStartup) {
// Retrieve list of cached games
val storedGames =
PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
.getStringSet(GameHelper.KEY_GAMES, emptySet())
if (storedGames!!.isNotEmpty()) {
val deserializedGames = mutableSetOf<Game>()
storedGames.forEach {
val game: Game
try {
game = Json.decodeFromString(it)
} catch (e: Exception) {
// We don't care about any errors related to parsing the game cache
return@forEach
}
val gameExists =
DocumentFile.fromSingleUri(
YuzuApplication.appContext,
Uri.parse(game.path)
)?.exists()
if (gameExists == true) {
deserializedGames.add(game)
try {
if (firstStartup) {
// Retrieve list of cached games
val storedGames =
PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
.getStringSet(GameHelper.KEY_GAMES, emptySet())
if (storedGames!!.isNotEmpty()) {
val deserializedGames = mutableSetOf<Game>()
storedGames.forEach {
val game: Game
try {
game = Json.decodeFromString(it)
} catch (e: Exception) {
// We don't care about any errors related to parsing the game cache
return@forEach
}
val gameExists =
DocumentFile.fromSingleUri(
YuzuApplication.appContext,
Uri.parse(game.path)
)?.exists()
if (gameExists == true) {
deserializedGames.add(game)
}
}
setGames(deserializedGames.toList())
}
setGames(deserializedGames.toList())
}
}
setGames(GameHelper.getGames())
reloading.set(false)
_isReloading.value = false
_shouldScrollAfterReload.value = true
setGames(GameHelper.getGames())
_shouldScrollAfterReload.value = true
if (directoriesChanged) {
setShouldSwapData(true)
if (directoriesChanged) {
setShouldSwapData(true)
}
} finally {
reloading.set(false)
_isReloading.value = false
}
}
}

2
src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt

@ -23,8 +23,8 @@ object DirectoryInitialization {
fun start() {
if (!areDirectoriesReady) {
initializeInternalStorage()
NativeLibrary.initializeSystem(false)
NativeConfig.initializeGlobalConfig()
NativeLibrary.initializeSystem(false)
NativeLibrary.reloadProfiles()
migrateSettings()
areDirectoriesReady = true

99
src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt

@ -8,9 +8,11 @@ package org.yuzu.yuzu_emu.utils
import android.content.SharedPreferences
import android.net.Uri
import android.provider.DocumentsContract
import androidx.preference.PreferenceManager
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import java.io.File
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.model.Game
@ -49,29 +51,8 @@ object GameHelper {
// Remove previous filesystem provider information so we can get up to date version info
NativeLibrary.clearFilesystemProvider()
// Scan External Content directories and register all NSP/XCI files
val externalContentDirs = NativeConfig.getExternalContentDirs()
val uniqueExternalContentDirs = linkedSetOf<String>()
externalContentDirs.forEach { externalDir ->
if (externalDir.isNotEmpty()) {
uniqueExternalContentDirs.add(externalDir)
}
}
val mountedContainerUris = mutableSetOf<String>()
for (externalDir in uniqueExternalContentDirs) {
if (externalDir.isNotEmpty()) {
val externalDirUri = externalDir.toUri()
if (FileUtil.isTreeUriValid(externalDirUri)) {
scanContentContainersRecursive(FileUtil.listFiles(externalDirUri), 3) {
val containerUri = it.uri.toString()
if (mountedContainerUris.add(containerUri)) {
NativeLibrary.addFileToFilesystemProvider(containerUri)
}
}
}
}
}
mountExternalContentDirectories(mountedContainerUris)
val badDirs = mutableListOf<Int>()
gameDirs.forEachIndexed { index: Int, gameDir: GameDir ->
@ -115,6 +96,15 @@ object GameHelper {
return games.toList()
}
fun restoreContentForGame(game: Game) {
NativeLibrary.reloadKeys()
val mountedContainerUris = mutableSetOf<String>()
mountExternalContentDirectories(mountedContainerUris)
mountGameFolderContent(Uri.parse(game.path), mountedContainerUris)
NativeLibrary.addFileToFilesystemProvider(game.path)
}
// File extensions considered as external content, buuut should
// be done better imo.
private val externalContentExtensions = setOf("nsp", "xci")
@ -181,6 +171,71 @@ object GameHelper {
}
}
private fun mountExternalContentDirectories(mountedContainerUris: MutableSet<String>) {
val uniqueExternalContentDirs = linkedSetOf<String>()
NativeConfig.getExternalContentDirs().forEach { externalDir ->
if (externalDir.isNotEmpty()) {
uniqueExternalContentDirs.add(externalDir)
}
}
for (externalDir in uniqueExternalContentDirs) {
val externalDirUri = externalDir.toUri()
if (FileUtil.isTreeUriValid(externalDirUri)) {
scanContentContainersRecursive(FileUtil.listFiles(externalDirUri), 3) {
val containerUri = it.uri.toString()
if (mountedContainerUris.add(containerUri)) {
NativeLibrary.addFileToFilesystemProvider(containerUri)
}
}
}
}
}
private fun mountGameFolderContent(gameUri: Uri, mountedContainerUris: MutableSet<String>) {
if (gameUri.scheme == "content") {
val parentUri = getParentDocumentUri(gameUri) ?: return
scanContentContainersRecursive(FileUtil.listFiles(parentUri), 1) {
val containerUri = it.uri.toString()
if (mountedContainerUris.add(containerUri)) {
NativeLibrary.addGameFolderFileToFilesystemProvider(containerUri)
}
}
return
}
val gameFile = File(gameUri.path ?: gameUri.toString())
val parentDir = gameFile.parentFile ?: return
parentDir.listFiles()?.forEach { sibling ->
if (!sibling.isFile) {
return@forEach
}
val extension = sibling.extension.lowercase()
if (externalContentExtensions.contains(extension)) {
val containerUri = Uri.fromFile(sibling).toString()
if (mountedContainerUris.add(containerUri)) {
NativeLibrary.addGameFolderFileToFilesystemProvider(containerUri)
}
}
}
}
private fun getParentDocumentUri(uri: Uri): Uri? {
return try {
val documentId = DocumentsContract.getDocumentId(uri)
val separatorIndex = documentId.lastIndexOf('/')
if (separatorIndex == -1) {
null
} else {
val parentDocumentId = documentId.substring(0, separatorIndex)
DocumentsContract.buildDocumentUriUsingTree(uri, parentDocumentId)
}
} catch (_: Exception) {
null
}
}
fun getGame(
uri: Uri,
addedToLibrary: Boolean,

Loading…
Cancel
Save