|
|
@@ -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()
|
|
|
+ )
|
|
|
+ )
|
|
|
+ }
|
|
|
+}
|