aboutsummaryrefslogtreecommitdiff
path: root/src/android
diff options
context:
space:
mode:
authorRibbit <ribbit@eden-emu.dev>2025-10-27 04:52:08 +0100
committercrueter <crueter@eden-emu.dev>2025-10-27 04:52:08 +0100
commitdc907616a954e66c0b770fcb33c85ea0759fa824 (patch)
tree7f5f90559642e2d144760f8e487350769202d56f /src/android
parent0be29d29475dcb428f98d492c461f76014214a88 (diff)
[Android] Fix Amiibo bug (#2847)
Co-authored-by: Ribbit <ribbit@placeholder.com> Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/2847 Reviewed-by: crueter <crueter@eden-emu.dev> Co-authored-by: Ribbit <ribbit@eden-emu.dev> Co-committed-by: Ribbit <ribbit@eden-emu.dev>
Diffstat (limited to 'src/android')
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt92
1 files changed, 73 insertions, 19 deletions
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 5535e45711..4ee68df3ff 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
@@ -56,6 +56,12 @@ import androidx.window.layout.WindowLayoutInfo
import com.google.android.material.color.MaterialColors
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.textview.MaterialTextView
+import kotlinx.coroutines.CancellationException
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.ensureActive
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
import org.yuzu.yuzu_emu.HomeNavigationDirections
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R
@@ -85,12 +91,11 @@ import org.yuzu.yuzu_emu.utils.ViewUtils
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
import org.yuzu.yuzu_emu.utils.collect
import org.yuzu.yuzu_emu.utils.CustomSettingsHandler
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
-import kotlinx.coroutines.Dispatchers
+import java.io.ByteArrayOutputStream
+import java.io.File
+import kotlin.coroutines.coroutineContext
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
-import java.io.File
class EmulationFragment : Fragment(), SurfaceHolder.Callback {
private lateinit var emulationState: EmulationState
@@ -125,6 +130,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
private var perfStatsRunnable: Runnable? = null
private var socRunnable: Runnable? = null
private var isAmiiboPickerOpen = false
+ private var amiiboLoadJob: Job? = null
private val loadAmiiboLauncher =
registerForActivityResult(ActivityResultContracts.OpenDocument()) { uri ->
@@ -136,26 +142,46 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
return@registerForActivityResult
}
- val data = try {
- requireContext().contentResolver.openInputStream(uri)?.use { it.readBytes() }
- } catch (e: Exception) {
- Log.error("[EmulationFragment] Failed to read amiibo: ${e.message}")
- showAmiiboDialog(R.string.amiibo_unknown_error)
+ if (!NativeLibrary.isRunning()) {
+ showAmiiboDialog(R.string.amiibo_wrong_state)
return@registerForActivityResult
}
- val amiiboData = data ?: run {
- showAmiiboDialog(R.string.amiibo_not_valid)
- return@registerForActivityResult
- }
+ amiiboLoadJob?.cancel()
+ val owner = viewLifecycleOwner
+ amiiboLoadJob = owner.lifecycleScope.launch {
+ val bytes = readAmiiboFile(uri)
- if (amiiboData.isEmpty()) {
- showAmiiboDialog(R.string.amiibo_not_valid)
- return@registerForActivityResult
- }
+ if (!isAdded) {
+ return@launch
+ }
+
+ if (bytes == null || bytes.isEmpty()) {
+ showAmiiboDialog(
+ if (bytes == null) R.string.amiibo_unknown_error else R.string.amiibo_not_valid
+ )
+ return@launch
+ }
- val result = NativeLibrary.loadAmiibo(amiiboData)
- handleAmiiboLoadResult(result)
+ val result = withContext(Dispatchers.IO) {
+ if (NativeLibrary.isRunning()) {
+ NativeLibrary.loadAmiibo(bytes)
+ } else {
+ AmiiboLoadResult.WrongDeviceState.value
+ }
+ }
+
+ if (!isAdded) {
+ return@launch
+ }
+ handleAmiiboLoadResult(result)
+ }.also { job ->
+ job.invokeOnCompletion {
+ if (amiiboLoadJob == job) {
+ amiiboLoadJob = null
+ }
+ }
+ }
}
override fun onAttach(context: Context) {
@@ -943,6 +969,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
when (AmiiboState.fromValue(NativeLibrary.getVirtualAmiiboState())) {
AmiiboState.TagNearby -> {
+ amiiboLoadJob?.cancel()
NativeInput.onRemoveNfcTag()
showAmiiboDialog(R.string.amiibo_removed_message)
}
@@ -995,6 +1022,31 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
.show()
}
+ private suspend fun readAmiiboFile(uri: Uri): ByteArray? =
+ withContext(Dispatchers.IO) {
+ val resolver = context?.contentResolver ?: return@withContext null
+ try {
+ resolver.openInputStream(uri)?.use { stream ->
+ val buffer = ByteArrayOutputStream()
+ val chunk = ByteArray(DEFAULT_BUFFER_SIZE)
+ while (true) {
+ coroutineContext.ensureActive()
+ val read = stream.read(chunk)
+ if (read == -1) {
+ break
+ }
+ buffer.write(chunk, 0, read)
+ }
+ buffer.toByteArray()
+ }
+ } catch (ce: CancellationException) {
+ throw ce
+ } catch (e: Exception) {
+ Log.error("[EmulationFragment] Failed to read amiibo: ${e.message}")
+ null
+ }
+ }
+
override fun onPause() {
if (this::emulationState.isInitialized) {
if (emulationState.isRunning && emulationActivity?.isInPictureInPictureMode != true) {
@@ -1007,6 +1059,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
override fun onDestroyView() {
super.onDestroyView()
+ amiiboLoadJob?.cancel()
+ amiiboLoadJob = null
_binding = null
isAmiiboPickerOpen = false
}