diff options
Diffstat (limited to 'src/android/app/src/main/java/org')
5 files changed, 235 insertions, 1 deletions
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.java b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.java index c056b7d6d..5def17f2b 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.java +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.java @@ -124,6 +124,18 @@ public final class NativeLibrary { float gyro_z, float accel_x, float accel_y, float accel_z); /** + * Signals and load a nfc tag + * + * @param data Byte array containing all the data from a nfc tag + */ + public static native boolean onReadNfcTag(byte[] data); + + /** + * Removes current loaded nfc tag + */ + public static native boolean onRemoveNfcTag(); + + /** * Handles touch press events. * * @param finger_id The finger id corresponding to this event 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 3589e7629..32d04ef31 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 @@ -24,6 +24,7 @@ import org.yuzu.yuzu_emu.features.settings.model.Settings import org.yuzu.yuzu_emu.fragments.EmulationFragment import org.yuzu.yuzu_emu.model.Game import org.yuzu.yuzu_emu.utils.ControllerMappingHelper +import org.yuzu.yuzu_emu.utils.NfcReader import org.yuzu.yuzu_emu.utils.SerializableHelper.parcelable import org.yuzu.yuzu_emu.utils.ThemeHelper import kotlin.math.roundToInt @@ -37,6 +38,7 @@ open class EmulationActivity : AppCompatActivity() { var isActivityRecreated = false private var menuVisible = false private var emulationFragment: EmulationFragment? = null + private lateinit var nfcReader: NfcReader private lateinit var game: Game @@ -76,6 +78,9 @@ open class EmulationActivity : AppCompatActivity() { } title = game.title + nfcReader = NfcReader(this) + nfcReader.initialize() + // Start a foreground service to prevent the app from getting killed in the background // TODO(bunnei): Disable notifications until we support app suspension. //foregroundService = new Intent(EmulationActivity.this, ForegroundService.class); @@ -104,6 +109,21 @@ open class EmulationActivity : AppCompatActivity() { } return super.onKeyDown(keyCode, event) } + override fun onResume() { + super.onResume() + nfcReader.startScanning() + } + + override fun onPause() { + super.onPause() + nfcReader.stopScanning() + } + + override fun onNewIntent(intent: Intent) { + super.onNewIntent(intent) + setIntent(intent) + nfcReader.onNewIntent(intent) + } override fun onSaveInstanceState(outState: Bundle) { outState.putParcelable(EXTRA_SELECTED_GAME, game) 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 b87125d1c..441c9da9c 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 @@ -112,6 +112,7 @@ class MainActivity : AppCompatActivity(), MainView { when (request) { MainPresenter.REQUEST_ADD_DIRECTORY -> getGamesDirectory.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data) MainPresenter.REQUEST_INSTALL_KEYS -> getProdKey.launch(arrayOf("*/*")) + MainPresenter.REQUEST_INSTALL_AMIIBO_KEYS -> getAmiiboKey.launch(arrayOf("*/*")) MainPresenter.REQUEST_SELECT_GPU_DRIVER -> { // Get the driver name for the dialog message. var driverName = GpuDriverHelper.customDriverName @@ -221,6 +222,37 @@ class MainActivity : AppCompatActivity(), MainView { } } + private val getAmiiboKey = + registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> + if (result == null) + return@registerForActivityResult + + val takeFlags = + Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION + contentResolver.takePersistableUriPermission( + result, + takeFlags + ) + + val dstPath = DirectoryInitialization.userDirectory + "/keys/" + if (FileUtil.copyUriToInternalStorage(this, result, dstPath, "key_retail.bin")) { + if (NativeLibrary.ReloadKeys()) { + Toast.makeText( + this, + R.string.install_keys_success, + Toast.LENGTH_SHORT + ).show() + refreshFragment() + } else { + Toast.makeText( + this, + R.string.install_amiibo_keys_failure, + Toast.LENGTH_LONG + ).show() + } + } + } + private val getDriver = registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> if (result == null) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainPresenter.kt index dbfda7be3..554542e05 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainPresenter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainPresenter.kt @@ -36,6 +36,10 @@ class MainPresenter(private val view: MainView) { launchFileListActivity(REQUEST_INSTALL_KEYS) return true } + R.id.button_install_amiibo_keys -> { + launchFileListActivity(REQUEST_INSTALL_AMIIBO_KEYS) + return true + } R.id.button_select_gpu_driver -> { launchFileListActivity(REQUEST_SELECT_GPU_DRIVER) return true @@ -64,6 +68,7 @@ class MainPresenter(private val view: MainView) { companion object { const val REQUEST_ADD_DIRECTORY = 1 const val REQUEST_INSTALL_KEYS = 2 - const val REQUEST_SELECT_GPU_DRIVER = 3 + const val REQUEST_INSTALL_AMIIBO_KEYS = 3 + const val REQUEST_SELECT_GPU_DRIVER = 4 } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NfcReader.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NfcReader.kt new file mode 100644 index 000000000..1ce220d42 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NfcReader.kt @@ -0,0 +1,165 @@ +package org.yuzu.yuzu_emu.utils + +import android.app.Activity +import android.app.PendingIntent +import android.content.Intent +import android.content.IntentFilter +import android.nfc.NfcAdapter +import android.nfc.Tag +import android.nfc.tech.NfcA +import android.os.Build +import android.os.Handler +import android.os.Looper +import org.yuzu.yuzu_emu.NativeLibrary +import java.io.IOException + +class NfcReader(private val activity: Activity) { + private var nfcAdapter: NfcAdapter? = null + private var pendingIntent: PendingIntent? = null + + fun initialize() { + nfcAdapter = NfcAdapter.getDefaultAdapter(activity) ?: return + + pendingIntent = PendingIntent.getActivity( + activity, + 0, Intent(activity, activity.javaClass), + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE + else PendingIntent.FLAG_UPDATE_CURRENT + ) + + val tagDetected = IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED) + tagDetected.addCategory(Intent.CATEGORY_DEFAULT) + } + + fun startScanning() { + nfcAdapter?.enableForegroundDispatch(activity, pendingIntent, null, null) + } + + fun stopScanning() { + nfcAdapter?.disableForegroundDispatch(activity) + } + + fun onNewIntent(intent: Intent) { + val action = intent.action + if (NfcAdapter.ACTION_TAG_DISCOVERED != action + && NfcAdapter.ACTION_TECH_DISCOVERED != action + && NfcAdapter.ACTION_NDEF_DISCOVERED != action + ) { + return + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + val tag = + intent.getParcelableExtra(NfcAdapter.EXTRA_TAG, Tag::class.java) ?: return + readTagData(tag) + return + } + + val tag = + intent.getParcelableExtra<Tag>(NfcAdapter.EXTRA_TAG) ?: return + readTagData(tag) + } + + private fun readTagData(tag: Tag) { + if (!tag.techList.contains("android.nfc.tech.NfcA")) { + return + } + + val amiibo = NfcA.get(tag) ?: return + amiibo.connect() + + val tagData = ntag215ReadAll(amiibo) ?: return + NativeLibrary.onReadNfcTag(tagData) + + nfcAdapter?.ignore( + tag, + 1000, + { NativeLibrary.onRemoveNfcTag() }, + Handler(Looper.getMainLooper()) + ) + } + + private fun ntag215ReadAll(amiibo: NfcA): ByteArray? { + val bufferSize = amiibo.maxTransceiveLength; + val tagSize = 0x21C + val pageSize = 4 + val lastPage = tagSize / pageSize - 1 + val tagData = ByteArray(tagSize) + + // We need to read the ntag in steps otherwise we overflow the buffer + for (i in 0..tagSize step bufferSize - 1) { + val dataStart = i / pageSize + var dataEnd = (i + bufferSize) / pageSize + + if (dataEnd > lastPage) { + dataEnd = lastPage + } + + try { + val data = ntag215FastRead(amiibo, dataStart, dataEnd - 1) + System.arraycopy(data, 0, tagData, i, (dataEnd - dataStart) * pageSize) + } catch (e: IOException) { + return null; + } + } + return tagData + } + + private fun ntag215Read(amiibo: NfcA, page: Int): ByteArray? { + return amiibo.transceive( + byteArrayOf( + 0x30.toByte(), + (page and 0xFF).toByte() + ) + ) + } + + private fun ntag215FastRead(amiibo: NfcA, start: Int, end: Int): ByteArray? { + return amiibo.transceive( + byteArrayOf( + 0x3A.toByte(), + (start and 0xFF).toByte(), + (end and 0xFF).toByte() + ) + ) + } + + private fun ntag215PWrite( + amiibo: NfcA, + page: Int, + data1: Int, + data2: Int, + data3: Int, + data4: Int + ): ByteArray? { + return amiibo.transceive( + byteArrayOf( + 0xA2.toByte(), + (page and 0xFF).toByte(), + (data1 and 0xFF).toByte(), + (data2 and 0xFF).toByte(), + (data3 and 0xFF).toByte(), + (data4 and 0xFF).toByte() + ) + ) + } + + private fun ntag215PwdAuth( + amiibo: NfcA, + data1: Int, + data2: Int, + data3: Int, + data4: Int + ): ByteArray? { + return amiibo.transceive( + byteArrayOf( + 0x1B.toByte(), + (data1 and 0xFF).toByte(), + (data2 and 0xFF).toByte(), + (data3 and 0xFF).toByte(), + (data4 and 0xFF).toByte() + ) + ) + } +} |