summaryrefslogtreecommitdiff
path: root/Source
diff options
context:
space:
mode:
authorRyan Houdek <Sonicadvance1@gmail.com>2016-02-07 15:51:33 -0500
committerRyan Houdek <Sonicadvance1@gmail.com>2016-02-07 15:51:33 -0500
commit35d7c2bc78007f550474f035c486bcc5f79ef60f (patch)
tree862050acf1357424dbd0912dc5937ba1b0601393 /Source
parent76d614ededd95deb4e6dea774541d26827d5c4cc (diff)
parentfe53461611d86384478064be6b4297a7c7e437e1 (diff)
Merge pull request #3593 from Sonicadvance1/Android_Mayflash_adapter
[Android] Implement support for real Wiimotes with the DolphinBar
Diffstat (limited to 'Source')
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java5
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.java6
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/Java_WiimoteAdapter.java156
-rw-r--r--Source/Android/app/src/main/res/layout/fragment_ingame_menu.xml5
-rw-r--r--Source/Android/app/src/main/res/values/strings.xml1
-rw-r--r--Source/Android/jni/MainAndroid.cpp7
-rw-r--r--Source/Core/Core/CMakeLists.txt2
-rw-r--r--Source/Core/Core/HW/WiimoteReal/IOAndroid.cpp165
-rw-r--r--Source/Core/Core/HW/WiimoteReal/WiimoteReal.h4
9 files changed, 351 insertions, 0 deletions
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java
index 38ea670f5e..992b670b1f 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java
@@ -380,6 +380,11 @@ public final class NativeLibrary
public static native void eglBindAPI(int api);
/**
+ * Provides a way to refresh the connections on Wiimotes
+ */
+ public static native void RefreshWiimotes();
+
+ /**
* The methods C++ uses to find references to Java classes and methods
* are really expensive. Rather than calling them every time we want to
* run them, do it once when we load the native library.
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.java
index 2f38df3373..f90956dd9a 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.java
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.java
@@ -33,6 +33,7 @@ import org.dolphinemu.dolphinemu.fragments.SaveStateFragment;
import org.dolphinemu.dolphinemu.ui.main.MainPresenter;
import org.dolphinemu.dolphinemu.utils.Animations;
import org.dolphinemu.dolphinemu.utils.Java_GCAdapter;
+import org.dolphinemu.dolphinemu.utils.Java_WiimoteAdapter;
import org.dolphinemu.dolphinemu.utils.Log;
import java.util.List;
@@ -120,6 +121,7 @@ public final class EmulationActivity extends AppCompatActivity
super.onCreate(savedInstanceState);
Java_GCAdapter.manager = (UsbManager) getSystemService(Context.USB_SERVICE);
+ Java_WiimoteAdapter.manager = (UsbManager) getSystemService(Context.USB_SERVICE);
// Picasso will take a while to load these big-ass screenshots. So don't run
// the animation until we say so.
@@ -392,6 +394,10 @@ public final class EmulationActivity extends AppCompatActivity
return;
}
+ case R.id.menu_refresh_wiimotes:
+ NativeLibrary.RefreshWiimotes();
+ return;
+
// Screenshot capturing
case R.id.menu_emulation_screenshot:
NativeLibrary.SaveScreenShot();
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/Java_WiimoteAdapter.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/Java_WiimoteAdapter.java
new file mode 100644
index 0000000000..049f930cba
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/Java_WiimoteAdapter.java
@@ -0,0 +1,156 @@
+package org.dolphinemu.dolphinemu.utils;
+
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.hardware.usb.UsbConfiguration;
+import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbDeviceConnection;
+import android.hardware.usb.UsbEndpoint;
+import android.hardware.usb.UsbInterface;
+import android.hardware.usb.UsbManager;
+
+import org.dolphinemu.dolphinemu.NativeLibrary;
+import org.dolphinemu.dolphinemu.services.USBPermService;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+public class Java_WiimoteAdapter
+{
+ final static int MAX_PAYLOAD = 23;
+ final static int MAX_WIIMOTES = 4;
+ final static int TIMEOUT = 200;
+ final static short NINTENDO_VENDOR_ID = 0x057e;
+ final static short NINTENDO_WIIMOTE_PRODUCT_ID = 0x0306;
+ public static UsbManager manager;
+
+ static UsbDeviceConnection usb_con;
+ static UsbInterface[] usb_intf = new UsbInterface[MAX_WIIMOTES];
+ static UsbEndpoint[] usb_in = new UsbEndpoint[MAX_WIIMOTES];
+
+ public static byte[][] wiimote_payload = new byte[MAX_WIIMOTES][MAX_PAYLOAD];
+
+ private static void RequestPermission()
+ {
+ HashMap<String, UsbDevice> devices = manager.getDeviceList();
+ for (Map.Entry<String, UsbDevice> pair : devices.entrySet())
+ {
+ UsbDevice dev = (UsbDevice) pair.getValue();
+ if (dev.getProductId() == NINTENDO_WIIMOTE_PRODUCT_ID && dev.getVendorId() == NINTENDO_VENDOR_ID)
+ {
+ if (!manager.hasPermission(dev))
+ {
+ Log.warning("Requesting permission for Wiimote adapter");
+ Intent intent = new Intent();
+ PendingIntent pend_intent;
+ intent.setClass(NativeLibrary.sEmulationActivity, USBPermService.class);
+ pend_intent = PendingIntent.getService(NativeLibrary.sEmulationActivity, 0, intent, 0);
+ manager.requestPermission(dev, pend_intent);
+ }
+ }
+ }
+ }
+
+ public static boolean QueryAdapter()
+ {
+ HashMap<String, UsbDevice> devices = manager.getDeviceList();
+ for (Map.Entry<String, UsbDevice> pair : devices.entrySet())
+ {
+ UsbDevice dev = (UsbDevice) pair.getValue();
+ if (dev.getProductId() == NINTENDO_WIIMOTE_PRODUCT_ID && dev.getVendorId() == NINTENDO_VENDOR_ID)
+ {
+ if (manager.hasPermission(dev))
+ return true;
+ else
+ RequestPermission();
+ }
+ }
+ return false;
+ }
+
+ public static int Input(int index)
+ {
+ return usb_con.bulkTransfer(usb_in[index], wiimote_payload[index], MAX_PAYLOAD, TIMEOUT);
+ }
+
+ public static int Output(int index, byte[] buf, int size)
+ {
+ byte report_number = buf[0];
+
+ // Remove the report number from the buffer
+ buf = Arrays.copyOfRange(buf, 1, buf.length);
+ size--;
+
+ final int LIBUSB_REQUEST_TYPE_CLASS = (1 << 5);
+ final int LIBUSB_RECIPIENT_INTERFACE = 0x1;
+ final int LIBUSB_ENDPOINT_OUT = 0;
+
+ final int HID_SET_REPORT = 0x9;
+ final int HID_OUTPUT = (2 << 8);
+
+ int write = usb_con.controlTransfer(
+ LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_OUT,
+ HID_SET_REPORT,
+ HID_OUTPUT | report_number,
+ index,
+ buf, size,
+ 1000);
+
+ if (write < 0)
+ return -1;
+
+ return write + 1;
+ }
+
+ public static boolean OpenAdapter()
+ {
+ // If the adapter is already open. Don't attempt to do it again
+ if (usb_con != null && usb_con.getFileDescriptor() != -1)
+ return true;
+
+ HashMap<String, UsbDevice> devices = manager.getDeviceList();
+ for (Map.Entry<String, UsbDevice> pair : devices.entrySet())
+ {
+ UsbDevice dev = (UsbDevice) pair.getValue();
+ if (dev.getProductId() == NINTENDO_WIIMOTE_PRODUCT_ID && dev.getVendorId() == NINTENDO_VENDOR_ID)
+ {
+ if (manager.hasPermission(dev))
+ {
+ usb_con = manager.openDevice(dev);
+ UsbConfiguration conf = dev.getConfiguration(0);
+
+ Log.info("Number of configurations: " + dev.getConfigurationCount());
+ Log.info("Number of Interfaces: " + dev.getInterfaceCount());
+ Log.info("Number of Interfaces from conf: " + conf.getInterfaceCount());
+
+ // Sometimes the interface count is returned as zero.
+ // Means the device needs to be unplugged and plugged back in again
+ if (dev.getInterfaceCount() > 0)
+ {
+ for (int i = 0; i < MAX_WIIMOTES; ++i)
+ {
+ // One interface per Wiimote
+ usb_intf[i] = dev.getInterface(i);
+ usb_con.claimInterface(usb_intf[i], true);
+
+ // One endpoint per Wiimote. Input only
+ // Output reports go through the control channel.
+ usb_in[i] = usb_intf[i].getEndpoint(0);
+ Log.info("Interface " + i + " endpoint count:" + usb_intf[i].getEndpointCount());
+ }
+ return true;
+ }
+ else
+ {
+ // XXX: Message that the device was found, but it needs to be unplugged and plugged back in?
+ usb_con.close();
+ }
+ }
+ }
+ }
+ return false;
+ }
+}
diff --git a/Source/Android/app/src/main/res/layout/fragment_ingame_menu.xml b/Source/Android/app/src/main/res/layout/fragment_ingame_menu.xml
index 44093dc324..e218fac5b5 100644
--- a/Source/Android/app/src/main/res/layout/fragment_ingame_menu.xml
+++ b/Source/Android/app/src/main/res/layout/fragment_ingame_menu.xml
@@ -60,6 +60,11 @@
style="@style/InGameMenuOption"/>
<Button
+ android:id="@+id/menu_refresh_wiimotes"
+ android:text="@string/emulation_refresh_wiimotes"
+ style="@style/InGameMenuOption"/>
+
+ <Button
android:id="@+id/menu_exit"
android:text="@string/overlay_exit_emulation"
style="@style/InGameMenuOption"/>
diff --git a/Source/Android/app/src/main/res/values/strings.xml b/Source/Android/app/src/main/res/values/strings.xml
index 4ae5e14c12..e3e309ca73 100644
--- a/Source/Android/app/src/main/res/values/strings.xml
+++ b/Source/Android/app/src/main/res/values/strings.xml
@@ -346,6 +346,7 @@
<string name="emulation_toggle_input">Toggle Touch Controls</string>
<string name="emulation_quicksave">Quick Save</string>
<string name="emulation_quickload">Quick Load</string>
+ <string name="emulation_refresh_wiimotes">Refresh Wiimotes</string>
<!-- GC Adapter Menu-->
<string name="gc_adapter_rumble">Enable Vibration</string>
diff --git a/Source/Android/jni/MainAndroid.cpp b/Source/Android/jni/MainAndroid.cpp
index ffa0eca40a..7d2849c96e 100644
--- a/Source/Android/jni/MainAndroid.cpp
+++ b/Source/Android/jni/MainAndroid.cpp
@@ -27,6 +27,7 @@
#include "Core/Host.h"
#include "Core/State.h"
#include "Core/HW/Wiimote.h"
+#include "Core/HW/WiimoteReal/WiimoteReal.h"
#include "Core/PowerPC/JitInterface.h"
#include "Core/PowerPC/PowerPC.h"
#include "Core/PowerPC/Profiler.h"
@@ -632,6 +633,10 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SurfaceDestr
Renderer::s_ChangedSurface.Wait();
}
}
+JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_RefreshWiimotes(JNIEnv *env, jobject obj)
+{
+ WiimoteReal::Refresh();
+}
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_Run(JNIEnv *env, jobject obj)
{
@@ -646,6 +651,8 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_Run(JNIEnv *
UICommon::SetUserDirectory(g_set_userpath);
UICommon::Init();
+ WiimoteReal::InitAdapterClass();
+
// No use running the loop when booting fails
if ( BootManager::BootCore( g_filename.c_str() ) )
{
diff --git a/Source/Core/Core/CMakeLists.txt b/Source/Core/Core/CMakeLists.txt
index b035b64859..4fe1353d23 100644
--- a/Source/Core/Core/CMakeLists.txt
+++ b/Source/Core/Core/CMakeLists.txt
@@ -256,6 +256,8 @@ elseif(UNIX)
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux" AND BLUEZ_FOUND)
set(SRCS ${SRCS} HW/WiimoteReal/IONix.cpp)
set(LIBS ${LIBS} bluetooth)
+ elseif(ANDROID)
+ set(SRCS ${SRCS} HW/WiimoteReal/IOAndroid.cpp)
else()
set(SRCS ${SRCS} HW/WiimoteReal/IODummy.cpp)
endif()
diff --git a/Source/Core/Core/HW/WiimoteReal/IOAndroid.cpp b/Source/Core/Core/HW/WiimoteReal/IOAndroid.cpp
new file mode 100644
index 0000000000..7a2e1e249c
--- /dev/null
+++ b/Source/Core/Core/HW/WiimoteReal/IOAndroid.cpp
@@ -0,0 +1,165 @@
+// Copyright 2016 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#include <jni.h>
+
+#include "Common/CommonTypes.h"
+#include "Common/Event.h"
+#include "Common/Flag.h"
+#include "Common/StringUtil.h"
+#include "Common/Thread.h"
+#include "Common/Timer.h"
+#include "Common/Logging/Log.h"
+
+#include "Core/HW/WiimoteReal/WiimoteReal.h"
+
+// Global java_vm class
+extern JavaVM* g_java_vm;
+
+namespace WiimoteReal
+{
+
+// Java classes
+static jclass s_adapter_class;
+
+class WiimoteAndroid final : public Wiimote
+{
+public:
+ WiimoteAndroid(int index);
+ ~WiimoteAndroid() override;
+
+protected:
+ bool ConnectInternal() override;
+ void DisconnectInternal() override;
+ bool IsConnected() const override;
+ void IOWakeup() {}
+ int IORead(u8* buf) override;
+ int IOWrite(u8 const* buf, size_t len) override;
+
+private:
+ int m_mayflash_index;
+ bool is_connected = true;
+
+ JNIEnv* m_env;
+
+ jmethodID m_input_func;
+ jmethodID m_output_func;
+
+ jbyteArray m_java_wiimote_payload;
+};
+
+WiimoteScanner::WiimoteScanner()
+{}
+
+WiimoteScanner::~WiimoteScanner()
+{}
+
+void WiimoteScanner::Update()
+{}
+
+void WiimoteScanner::FindWiimotes(std::vector<Wiimote*>& found_wiimotes, Wiimote*& found_board)
+{
+ found_wiimotes.clear();
+ found_board = nullptr;
+
+ NOTICE_LOG(WIIMOTE, "Finding Wiimotes");
+
+ JNIEnv* env;
+ int get_env_status = g_java_vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6);
+
+ if (get_env_status == JNI_EDETACHED)
+ g_java_vm->AttachCurrentThread(&env, nullptr);
+
+ jmethodID openadapter_func = env->GetStaticMethodID(s_adapter_class, "OpenAdapter", "()Z");
+ jmethodID queryadapter_func = env->GetStaticMethodID(s_adapter_class, "QueryAdapter", "()Z");
+
+ if (env->CallStaticBooleanMethod(s_adapter_class, queryadapter_func) &&
+ env->CallStaticBooleanMethod(s_adapter_class, openadapter_func))
+ {
+ for (int i = 0; i < MAX_WIIMOTES; ++i)
+ found_wiimotes.emplace_back(new WiimoteAndroid(i));
+ }
+
+ if (get_env_status == JNI_EDETACHED)
+ g_java_vm->DetachCurrentThread();
+}
+
+bool WiimoteScanner::IsReady() const
+{
+ return true;
+}
+
+WiimoteAndroid::WiimoteAndroid(int index)
+ : Wiimote(), m_mayflash_index(index)
+{
+}
+
+WiimoteAndroid::~WiimoteAndroid()
+{
+ Shutdown();
+}
+
+// Connect to a Wiimote with a known address.
+bool WiimoteAndroid::ConnectInternal()
+{
+ g_java_vm->AttachCurrentThread(&m_env, nullptr);
+
+ jfieldID payload_field = m_env->GetStaticFieldID(s_adapter_class, "wiimote_payload", "[[B");
+ jobjectArray payload_object = reinterpret_cast<jobjectArray>(m_env->GetStaticObjectField(s_adapter_class, payload_field));
+ m_java_wiimote_payload = (jbyteArray)m_env->GetObjectArrayElement(payload_object, m_mayflash_index);
+
+ // Get function pointers
+ m_input_func = m_env->GetStaticMethodID(s_adapter_class, "Input", "(I)I");
+ m_output_func = m_env->GetStaticMethodID(s_adapter_class, "Output", "(I[BI)I");
+
+ is_connected = true;
+
+ return true;
+}
+
+void WiimoteAndroid::DisconnectInternal()
+{
+ g_java_vm->DetachCurrentThread();
+}
+
+bool WiimoteAndroid::IsConnected() const
+{
+ return is_connected;
+}
+
+// positive = read packet
+// negative = didn't read packet
+// zero = error
+int WiimoteAndroid::IORead(u8* buf)
+{
+ int read_size = m_env->CallStaticIntMethod(s_adapter_class, m_input_func, m_mayflash_index);
+ jbyte* java_data = m_env->GetByteArrayElements(m_java_wiimote_payload, nullptr);
+ memcpy(buf + 1, java_data, std::min(MAX_PAYLOAD - 1, read_size));
+ buf[0] = 0xA1;
+ m_env->ReleaseByteArrayElements(m_java_wiimote_payload, java_data, 0);
+ return read_size <= 0 ? read_size : read_size + 1;
+}
+
+
+int WiimoteAndroid::IOWrite(u8 const* buf, size_t len)
+{
+ jbyteArray output_array = m_env->NewByteArray(len);
+ jbyte* output = m_env->GetByteArrayElements(output_array, nullptr);
+ memcpy(output, buf, len);
+ m_env->ReleaseByteArrayElements(output_array, output, 0);
+ int written = m_env->CallStaticIntMethod(s_adapter_class, m_output_func, m_mayflash_index, output_array, len);
+ m_env->DeleteLocalRef(output_array);
+ return written;
+}
+
+void InitAdapterClass()
+{
+ JNIEnv* env;
+ g_java_vm->AttachCurrentThread(&env, nullptr);
+
+ jclass adapter_class = env->FindClass("org/dolphinemu/dolphinemu/utils/Java_WiimoteAdapter");
+ s_adapter_class = reinterpret_cast<jclass>(env->NewGlobalRef(adapter_class));
+}
+
+}
diff --git a/Source/Core/Core/HW/WiimoteReal/WiimoteReal.h b/Source/Core/Core/HW/WiimoteReal/WiimoteReal.h
index 1ca31a8d69..60c5e38bbb 100644
--- a/Source/Core/Core/HW/WiimoteReal/WiimoteReal.h
+++ b/Source/Core/Core/HW/WiimoteReal/WiimoteReal.h
@@ -161,4 +161,8 @@ void ChangeWiimoteSource(unsigned int index, int source);
bool IsValidBluetoothName(const std::string& name);
bool IsBalanceBoardName(const std::string& name);
+#ifdef ANDROID
+void InitAdapterClass();
+#endif
+
} // WiimoteReal