summaryrefslogtreecommitdiffstats
path: root/src/android
diff options
context:
space:
mode:
authorNarr the Reg <juangerman-13@hotmail.com>2023-03-22 18:09:12 +0100
committerbunnei <bunneidev@gmail.com>2023-06-03 09:05:48 +0200
commitca4be4283d8eec9624019f4bca88f7d1aa467f01 (patch)
tree6fc3424bb97d17315b1e63335020efc57a65b912 /src/android
parentandroid: vulkan_device: Disable VK_EXT_custom_border_color on Adreno. (diff)
downloadyuzu-ca4be4283d8eec9624019f4bca88f7d1aa467f01.tar
yuzu-ca4be4283d8eec9624019f4bca88f7d1aa467f01.tar.gz
yuzu-ca4be4283d8eec9624019f4bca88f7d1aa467f01.tar.bz2
yuzu-ca4be4283d8eec9624019f4bca88f7d1aa467f01.tar.lz
yuzu-ca4be4283d8eec9624019f4bca88f7d1aa467f01.tar.xz
yuzu-ca4be4283d8eec9624019f4bca88f7d1aa467f01.tar.zst
yuzu-ca4be4283d8eec9624019f4bca88f7d1aa467f01.zip
Diffstat (limited to 'src/android')
-rw-r--r--src/android/app/src/main/AndroidManifest.xml15
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.java12
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt20
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt32
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainPresenter.kt7
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NfcReader.kt165
-rw-r--r--src/android/app/src/main/jni/emu_window/emu_window.cpp11
-rw-r--r--src/android/app/src/main/jni/emu_window/emu_window.h3
-rw-r--r--src/android/app/src/main/jni/native.cpp20
-rw-r--r--src/android/app/src/main/jni/native.h8
-rw-r--r--src/android/app/src/main/res/menu/menu_game_grid.xml6
-rw-r--r--src/android/app/src/main/res/values/strings.xml2
-rw-r--r--src/android/app/src/main/res/xml/nfc_tech_filter.xml6
13 files changed, 303 insertions, 4 deletions
diff --git a/src/android/app/src/main/AndroidManifest.xml b/src/android/app/src/main/AndroidManifest.xml
index c8f1696c5..a5c063d52 100644
--- a/src/android/app/src/main/AndroidManifest.xml
+++ b/src/android/app/src/main/AndroidManifest.xml
@@ -13,6 +13,7 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
+ <uses-permission android:name="android.permission.NFC" />
<application
android:name="org.yuzu.yuzu_emu.YuzuApplication"
@@ -48,7 +49,19 @@
android:name="org.yuzu.yuzu_emu.activities.EmulationActivity"
android:theme="@style/Theme.Yuzu.Main"
android:launchMode="singleTop"
- android:screenOrientation="userLandscape" />
+ android:screenOrientation="userLandscape"
+ android:exported="true">
+
+ <intent-filter>
+ <action android:name="android.nfc.action.TECH_DISCOVERED" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <data android:mimeType="application/octet-stream" />
+ </intent-filter>
+
+ <meta-data
+ android:name="android.nfc.action.TECH_DISCOVERED"
+ android:resource="@xml/nfc_tech_filter" />
+ </activity>
<service android:name="org.yuzu.yuzu_emu.utils.ForegroundService"/>
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()
+ )
+ )
+ }
+}
diff --git a/src/android/app/src/main/jni/emu_window/emu_window.cpp b/src/android/app/src/main/jni/emu_window/emu_window.cpp
index ad17cf129..2fdb68823 100644
--- a/src/android/app/src/main/jni/emu_window/emu_window.cpp
+++ b/src/android/app/src/main/jni/emu_window/emu_window.cpp
@@ -2,6 +2,7 @@
#include "common/logging/log.h"
#include "input_common/drivers/touch_screen.h"
+#include "input_common/drivers/virtual_amiibo.h"
#include "input_common/drivers/virtual_gamepad.h"
#include "input_common/main.h"
#include "jni/emu_window/emu_window.h"
@@ -36,7 +37,15 @@ void EmuWindow_Android::OnGamepadMotionEvent(int player_index, u64 delta_timesta
float gyro_y, float gyro_z, float accel_x,
float accel_y, float accel_z) {
m_input_subsystem->GetVirtualGamepad()->SetMotionState(
- player_index, delta_timestamp, gyro_x, gyro_y, gyro_z, accel_x, accel_y, accel_z);
+ player_index, delta_timestamp, gyro_x, gyro_y, gyro_z, accel_x, accel_y, accel_z);
+}
+
+void EmuWindow_Android::OnReadNfcTag(std::span<u8> data) {
+ m_input_subsystem->GetVirtualAmiibo()->LoadAmiibo(data);
+}
+
+void EmuWindow_Android::OnRemoveNfcTag() {
+ m_input_subsystem->GetVirtualAmiibo()->CloseAmiibo();
}
EmuWindow_Android::EmuWindow_Android(InputCommon::InputSubsystem* input_subsystem,
diff --git a/src/android/app/src/main/jni/emu_window/emu_window.h b/src/android/app/src/main/jni/emu_window/emu_window.h
index 1c1edf62c..2a0a72077 100644
--- a/src/android/app/src/main/jni/emu_window/emu_window.h
+++ b/src/android/app/src/main/jni/emu_window/emu_window.h
@@ -1,6 +1,7 @@
#pragma once
#include <memory>
+#include <span>
#include "core/frontend/emu_window.h"
#include "core/frontend/graphics_context.h"
@@ -39,6 +40,8 @@ public:
void OnGamepadJoystickEvent(int player_index, int stick_id, float x, float y);
void OnGamepadMotionEvent(int player_index, u64 delta_timestamp, float gyro_x, float gyro_y,
float gyro_z, float accel_x, float accel_y, float accel_z);
+ void OnReadNfcTag(std::span<u8> data);
+ void OnRemoveNfcTag();
void OnFrameDisplayed() override {}
std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override {
diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp
index 10603c8fa..86994f734 100644
--- a/src/android/app/src/main/jni/native.cpp
+++ b/src/android/app/src/main/jni/native.cpp
@@ -451,6 +451,26 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadMotionEvent(
return static_cast<jboolean>(true);
}
+jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onReadNfcTag(
+ [[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz, jbyteArray j_data) {
+ jboolean isCopy{false};
+ std::span<u8> data(reinterpret_cast<u8 *>(env->GetByteArrayElements(j_data, &isCopy)),
+ static_cast<size_t>(env->GetArrayLength(j_data)));
+
+ if (EmulationSession::GetInstance().IsRunning()) {
+ EmulationSession::GetInstance().Window().OnReadNfcTag(data);
+ }
+ return static_cast<jboolean>(true);
+}
+
+jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onRemoveNfcTag(
+ [[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz) {
+ if (EmulationSession::GetInstance().IsRunning()) {
+ EmulationSession::GetInstance().Window().OnRemoveNfcTag();
+ }
+ return static_cast<jboolean>(true);
+}
+
void Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchPressed([[maybe_unused]] JNIEnv* env,
[[maybe_unused]] jclass clazz, jint id,
jfloat x, jfloat y) {
diff --git a/src/android/app/src/main/jni/native.h b/src/android/app/src/main/jni/native.h
index d30351c16..8336e525a 100644
--- a/src/android/app/src/main/jni/native.h
+++ b/src/android/app/src/main/jni/native.h
@@ -32,7 +32,13 @@ JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadMoveEv
JNIEnv* env, jclass clazz, jstring j_device, jint axis, jfloat x, jfloat y);
JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadAxisEvent(
- JNIEnv* env, jclass clazz, jstring j_device, jint axis_id, jfloat axis_val);
+ JNIEnv* env, jclass clazz, jstring j_device, jint axis_id, jfloat axis_val);
+
+JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onReadNfcTag(
+ JNIEnv* env, jclass clazz, jbyteArray j_data);
+
+JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onRemoveNfcTag(
+ JNIEnv* env, jclass clazz);
JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchEvent(JNIEnv* env,
jclass clazz,
diff --git a/src/android/app/src/main/res/menu/menu_game_grid.xml b/src/android/app/src/main/res/menu/menu_game_grid.xml
index b834a444e..73046de0e 100644
--- a/src/android/app/src/main/res/menu/menu_game_grid.xml
+++ b/src/android/app/src/main/res/menu/menu_game_grid.xml
@@ -23,6 +23,12 @@
app:showAsAction="ifRoom" />
<item
+ android:id="@+id/button_install_amiibo_keys"
+ android:icon="@drawable/ic_install"
+ android:title="@string/install_amiibo_keys"
+ app:showAsAction="ifRoom" />
+
+ <item
android:id="@+id/button_select_gpu_driver"
android:icon="@drawable/ic_settings"
android:title="@string/select_gpu_driver"
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml
index ea9290df5..29c1b1691 100644
--- a/src/android/app/src/main/res/values/strings.xml
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -52,8 +52,10 @@
<!-- Add Directory Screen-->
<string name="select_game_folder">Select game folder</string>
<string name="install_keys">Install keys</string>
+ <string name="install_amiibo_keys">Install amiibo keys</string>
<string name="install_keys_success">Keys successfully installed</string>
<string name="install_keys_failure">Keys file (prod.keys) is invalid</string>
+ <string name="install_amiibo_keys_failure">Keys file (key_retail.bin) is invalid</string>
<!-- GPU driver installation -->
<string name="select_gpu_driver">Select GPU driver</string>
diff --git a/src/android/app/src/main/res/xml/nfc_tech_filter.xml b/src/android/app/src/main/res/xml/nfc_tech_filter.xml
new file mode 100644
index 000000000..eb4497446
--- /dev/null
+++ b/src/android/app/src/main/res/xml/nfc_tech_filter.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <tech-list>
+ <tech>android.nfc.tech.NfcA</tech>
+ </tech-list>
+</resources>