WiimoteReal: reimplement using HIDAPI

Using the HIDAPI library has the advantage that we get Wiimote and
DolphinBar support on all platforms with only one IO implementation.
This commit is contained in:
Tillmann Karras 2015-01-10 18:10:45 +01:00
parent 1c26d41c9a
commit ca0c2efe7a
19 changed files with 245 additions and 1703 deletions

View File

@ -2,6 +2,7 @@
# General setup
#
cmake_minimum_required(VERSION 2.8.8)
project(dolphin-emu)
option(USE_EGL "Enables EGL OpenGL Interface" OFF)
option(TRY_X11 "Enables X11 Support" ON)
@ -13,6 +14,9 @@ option(ENABLE_LTO "Enables Link Time Optimization" OFF)
option(ENABLE_GENERIC "Enables generic build that should run on any little-endian host" OFF)
if(APPLE)
option(OSX_USE_DEFAULT_SEARCH_PATH "Don't prioritize system library paths" OFF)
option(SKIP_POSTPROCESS_BUNDLE "Skip postprocessing bundle for redistributability" OFF)
elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
option(INSTALL_UDEV_RULES "Install udev rules for hidraw/usb devices" OFF)
endif()
option(ENCODE_FRAMEDUMPS "Encode framedumps in AVI format" ON)
@ -21,9 +25,6 @@ option(FASTLOG "Enable all logs" OFF)
option(OPROFILING "Enable profiling" OFF)
option(GDBSTUB "Enable gdb stub for remote debugging." OFF)
if(APPLE)
option(SKIP_POSTPROCESS_BUNDLE "Skip postprocessing bundle for redistributability" OFF)
endif()
########################################
# Optional Targets
# TODO: Add DSPSpy
@ -45,7 +46,6 @@ if (APPLE)
endif()
endif()
endif()
project(dolphin-emu)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/CMakeTests)
set(DOLPHIN_IS_STABLE FALSE)
# Libraries to link
@ -292,7 +292,6 @@ if(APPLE)
find_library(COREFUND_LIBRARY CoreFoundation)
find_library(CORESERV_LIBRARY CoreServices)
find_library(FOUNDATION_LIBRARY foundation)
find_library(IOB_LIBRARY IOBluetooth)
find_library(IOK_LIBRARY IOKit)
find_library(QUICKTIME_LIBRARY QuickTime)
find_library(WEBKIT_LIBRARY WebKit)
@ -379,15 +378,6 @@ if(NOT ANDROID)
message("ao NOT found, disabling ao sound backend")
endif(AO_FOUND)
check_lib(BLUEZ bluez QUIET)
if(BLUEZ_FOUND)
add_definitions(-DHAVE_BLUEZ=1)
message("bluez found, enabling bluetooth support")
else()
add_definitions(-DHAVE_BLUEZ=0)
message("bluez NOT found, disabling bluetooth support")
endif(BLUEZ_FOUND)
check_lib(PULSEAUDIO libpulse QUIET)
if(PULSEAUDIO_FOUND)
add_definitions(-DHAVE_PULSEAUDIO=1)
@ -595,10 +585,19 @@ endif()
include(FindLibUSB OPTIONAL)
if(LIBUSB_FOUND)
message("Using shared LibUSB")
message("Using shared libusb")
add_definitions(-D__LIBUSB__)
include_directories(${LIBUSB_INCLUDE_DIR})
endif(LIBUSB_FOUND)
list(APPEND LIBS ${LIBUSB_LIBRARIES})
elseif(ANDROID)
message("Using static libusb from Externals")
add_subdirectory(Externals/libusb/libusb)
include_directories(Externals/libusb/libusb)
list(APPEND LIBS usb-1.0)
else()
message(FATAL_ERROR "libusb is required. Either extend the CMakeLists.txt file to support your OS"
" or just install libusb as a shared library.")
endif()
set(SFML_REQD_VERSION 2.1)
if(NOT APPLE AND NOT ANDROID)
@ -665,6 +664,32 @@ else()
mark_as_advanced(ICONV_INCLUDE_DIR ICONV_LIBRARIES)
endif()
include(FindHIDAPI OPTIONAL)
find_package(HIDAPI)
if(HIDAPI_FOUND)
message("Using shared ${HIDAPI_LIBRARIES} ${HIDAPI_VERSION}")
include_directories(${HIDAPI_INCLUDE_DIRS})
list(APPEND LIBS ${HIDAPI_LIBRARIES})
else()
include_directories(Externals/hidapi/hidapi)
if(APPLE)
message("Using static hidapi from Externals")
add_subdirectory(Externals/hidapi/mac)
list(APPEND LIBS hidapi)
elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Linux" AND NOT ANDROID)
message("Using static hidapi-hidraw from Externals")
add_subdirectory(Externals/hidapi/linux)
list(APPEND LIBS hidapi-hidraw udev)
else()
message("Using static hidapi-libusb from Externals")
add_subdirectory(Externals/hidapi/libusb)
list(APPEND LIBS hidapi-libusb)
endif()
endif()
if(INSTALL_UDEV_RULES)
install(FILES Installer/90-wiimote.rules /etc/udev/rules.d/)
endif()
if(ENABLE_QT)
find_package(Qt5Widgets REQUIRED)
message("Found Qt version ${Qt5Core_VERSION}, enabling the Qt backend")

View File

@ -0,0 +1,9 @@
find_path(HIDAPI_INCLUDE_DIR NAMES hidapi.h PATH_SUFFIXES hidapi)
find_library(HIDAPI_LIBRARY NAMES hidapi hidapi-hidraw hidapi-libusb)
set(HIDAPI_LIBRARIES ${HIDAPI_LIBRARY})
set(HIDAPI_INCLUDE_DIRS ${HIDAPI_INCLUDE_DIR})
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(HIDAPI DEFAULT_MSG HIDAPI_LIBRARY HIDAPI_INCLUDE_DIR)
mark_as_advanced(HIDAPI_INCLUDE_DIR HIDAPI_LIBRARY)

View File

@ -0,0 +1 @@
add_library(hidapi-libusb hid.c)

1
Externals/hidapi/linux/CMakeLists.txt vendored Normal file
View File

@ -0,0 +1 @@
add_library(hidapi-hidraw hid.c)

1
Externals/hidapi/mac/CMakeLists.txt vendored Normal file
View File

@ -0,0 +1 @@
add_library(hidapi hid.c)

17
Externals/libusb/libusb/CMakeLists.txt vendored Normal file
View File

@ -0,0 +1,17 @@
# This file is only used for Android (because I'm lazy).
# We don't use CMake on Windows and just require libusb to be installed everywhere else.
set(SRCS
core.c
descriptor.c
hotplug.c
io.c
strerror.c
sync.c
)
include_directories(BEFORE ../android)
add_subdirectory(os)
add_library(usb-1.0 STATIC ${SRCS})
target_link_libraries(usb-1.0 usb-os)

View File

@ -0,0 +1,7 @@
include_directories(..)
add_library(usb-os STATIC
linux_netlink.c
linux_usbfs.c
poll_posix.c
threads_posix.c
)

View File

@ -27,7 +27,6 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<CharacterSet>Unicode</CharacterSet>
<WholeProgramOptimization>true</WholeProgramOptimization>
<PlatformToolset>v120</PlatformToolset>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
@ -38,7 +37,6 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<CharacterSet>Unicode</CharacterSet>
<WholeProgramOptimization>true</WholeProgramOptimization>
<PlatformToolset>v120</PlatformToolset>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">

View File

@ -0,0 +1,8 @@
# This is how you can match Wiimotes (and Wiimote-emulating devices like the DolphinBar) in udev.
# If you are a package maintainer you'll probably want to restrict permissions to a group: GROUP="plugdev", MODE="0660".
# Wiimote or DolphinBar
SUBSYSTEMS=="hid", ATTRS{idVendor}=="057e", ATTRS{idProduct}=="0306", MODE="0666"
# newer Wiimotes (RVL-CNT-01-TR)
SUBSYSTEMS=="hid", ATTRS{idVendor}=="057e", ATTRS{idProduct}=="0330", MODE="0666"

View File

@ -116,6 +116,7 @@ set(SRCS ActionReplay.cpp
HW/SI_DeviceGBA.cpp
HW/SI_DeviceGCController.cpp
HW/SI_DeviceGCSteeringWheel.cpp
HW/SI_GCAdapter.cpp
HW/Sram.cpp
HW/StreamADPCM.cpp
HW/SystemTimers.cpp
@ -132,6 +133,7 @@ set(SRCS ActionReplay.cpp
HW/WiimoteEmu/EmuSubroutines.cpp
HW/WiimoteEmu/Encryption.cpp
HW/WiimoteEmu/Speaker.cpp
HW/WiimoteReal/IOhidapi.cpp
HW/WiimoteReal/WiimoteReal.cpp
HW/WiiSaveCrypted.cpp
IPC_HLE/ICMPLin.cpp
@ -140,6 +142,7 @@ set(SRCS ActionReplay.cpp
IPC_HLE/WII_IPC_HLE_Device_es.cpp
IPC_HLE/WII_IPC_HLE_Device_FileIO.cpp
IPC_HLE/WII_IPC_HLE_Device_fs.cpp
IPC_HLE/WII_IPC_HLE_Device_hid.cpp
IPC_HLE/WII_Socket.cpp
IPC_HLE/WII_IPC_HLE_Device_net.cpp
IPC_HLE/WII_IPC_HLE_Device_net_ssl.cpp
@ -245,29 +248,15 @@ set(LIBS
z
)
if(LIBUSB_FOUND)
# Using shared LibUSB
set(LIBS ${LIBS} ${LIBUSB_LIBRARIES})
set(SRCS ${SRCS} IPC_HLE/WII_IPC_HLE_Device_hid.cpp
HW/SI_GCAdapter.cpp)
endif(LIBUSB_FOUND)
set(LIBS ${LIBS} ${POLARSSL_LIBRARY})
if(WIN32)
set(SRCS ${SRCS} HW/BBA-TAP/TAP_Win32.cpp HW/WiimoteReal/IOWin.cpp)
set(SRCS ${SRCS} HW/BBA-TAP/TAP_Win32.cpp)
elseif(APPLE)
set(SRCS ${SRCS} HW/BBA-TAP/TAP_Apple.cpp HW/WiimoteReal/IOdarwin.mm)
set(LIBS ${LIBS}
${IOB_LIBRARY})
set(SRCS ${SRCS} HW/BBA-TAP/TAP_Apple.cpp)
elseif(UNIX)
set(SRCS ${SRCS} HW/BBA-TAP/TAP_Unix.cpp)
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux" AND BLUEZ_FOUND)
set(SRCS ${SRCS} HW/WiimoteReal/IONix.cpp)
set(LIBS ${LIBS} bluetooth)
else()
set(SRCS ${SRCS} HW/WiimoteReal/IODummy.cpp)
endif()
endif()
if(PORTAUDIO_FOUND)

View File

@ -169,7 +169,7 @@
<ClCompile Include="HW\WiimoteEmu\Encryption.cpp" />
<ClCompile Include="HW\WiimoteEmu\Speaker.cpp" />
<ClCompile Include="HW\WiimoteEmu\WiimoteEmu.cpp" />
<ClCompile Include="HW\WiimoteReal\IOWin.cpp" />
<ClCompile Include="HW\WiimoteReal\IOhidapi.cpp" />
<ClCompile Include="HW\WiimoteReal\WiimoteReal.cpp" />
<ClCompile Include="HW\WII_IPC.cpp" />
<ClCompile Include="HW\WiiSaveCrypted.cpp" />
@ -470,6 +470,9 @@
<ProjectReference Include="$(CoreDir)VideoCommon\VideoCommon.vcxproj">
<Project>{3de9ee35-3e91-4f27-a014-2866ad8c3fe3}</Project>
</ProjectReference>
<ProjectReference Include="$(ExternalsDir)hidapi\hidapi.vcxproj">
<Project>{549d32d8-1640-46f9-9d78-bae6eb0d723d}</Project>
</ProjectReference>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">

View File

@ -484,7 +484,7 @@
<ClCompile Include="HW\WiimoteEmu\WiimoteEmu.cpp">
<Filter>HW %28Flipper/Hollywood%29\Wiimote\Emu</Filter>
</ClCompile>
<ClCompile Include="HW\WiimoteReal\IOWin.cpp">
<ClCompile Include="HW\WiimoteReal\IOhidapi.cpp">
<Filter>HW %28Flipper/Hollywood%29\Wiimote\Real</Filter>
</ClCompile>
<ClCompile Include="HW\WiimoteReal\WiimoteReal.cpp">

View File

@ -1,31 +0,0 @@
// Copyright 2014 Dolphin Emulator Project
// Licensed under GPLv2
// Refer to the license.txt file included.
#include "Common/CommonTypes.h"
#include "Core/HW/WiimoteReal/WiimoteReal.h"
namespace WiimoteReal
{
WiimoteScanner::WiimoteScanner()
{}
WiimoteScanner::~WiimoteScanner()
{}
void WiimoteScanner::Update()
{}
void WiimoteScanner::FindWiimotes(std::vector<Wiimote*> & found_wiimotes, Wiimote* & found_board)
{
found_wiimotes.clear();
found_board = nullptr;
}
bool WiimoteScanner::IsReady() const
{
return false;
}
};

View File

@ -1,279 +0,0 @@
// Copyright 2014 Dolphin Emulator Project
// Licensed under GPLv2
// Refer to the license.txt file included.
#include <bluetooth/bluetooth.h>
#include <bluetooth/hci.h>
#include <bluetooth/hci_lib.h>
#include <bluetooth/l2cap.h>
#include "Common/Common.h"
#include "Core/HW/WiimoteReal/WiimoteReal.h"
namespace WiimoteReal
{
class WiimoteLinux final : public Wiimote
{
public:
WiimoteLinux(bdaddr_t bdaddr);
~WiimoteLinux() override;
protected:
bool ConnectInternal() override;
void DisconnectInternal() override;
bool IsConnected() const override;
void IOWakeup() override;
int IORead(u8* buf) override;
int IOWrite(u8 const* buf, size_t len) override;
private:
bdaddr_t m_bdaddr; // Bluetooth address
int m_cmd_sock; // Command socket
int m_int_sock; // Interrupt socket
int m_wakeup_pipe_w;
int m_wakeup_pipe_r;
};
WiimoteScanner::WiimoteScanner()
: m_want_wiimotes()
, device_id(-1)
, device_sock(-1)
{
// Get the id of the first bluetooth device.
device_id = hci_get_route(nullptr);
if (device_id < 0)
{
NOTICE_LOG(WIIMOTE, "Bluetooth not found.");
return;
}
// Create a socket to the device
device_sock = hci_open_dev(device_id);
if (device_sock < 0)
{
ERROR_LOG(WIIMOTE, "Unable to open bluetooth.");
return;
}
}
bool WiimoteScanner::IsReady() const
{
return device_sock > 0;
}
WiimoteScanner::~WiimoteScanner()
{
if (IsReady())
close(device_sock);
}
void WiimoteScanner::Update()
{}
void WiimoteScanner::FindWiimotes(std::vector<Wiimote*> & found_wiimotes, Wiimote* & found_board)
{
// supposedly 1.28 seconds
int const wait_len = 1;
int const max_infos = 255;
inquiry_info scan_infos[max_infos] = {};
auto* scan_infos_ptr = scan_infos;
found_board = nullptr;
// Scan for bluetooth devices
int const found_devices = hci_inquiry(device_id, wait_len, max_infos, nullptr, &scan_infos_ptr, IREQ_CACHE_FLUSH);
if (found_devices < 0)
{
ERROR_LOG(WIIMOTE, "Error searching for bluetooth devices.");
return;
}
DEBUG_LOG(WIIMOTE, "Found %i bluetooth device(s).", found_devices);
// Display discovered devices
for (int i = 0; i < found_devices; ++i)
{
ERROR_LOG(WIIMOTE, "found a device...");
// BT names are a maximum of 248 bytes apparently
char name[255] = {};
if (hci_read_remote_name(device_sock, &scan_infos[i].bdaddr, sizeof(name), name, 1000) < 0)
{
ERROR_LOG(WIIMOTE, "name request failed");
continue;
}
ERROR_LOG(WIIMOTE, "device name %s", name);
if (IsValidBluetoothName(name))
{
bool new_wiimote = true;
// TODO: do this
// Determine if this wiimote has already been found.
//for (int j = 0; j < MAX_WIIMOTES && new_wiimote; ++j)
//{
// if (wm[j] && bacmp(&scan_infos[i].bdaddr,&wm[j]->bdaddr) == 0)
// new_wiimote = false;
//}
if (new_wiimote)
{
// Found a new device
char bdaddr_str[18] = {};
ba2str(&scan_infos[i].bdaddr, bdaddr_str);
Wiimote* wm = new WiimoteLinux(scan_infos[i].bdaddr);
if (IsBalanceBoardName(name))
{
found_board = wm;
NOTICE_LOG(WIIMOTE, "Found balance board (%s).", bdaddr_str);
}
else
{
found_wiimotes.push_back(wm);
NOTICE_LOG(WIIMOTE, "Found wiimote (%s).", bdaddr_str);
}
}
}
}
}
WiimoteLinux::WiimoteLinux(bdaddr_t bdaddr) : Wiimote(), m_bdaddr(bdaddr)
{
m_cmd_sock = -1;
m_int_sock = -1;
int fds[2];
if (pipe(fds))
{
ERROR_LOG(WIIMOTE, "pipe failed");
abort();
}
m_wakeup_pipe_w = fds[1];
m_wakeup_pipe_r = fds[0];
}
WiimoteLinux::~WiimoteLinux()
{
Shutdown();
close(m_wakeup_pipe_w);
close(m_wakeup_pipe_r);
}
// Connect to a wiimote with a known address.
bool WiimoteLinux::ConnectInternal()
{
sockaddr_l2 addr = {};
addr.l2_family = AF_BLUETOOTH;
addr.l2_bdaddr = m_bdaddr;
addr.l2_cid = 0;
// Output channel
addr.l2_psm = htobs(WM_OUTPUT_CHANNEL);
if ((m_cmd_sock = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP)) == -1 ||
connect(m_cmd_sock, (sockaddr*)&addr, sizeof(addr)) < 0)
{
WARN_LOG(WIIMOTE, "Unable to open output socket to wiimote: %s", strerror(errno));
close(m_cmd_sock);
m_cmd_sock = -1;
return false;
}
// Input channel
addr.l2_psm = htobs(WM_INPUT_CHANNEL);
if ((m_int_sock = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP)) == -1 ||
connect(m_int_sock, (sockaddr*)&addr, sizeof(addr)) < 0)
{
WARN_LOG(WIIMOTE, "Unable to open input socket from wiimote: %s", strerror(errno));
close(m_int_sock);
close(m_cmd_sock);
m_int_sock = m_cmd_sock = -1;
return false;
}
return true;
}
void WiimoteLinux::DisconnectInternal()
{
close(m_cmd_sock);
close(m_int_sock);
m_cmd_sock = -1;
m_int_sock = -1;
}
bool WiimoteLinux::IsConnected() const
{
return m_cmd_sock != -1;// && int_sock != -1;
}
void WiimoteLinux::IOWakeup()
{
char c = 0;
if (write(m_wakeup_pipe_w, &c, 1) != 1)
{
ERROR_LOG(WIIMOTE, "Unable to write to wakeup pipe.");
}
}
// positive = read packet
// negative = didn't read packet
// zero = error
int WiimoteLinux::IORead(u8* buf)
{
// Block select for 1/2000th of a second
fd_set fds;
FD_ZERO(&fds);
FD_SET(m_int_sock, &fds);
FD_SET(m_wakeup_pipe_r, &fds);
if (select(m_int_sock + 1, &fds, nullptr, nullptr, nullptr) == -1)
{
ERROR_LOG(WIIMOTE, "Unable to select wiimote %i input socket.", m_index + 1);
return -1;
}
if (FD_ISSET(m_wakeup_pipe_r, &fds))
{
char c;
if (read(m_wakeup_pipe_r, &c, 1) != 1)
{
ERROR_LOG(WIIMOTE, "Unable to read from wakeup pipe.");
}
return -1;
}
if (!FD_ISSET(m_int_sock, &fds))
return -1;
// Read the pending message into the buffer
int r = read(m_int_sock, buf, MAX_PAYLOAD);
if (r == -1)
{
// Error reading data
ERROR_LOG(WIIMOTE, "Receiving data from wiimote %i.", m_index + 1);
if (errno == ENOTCONN)
{
// This can happen if the bluetooth dongle is disconnected
ERROR_LOG(WIIMOTE, "Bluetooth appears to be disconnected. "
"Wiimote %i will be disconnected.", m_index + 1);
}
r = 0;
}
return r;
}
int WiimoteLinux::IOWrite(u8 const* buf, size_t len)
{
return write(m_int_sock, buf, (int)len);
}
}; // WiimoteReal

View File

@ -1,954 +0,0 @@
// Copyright 2013 Dolphin Emulator Project
// Licensed under GPLv2
// Refer to the license.txt file included.
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <ctime>
#include <hidsdi.h>
#include <unordered_map>
#include <unordered_set>
#include <windows.h>
// The following Windows headers must be included AFTER windows.h.
#include <BluetoothAPIs.h> //NOLINT
#include <dbt.h> //NOLINT
#include <setupapi.h> //NOLINT
#include "Common/Common.h"
#include "Common/StringUtil.h"
#include "Core/HW/WiimoteReal/WiimoteReal.h"
//#define AUTHENTICATE_WIIMOTES
#define SHARE_WRITE_WIIMOTES
// Create func_t function pointer type and declare a nullptr-initialized static variable of that
// type named "pfunc".
#define DYN_FUNC_DECLARE(func) \
typedef decltype(&func) func ## _t; \
static func ## _t p ## func = nullptr;
DYN_FUNC_DECLARE(HidD_GetHidGuid);
DYN_FUNC_DECLARE(HidD_GetAttributes);
DYN_FUNC_DECLARE(HidD_SetOutputReport);
DYN_FUNC_DECLARE(HidD_GetProductString);
DYN_FUNC_DECLARE(BluetoothFindDeviceClose);
DYN_FUNC_DECLARE(BluetoothFindFirstDevice);
DYN_FUNC_DECLARE(BluetoothFindFirstRadio);
DYN_FUNC_DECLARE(BluetoothFindNextDevice);
DYN_FUNC_DECLARE(BluetoothFindNextRadio);
DYN_FUNC_DECLARE(BluetoothFindRadioClose);
DYN_FUNC_DECLARE(BluetoothGetRadioInfo);
DYN_FUNC_DECLARE(BluetoothRemoveDevice);
DYN_FUNC_DECLARE(BluetoothSetServiceState);
DYN_FUNC_DECLARE(BluetoothAuthenticateDeviceEx);
DYN_FUNC_DECLARE(BluetoothEnumerateInstalledServices);
#undef DYN_FUNC_DECLARE
static HINSTANCE s_hid_lib = nullptr;
static HINSTANCE s_bthprops_lib = nullptr;
static bool s_loaded_ok = false;
std::unordered_map<BTH_ADDR, std::time_t> g_connect_times;
#ifdef SHARE_WRITE_WIIMOTES
std::unordered_set<std::basic_string<TCHAR>> g_connected_wiimotes;
std::mutex g_connected_wiimotes_lock;
#endif
#define DYN_FUNC_UNLOAD(func) \
p ## func = nullptr;
// Attempt to load the function from the given module handle.
#define DYN_FUNC_LOAD(module, func) \
p ## func = ( func ## _t)::GetProcAddress(module, # func ); \
if (! p ## func ) \
{ \
return false; \
}
bool load_hid()
{
auto loader = [&]()
{
s_hid_lib = ::LoadLibrary(_T("hid.dll"));
if (!s_hid_lib)
{
return false;
}
DYN_FUNC_LOAD(s_hid_lib, HidD_GetHidGuid);
DYN_FUNC_LOAD(s_hid_lib, HidD_GetAttributes);
DYN_FUNC_LOAD(s_hid_lib, HidD_SetOutputReport);
DYN_FUNC_LOAD(s_hid_lib, HidD_GetProductString);
return true;
};
bool loaded_ok = loader();
if (!loaded_ok)
{
DYN_FUNC_UNLOAD(HidD_GetHidGuid);
DYN_FUNC_UNLOAD(HidD_GetAttributes);
DYN_FUNC_UNLOAD(HidD_SetOutputReport);
DYN_FUNC_UNLOAD(HidD_GetProductString);
if (s_hid_lib)
{
::FreeLibrary(s_hid_lib);
s_hid_lib = nullptr;
}
}
return loaded_ok;
}
bool load_bthprops()
{
auto loader = [&]()
{
s_bthprops_lib = ::LoadLibrary(_T("bthprops.cpl"));
if (!s_bthprops_lib)
{
return false;
}
DYN_FUNC_LOAD(s_bthprops_lib, BluetoothFindDeviceClose);
DYN_FUNC_LOAD(s_bthprops_lib, BluetoothFindFirstDevice);
DYN_FUNC_LOAD(s_bthprops_lib, BluetoothFindFirstRadio);
DYN_FUNC_LOAD(s_bthprops_lib, BluetoothFindNextDevice);
DYN_FUNC_LOAD(s_bthprops_lib, BluetoothFindNextRadio);
DYN_FUNC_LOAD(s_bthprops_lib, BluetoothFindRadioClose);
DYN_FUNC_LOAD(s_bthprops_lib, BluetoothGetRadioInfo);
DYN_FUNC_LOAD(s_bthprops_lib, BluetoothRemoveDevice);
DYN_FUNC_LOAD(s_bthprops_lib, BluetoothSetServiceState);
DYN_FUNC_LOAD(s_bthprops_lib, BluetoothAuthenticateDeviceEx);
DYN_FUNC_LOAD(s_bthprops_lib, BluetoothEnumerateInstalledServices);
return true;
};
bool loaded_ok = loader();
if (!loaded_ok)
{
DYN_FUNC_UNLOAD(BluetoothFindDeviceClose);
DYN_FUNC_UNLOAD(BluetoothFindFirstDevice);
DYN_FUNC_UNLOAD(BluetoothFindFirstRadio);
DYN_FUNC_UNLOAD(BluetoothFindNextDevice);
DYN_FUNC_UNLOAD(BluetoothFindNextRadio);
DYN_FUNC_UNLOAD(BluetoothFindRadioClose);
DYN_FUNC_UNLOAD(BluetoothGetRadioInfo);
DYN_FUNC_UNLOAD(BluetoothRemoveDevice);
DYN_FUNC_UNLOAD(BluetoothSetServiceState);
DYN_FUNC_UNLOAD(BluetoothAuthenticateDeviceEx);
DYN_FUNC_UNLOAD(BluetoothEnumerateInstalledServices);
if (s_bthprops_lib)
{
::FreeLibrary(s_bthprops_lib);
s_bthprops_lib = nullptr;
}
}
return loaded_ok;
}
#undef DYN_FUNC_LOAD
#undef DYN_FUNC_UNLOAD
inline void init_lib()
{
static bool initialized = false;
if (!initialized)
{
// Only try once
initialized = true;
// After these calls, we know all dynamically loaded APIs will either all be valid or
// all nullptr.
if (!load_hid() || !load_bthprops())
{
NOTICE_LOG(WIIMOTE,
"Failed to load bluetooth support libraries, wiimotes will not function");
return;
}
s_loaded_ok = true;
}
}
namespace WiimoteReal
{
class WiimoteWindows final : public Wiimote
{
public:
WiimoteWindows(const std::basic_string<TCHAR>& path);
~WiimoteWindows() override;
protected:
bool ConnectInternal() override;
void DisconnectInternal() override;
bool IsConnected() const override;
void IOWakeup() override;
int IORead(u8* buf) override;
int IOWrite(u8 const* buf, size_t len) override;
private:
std::basic_string<TCHAR> m_devicepath; // Unique wiimote reference
HANDLE m_dev_handle; // HID handle
OVERLAPPED m_hid_overlap_read; // Overlap handles
OVERLAPPED m_hid_overlap_write;
enum win_bt_stack_t m_stack; // Type of bluetooth stack to use
};
int _IOWrite(HANDLE &dev_handle, OVERLAPPED &hid_overlap_write, enum win_bt_stack_t &stack, const u8* buf, size_t len, DWORD* written);
int _IORead(HANDLE &dev_handle, OVERLAPPED &hid_overlap_read, u8* buf, int index);
template <typename T>
void ProcessWiimotes(bool new_scan, T& callback);
bool AttachWiimote(HANDLE hRadio, const BLUETOOTH_RADIO_INFO&, BLUETOOTH_DEVICE_INFO_STRUCT&);
void RemoveWiimote(BLUETOOTH_DEVICE_INFO_STRUCT&);
bool ForgetWiimote(BLUETOOTH_DEVICE_INFO_STRUCT&);
WiimoteScanner::WiimoteScanner()
: m_run_thread()
, m_want_wiimotes()
{
init_lib();
}
WiimoteScanner::~WiimoteScanner()
{
// TODO: what do we want here?
#if 0
ProcessWiimotes(false, [](HANDLE, BLUETOOTH_RADIO_INFO&, BLUETOOTH_DEVICE_INFO_STRUCT& btdi)
{
RemoveWiimote(btdi);
});
#endif
}
void WiimoteScanner::Update()
{
if (!s_loaded_ok)
return;
bool forgot_some = false;
ProcessWiimotes(false, [&](HANDLE, BLUETOOTH_RADIO_INFO&, BLUETOOTH_DEVICE_INFO_STRUCT& btdi)
{
forgot_some |= ForgetWiimote(btdi);
});
// Some hacks that allows disconnects to be detected before connections are handled
// workaround for wiimote 1 moving to slot 2 on temporary disconnect
if (forgot_some)
SLEEP(100);
}
// Find and connect wiimotes.
// Does not replace already found wiimotes even if they are disconnected.
// wm is an array of max_wiimotes wiimotes
// Returns the total number of found and connected wiimotes.
void WiimoteScanner::FindWiimotes(std::vector<Wiimote*> & found_wiimotes, Wiimote* & found_board)
{
if (!s_loaded_ok)
return;
ProcessWiimotes(true, [](HANDLE hRadio, const BLUETOOTH_RADIO_INFO& rinfo, BLUETOOTH_DEVICE_INFO_STRUCT& btdi)
{
ForgetWiimote(btdi);
AttachWiimote(hRadio, rinfo, btdi);
});
// Get the device id
GUID device_id;
pHidD_GetHidGuid(&device_id);
// Get all hid devices connected
HDEVINFO const device_info = SetupDiGetClassDevs(&device_id, nullptr, nullptr, (DIGCF_DEVICEINTERFACE | DIGCF_PRESENT));
SP_DEVICE_INTERFACE_DATA device_data;
device_data.cbSize = sizeof(device_data);
PSP_DEVICE_INTERFACE_DETAIL_DATA detail_data = nullptr;
for (int index = 0; SetupDiEnumDeviceInterfaces(device_info, nullptr, &device_id, index, &device_data); ++index)
{
// Get the size of the data block required
DWORD len;
SetupDiGetDeviceInterfaceDetail(device_info, &device_data, nullptr, 0, &len, nullptr);
detail_data = (PSP_DEVICE_INTERFACE_DETAIL_DATA)malloc(len);
detail_data->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
// Query the data for this device
if (SetupDiGetDeviceInterfaceDetail(device_info, &device_data, detail_data, len, nullptr, nullptr))
{
std::basic_string<TCHAR> device_path(detail_data->DevicePath);
Wiimote* wm = new WiimoteWindows(device_path);
bool real_wiimote = false, is_bb = false;
CheckDeviceType(device_path, real_wiimote, is_bb);
if (is_bb)
{
found_board = wm;
}
else if (real_wiimote)
{
found_wiimotes.push_back(wm);
}
else
{
delete wm;
}
}
free(detail_data);
}
SetupDiDestroyDeviceInfoList(device_info);
// Don't mind me, just a random sleep to fix stuff on Windows
//if (!wiimotes.empty())
// SLEEP(2000);
}
int CheckDeviceType_Write(HANDLE &dev_handle, const u8* buf, size_t size, int attempts)
{
OVERLAPPED hid_overlap_write = OVERLAPPED();
hid_overlap_write.hEvent = CreateEvent(nullptr, true, false, nullptr);
enum win_bt_stack_t stack = MSBT_STACK_UNKNOWN;
DWORD written = 0;
for (; attempts>0; --attempts)
{
if (_IOWrite(dev_handle, hid_overlap_write, stack, buf, size, &written))
break;
}
CloseHandle(hid_overlap_write.hEvent);
return written;
}
int CheckDeviceType_Read(HANDLE &dev_handle, u8* buf, int attempts)
{
OVERLAPPED hid_overlap_read = OVERLAPPED();
hid_overlap_read.hEvent = CreateEvent(nullptr, true, false, nullptr);
int read = 0;
for (; attempts>0; --attempts)
{
read = _IORead(dev_handle, hid_overlap_read, buf, 1);
if (read > 0)
break;
}
CloseHandle(hid_overlap_read.hEvent);
return read;
}
// A convoluted way of checking if a device is a Wii Balance Board and if it is a connectible Wiimote.
// Because nothing on Windows should be easy.
// (We can't seem to easily identify the bluetooth device an HID device belongs to...)
void WiimoteScanner::CheckDeviceType(std::basic_string<TCHAR> &devicepath, bool &real_wiimote, bool &is_bb)
{
real_wiimote = false;
is_bb = false;
#ifdef SHARE_WRITE_WIIMOTES
std::lock_guard<std::mutex> lk(g_connected_wiimotes_lock);
if (g_connected_wiimotes.count(devicepath) != 0)
return;
#endif
HANDLE dev_handle = CreateFile(devicepath.c_str(),
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
nullptr, OPEN_EXISTING, FILE_FLAG_OVERLAPPED,
nullptr);
if (dev_handle == INVALID_HANDLE_VALUE)
return;
// enable to only check for official nintendo wiimotes/bb's
bool check_vidpid = false;
HIDD_ATTRIBUTES attrib;
attrib.Size = sizeof(attrib);
if (!check_vidpid ||
(pHidD_GetAttributes(dev_handle, &attrib) &&
(attrib.VendorID == 0x057e) &&
(attrib.ProductID == 0x0306)))
{
// max_cycles insures we are never stuck here due to bad coding...
int max_cycles = 20;
u8 buf[MAX_PAYLOAD] = {0};
u8 const req_status_report[] = {WM_SET_REPORT | WM_BT_OUTPUT, WM_REQUEST_STATUS, 0};
// The new way to initialize the extension is by writing 0x55 to 0x(4)A400F0, then writing 0x00 to 0x(4)A400FB
// 52 16 04 A4 00 F0 01 55
// 52 16 04 A4 00 FB 01 00
u8 const disable_enc_pt1_report[MAX_PAYLOAD] = {WM_SET_REPORT | WM_BT_OUTPUT, WM_WRITE_DATA, 0x04, 0xa4, 0x00, 0xf0, 0x01, 0x55};
u8 const disable_enc_pt2_report[MAX_PAYLOAD] = {WM_SET_REPORT | WM_BT_OUTPUT, WM_WRITE_DATA, 0x04, 0xa4, 0x00, 0xfb, 0x01, 0x00};
CheckDeviceType_Write(dev_handle,
disable_enc_pt1_report,
sizeof(disable_enc_pt1_report),
1);
CheckDeviceType_Write(dev_handle,
disable_enc_pt2_report,
sizeof(disable_enc_pt2_report),
1);
int rc = CheckDeviceType_Write(dev_handle,
req_status_report,
sizeof(req_status_report),
1);
while (rc > 0 && --max_cycles > 0)
{
if ((rc = CheckDeviceType_Read(dev_handle, buf, 1)) <= 0)
{
// DEBUG_LOG(WIIMOTE, "CheckDeviceType: Read failed...");
break;
}
switch (buf[1])
{
case WM_STATUS_REPORT:
{
real_wiimote = true;
// DEBUG_LOG(WIIMOTE, "CheckDeviceType: Got Status Report");
wm_status_report * wsr = (wm_status_report*)&buf[2];
if (wsr->extension)
{
// Wiimote with extension, we ask it what kind.
u8 read_ext[MAX_PAYLOAD] = {0};
read_ext[0] = WM_SET_REPORT | WM_BT_OUTPUT;
read_ext[1] = WM_READ_DATA;
// Extension type register.
*(u32*)&read_ext[2] = Common::swap32(0x4a400fa);
// Size.
*(u16*)&read_ext[6] = Common::swap16(6);
rc = CheckDeviceType_Write(dev_handle, read_ext, 8, 1);
}
else
{
// Normal Wiimote, exit while and be happy.
rc = -1;
}
break;
}
case WM_ACK_DATA:
{
real_wiimote = true;
//wm_acknowledge * wm = (wm_acknowledge*)&buf[2];
//DEBUG_LOG(WIIMOTE, "CheckDeviceType: Got Ack Error: %X ReportID: %X", wm->errorID, wm->reportID);
break;
}
case WM_READ_DATA_REPLY:
{
// DEBUG_LOG(WIIMOTE, "CheckDeviceType: Got Data Reply");
wm_read_data_reply * wrdr
= (wm_read_data_reply*)&buf[2];
// Check if it has returned what we asked.
if (Common::swap16(wrdr->address) == 0x00fa)
{
real_wiimote = true;
// 0x020420A40000ULL means balance board.
u64 ext_type = (*(u64*)&wrdr->data[0]);
// DEBUG_LOG(WIIMOTE,
// "CheckDeviceType: GOT EXT TYPE %llX",
// ext_type);
is_bb = (ext_type == 0x020420A40000ULL);
}
else
{
ERROR_LOG(WIIMOTE,
"CheckDeviceType: GOT UNREQUESTED ADDRESS %X",
Common::swap16(wrdr->address));
}
// force end
rc = -1;
break;
}
default:
{
// We let read try again incase there is another packet waiting.
// DEBUG_LOG(WIIMOTE, "CheckDeviceType: GOT UNKNOWN REPLY: %X", buf[1]);
break;
}
}
}
}
CloseHandle(dev_handle);
}
bool WiimoteScanner::IsReady() const
{
if (!s_loaded_ok)
{
return false;
}
// TODO: don't search for a radio each time
BLUETOOTH_FIND_RADIO_PARAMS radioParam;
radioParam.dwSize = sizeof(radioParam);
HANDLE hRadio;
HBLUETOOTH_RADIO_FIND hFindRadio = pBluetoothFindFirstRadio(&radioParam, &hRadio);
if (nullptr != hFindRadio)
{
pBluetoothFindRadioClose(hFindRadio);
return true;
}
else
{
return false;
}
}
// Connect to a wiimote with a known device path.
bool WiimoteWindows::ConnectInternal()
{
if (IsConnected())
return false;
#ifdef SHARE_WRITE_WIIMOTES
std::lock_guard<std::mutex> lk(g_connected_wiimotes_lock);
if (g_connected_wiimotes.count(m_devicepath) != 0)
return false;
auto const open_flags = FILE_SHARE_READ | FILE_SHARE_WRITE;
#else
// Having no FILE_SHARE_WRITE disallows us from connecting to the same wiimote twice.
// (And disallows using wiimotes in use by other programs)
// This is what "WiiYourself" does.
// Apparently this doesn't work for everyone. It might be their fault.
auto const open_flags = FILE_SHARE_READ;
#endif
m_dev_handle = CreateFile(m_devicepath.c_str(),
GENERIC_READ | GENERIC_WRITE, open_flags,
nullptr, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, nullptr);
if (m_dev_handle == INVALID_HANDLE_VALUE)
{
m_dev_handle = 0;
return false;
}
#if 0
TCHAR name[128] = {};
pHidD_GetProductString(dev_handle, name, 128);
//ERROR_LOG(WIIMOTE, "Product string: %s", TStrToUTF8(name).c_str());
if (!IsValidBluetoothName(TStrToUTF8(name)))
{
CloseHandle(dev_handle);
dev_handle = 0;
return false;
}
#endif
#if 0
HIDD_ATTRIBUTES attr;
attr.Size = sizeof(attr);
if (!pHidD_GetAttributes(dev_handle, &attr))
{
CloseHandle(dev_handle);
dev_handle = 0;
return false;
}
#endif
// TODO: thread isn't started here now, do this elsewhere
// This isn't as drastic as it sounds, since the process in which the threads
// reside is normal priority. Needed for keeping audio reports at a decent rate
/*
if (!SetThreadPriority(m_wiimote_thread.native_handle(), THREAD_PRIORITY_TIME_CRITICAL))
{
ERROR_LOG(WIIMOTE, "Failed to set Wiimote thread priority");
}
*/
#ifdef SHARE_WRITE_WIIMOTES
g_connected_wiimotes.insert(m_devicepath);
#endif
return true;
}
void WiimoteWindows::DisconnectInternal()
{
if (!IsConnected())
return;
CloseHandle(m_dev_handle);
m_dev_handle = 0;
#ifdef SHARE_WRITE_WIIMOTES
std::lock_guard<std::mutex> lk(g_connected_wiimotes_lock);
g_connected_wiimotes.erase(m_devicepath);
#endif
}
WiimoteWindows::WiimoteWindows(const std::basic_string<TCHAR>& path) : m_devicepath(path)
{
m_dev_handle = 0;
m_stack = MSBT_STACK_UNKNOWN;
m_hid_overlap_read = OVERLAPPED();
m_hid_overlap_read.hEvent = CreateEvent(nullptr, true, false, nullptr);
m_hid_overlap_write = OVERLAPPED();
m_hid_overlap_write.hEvent = CreateEvent(nullptr, true, false, nullptr);
}
WiimoteWindows::~WiimoteWindows()
{
Shutdown();
CloseHandle(m_hid_overlap_read.hEvent);
CloseHandle(m_hid_overlap_write.hEvent);
}
bool WiimoteWindows::IsConnected() const
{
return m_dev_handle != 0;
}
// positive = read packet
// negative = didn't read packet
// zero = error
int _IORead(HANDLE &dev_handle, OVERLAPPED &hid_overlap_read, u8* buf, int index)
{
// Add data report indicator byte (here, 0xa1)
buf[0] = 0xa1;
// Used below for a warning
buf[1] = 0;
DWORD bytes = 0;
ResetEvent(hid_overlap_read.hEvent);
if (!ReadFile(dev_handle, buf + 1, MAX_PAYLOAD - 1, &bytes, &hid_overlap_read))
{
auto const read_err = GetLastError();
if (ERROR_IO_PENDING == read_err)
{
auto const wait_result = WaitForSingleObject(hid_overlap_read.hEvent, INFINITE);
// In case the event was signalled by IOWakeup before the read completed, cancel it.
CancelIo(dev_handle);
if (WAIT_FAILED == wait_result)
{
WARN_LOG(WIIMOTE, "A wait error occurred on reading from Wiimote %i.", index + 1);
}
if (!GetOverlappedResult(dev_handle, &hid_overlap_read, &bytes, FALSE))
{
auto const overlapped_err = GetLastError();
if (ERROR_OPERATION_ABORTED == overlapped_err)
{
// It was.
return -1;
}
WARN_LOG(WIIMOTE, "GetOverlappedResult error %d on Wiimote %i.", overlapped_err, index + 1);
return 0;
}
}
else
{
WARN_LOG(WIIMOTE, "ReadFile error %d on Wiimote %i.", read_err, index + 1);
return 0;
}
}
return bytes + 1;
}
void WiimoteWindows::IOWakeup()
{
SetEvent(m_hid_overlap_read.hEvent);
}
// positive = read packet
// negative = didn't read packet
// zero = error
int WiimoteWindows::IORead(u8* buf)
{
return _IORead(m_dev_handle, m_hid_overlap_read, buf, m_index);
}
int _IOWrite(HANDLE &dev_handle, OVERLAPPED &hid_overlap_write, enum win_bt_stack_t &stack, const u8* buf, size_t len, DWORD* written)
{
switch (stack)
{
case MSBT_STACK_UNKNOWN:
{
// Try to auto-detect the stack type
stack = MSBT_STACK_BLUESOLEIL;
if (_IOWrite(dev_handle, hid_overlap_write, stack, buf, len, written))
return 1;
stack = MSBT_STACK_MS;
if (_IOWrite(dev_handle, hid_overlap_write, stack, buf, len, written))
return 1;
stack = MSBT_STACK_UNKNOWN;
break;
}
case MSBT_STACK_MS:
{
auto result = pHidD_SetOutputReport(dev_handle, const_cast<u8*>(buf) + 1, (ULONG)(len - 1));
//FlushFileBuffers(dev_handle);
if (!result)
{
auto err = GetLastError();
if (err == 121)
{
// Semaphore timeout
NOTICE_LOG(WIIMOTE, "WiimoteIOWrite[MSBT_STACK_MS]: Unable to send data to the Wiimote");
}
else if (err != 0x1F) // Some third-party adapters (DolphinBar) use this
// error code to signal the absence of a WiiMote
// linked to the HID device.
{
WARN_LOG(WIIMOTE, "IOWrite[MSBT_STACK_MS]: ERROR: %08x", err);
}
}
if (written)
*written = (result ? (DWORD)len : 0);
return result;
}
case MSBT_STACK_BLUESOLEIL:
{
u8 big_buf[MAX_PAYLOAD];
if (len < MAX_PAYLOAD)
{
std::copy(buf, buf + len, big_buf);
std::fill(big_buf + len, big_buf + MAX_PAYLOAD, 0);
buf = big_buf;
}
ResetEvent(hid_overlap_write.hEvent);
DWORD bytes = 0;
if (WriteFile(dev_handle, buf + 1, MAX_PAYLOAD - 1, &bytes, &hid_overlap_write))
{
// If the number of written bytes is requested, block until we can provide
// this information to the called.
if (written)
{
auto const wait_result = WaitForSingleObject(hid_overlap_write.hEvent, WIIMOTE_DEFAULT_TIMEOUT);
if (WAIT_TIMEOUT == wait_result)
{
WARN_LOG(WIIMOTE, "_IOWrite: A timeout occurred on writing to Wiimote.");
CancelIo(dev_handle);
*written = 0;
}
else if (WAIT_FAILED == wait_result)
{
WARN_LOG(WIIMOTE, "_IOWrite: A wait error occurred on writing to Wiimote.");
CancelIo(dev_handle);
*written = 0;
}
else if (!GetOverlappedResult(dev_handle, &hid_overlap_write, written, TRUE))
*written = 0;
}
return 1;
}
else
{
auto const err = GetLastError();
if (ERROR_IO_PENDING == err)
{
CancelIo(dev_handle);
}
return 0;
}
}
}
return 0;
}
int WiimoteWindows::IOWrite(const u8* buf, size_t len)
{
return _IOWrite(m_dev_handle, m_hid_overlap_write, m_stack, buf, len, nullptr);
}
// invokes callback for each found wiimote bluetooth device
template <typename T>
void ProcessWiimotes(bool new_scan, T& callback)
{
BLUETOOTH_DEVICE_SEARCH_PARAMS srch;
srch.dwSize = sizeof(srch);
srch.fReturnAuthenticated = true;
srch.fReturnRemembered = true;
// Does not filter properly somehow, so we need to do an additional check on
// fConnected BT Devices
srch.fReturnConnected = true;
srch.fReturnUnknown = true;
srch.fIssueInquiry = new_scan;
// multiple of 1.28 seconds
srch.cTimeoutMultiplier = 2;
BLUETOOTH_FIND_RADIO_PARAMS radioParam;
radioParam.dwSize = sizeof(radioParam);
HANDLE hRadio;
// TODO: save radio(s) in the WiimoteScanner constructor?
// Enumerate BT radios
HBLUETOOTH_RADIO_FIND hFindRadio = pBluetoothFindFirstRadio(&radioParam, &hRadio);
while (hFindRadio)
{
BLUETOOTH_RADIO_INFO radioInfo;
radioInfo.dwSize = sizeof(radioInfo);
auto const rinfo_result = pBluetoothGetRadioInfo(hRadio, &radioInfo);
if (ERROR_SUCCESS == rinfo_result)
{
srch.hRadio = hRadio;
BLUETOOTH_DEVICE_INFO btdi;
btdi.dwSize = sizeof(btdi);
// Enumerate BT devices
HBLUETOOTH_DEVICE_FIND hFindDevice = pBluetoothFindFirstDevice(&srch, &btdi);
while (hFindDevice)
{
// btdi.szName is sometimes missing it's content - it's a bt feature..
DEBUG_LOG(WIIMOTE, "Authenticated %i connected %i remembered %i ",
btdi.fAuthenticated, btdi.fConnected, btdi.fRemembered);
if (IsValidBluetoothName(UTF16ToUTF8(btdi.szName)))
{
callback(hRadio, radioInfo, btdi);
}
if (false == pBluetoothFindNextDevice(hFindDevice, &btdi))
{
pBluetoothFindDeviceClose(hFindDevice);
hFindDevice = nullptr;
}
}
}
if (false == pBluetoothFindNextRadio(hFindRadio, &hRadio))
{
pBluetoothFindRadioClose(hFindRadio);
hFindRadio = nullptr;
}
}
}
void RemoveWiimote(BLUETOOTH_DEVICE_INFO_STRUCT& btdi)
{
//if (btdi.fConnected)
{
if (SUCCEEDED(pBluetoothRemoveDevice(&btdi.Address)))
{
NOTICE_LOG(WIIMOTE, "Removed BT Device", GetLastError());
}
}
}
bool AttachWiimote(HANDLE hRadio, const BLUETOOTH_RADIO_INFO& radio_info, BLUETOOTH_DEVICE_INFO_STRUCT& btdi)
{
// We don't want "remembered" devices.
// SetServiceState will just fail with them..
if (!btdi.fConnected && !btdi.fRemembered)
{
auto const& wm_addr = btdi.Address.rgBytes;
NOTICE_LOG(WIIMOTE, "Found Wiimote (%02x:%02x:%02x:%02x:%02x:%02x). Enabling HID service.",
wm_addr[0], wm_addr[1], wm_addr[2], wm_addr[3], wm_addr[4], wm_addr[5]);
#if defined(AUTHENTICATE_WIIMOTES)
// Authenticate
auto const& radio_addr = radio_info.address.rgBytes;
// FIXME Not sure this usage of OOB_DATA_INFO is correct...
BLUETOOTH_OOB_DATA_INFO oob_data_info = { 0 };
memcpy(&oob_data_info.C[0], &radio_addr[0], sizeof(WCHAR) * 6);
const DWORD auth_result = pBluetoothAuthenticateDeviceEx(nullptr, hRadio, &btdi,
&oob_data_info, MITMProtectionNotDefined);
if (ERROR_SUCCESS != auth_result)
{
ERROR_LOG(WIIMOTE, "AttachWiimote: BluetoothAuthenticateDeviceEx returned %08x", auth_result);
}
DWORD pcServices = 16;
GUID guids[16];
// If this is not done, the Wii device will not remember the pairing
const DWORD srv_result = pBluetoothEnumerateInstalledServices(hRadio, &btdi, &pcServices, guids);
if (ERROR_SUCCESS != srv_result)
{
ERROR_LOG(WIIMOTE, "AttachWiimote: BluetoothEnumerateInstalledServices returned %08x", srv_result);
}
#endif
// Activate service
const DWORD hr = pBluetoothSetServiceState(hRadio, &btdi,
&HumanInterfaceDeviceServiceClass_UUID, BLUETOOTH_SERVICE_ENABLE);
g_connect_times[btdi.Address.ullLong] = std::time(nullptr);
if (FAILED(hr))
{
ERROR_LOG(WIIMOTE, "AttachWiimote: BluetoothSetServiceState returned %08x", hr);
}
else
{
return true;
}
}
return false;
}
// Removes remembered non-connected devices
bool ForgetWiimote(BLUETOOTH_DEVICE_INFO_STRUCT& btdi)
{
if (!btdi.fConnected && btdi.fRemembered)
{
// Time to avoid RemoveDevice after SetServiceState.
// Sometimes SetServiceState takes a while..
auto const avoid_forget_seconds = 5.0;
auto pair_time = g_connect_times.find(btdi.Address.ullLong);
if (pair_time == g_connect_times.end() ||
std::difftime(time(nullptr), pair_time->second) >= avoid_forget_seconds)
{
// Make Windows forget about device so it will re-find it if visible.
// This is also required to detect a disconnect for some reason..
NOTICE_LOG(WIIMOTE, "Removing remembered Wiimote.");
pBluetoothRemoveDevice(&btdi.Address);
return true;
}
}
return false;
}
};

View File

@ -1,380 +0,0 @@
#define BLUETOOTH_VERSION_USE_CURRENT
#include "Common/Common.h"
#include "Core/HW/WiimoteReal/WiimoteReal.h"
@interface SearchBT: NSObject {
@public
unsigned int maxDevices;
bool done;
}
@end
@interface ConnectBT: NSObject {}
@end
namespace WiimoteReal
{
class WiimoteDarwin final : public Wiimote
{
public:
WiimoteDarwin(IOBluetoothDevice* device);
~WiimoteDarwin() override;
// These are not protected/private because ConnectBT needs them.
void DisconnectInternal() override;
IOBluetoothDevice* m_btd;
unsigned char* m_input;
int m_inputlen;
protected:
bool ConnectInternal() override;
bool IsConnected() const override;
void IOWakeup() override;
int IORead(u8* buf) override;
int IOWrite(u8 const* buf, size_t len) override;
void EnablePowerAssertionInternal() override;
void DisablePowerAssertionInternal() override;
private:
IOBluetoothL2CAPChannel* m_ichan;
IOBluetoothL2CAPChannel* m_cchan;
bool m_connected;
CFRunLoopRef m_wiimote_thread_run_loop;
IOPMAssertionID m_pm_assertion;
};
WiimoteScanner::WiimoteScanner()
: m_run_thread()
, m_want_wiimotes()
{}
WiimoteScanner::~WiimoteScanner()
{}
void WiimoteScanner::Update()
{}
void WiimoteScanner::FindWiimotes(std::vector<Wiimote*> & found_wiimotes, Wiimote* & found_board)
{
// TODO: find the device in the constructor and save it for later
IOBluetoothHostController *bth;
IOBluetoothDeviceInquiry *bti;
SearchBT *sbt;
NSEnumerator *en;
found_board = nullptr;
bth = [[IOBluetoothHostController alloc] init];
if ([bth addressAsString] == nil)
{
WARN_LOG(WIIMOTE, "No bluetooth host controller");
[bth release];
return;
}
sbt = [[SearchBT alloc] init];
sbt->maxDevices = 32;
bti = [[IOBluetoothDeviceInquiry alloc] init];
[bti setDelegate: sbt];
[bti setInquiryLength: 2];
if ([bti start] != kIOReturnSuccess)
{
ERROR_LOG(WIIMOTE, "Unable to do bluetooth discovery");
[bth release];
[sbt release];
return;
}
do
{
CFRunLoopRun();
}
while (!sbt->done);
int found_devices = [[bti foundDevices] count];
if (found_devices)
NOTICE_LOG(WIIMOTE, "Found %i bluetooth devices", found_devices);
en = [[bti foundDevices] objectEnumerator];
for (int i = 0; i < found_devices; i++)
{
IOBluetoothDevice *dev = [en nextObject];
if (!IsValidBluetoothName([[dev name] UTF8String]))
continue;
Wiimote* wm = new WiimoteDarwin([dev retain]);
if (IsBalanceBoardName([[dev name] UTF8String]))
{
found_board = wm;
}
else
{
found_wiimotes.push_back(wm);
}
}
[bth release];
[bti release];
[sbt release];
}
bool WiimoteScanner::IsReady() const
{
// TODO: only return true when a BT device is present
return true;
}
WiimoteDarwin::WiimoteDarwin(IOBluetoothDevice* device) : m_btd(device)
{
m_inputlen = 0;
m_connected = false;
m_wiimote_thread_run_loop = nullptr;
m_pm_assertion = kIOPMNullAssertionID;
}
WiimoteDarwin::~WiimoteDarwin()
{
Shutdown();
if (m_wiimote_thread_run_loop)
{
CFRelease(m_wiimote_thread_run_loop);
m_wiimote_thread_run_loop = nullptr;
}
[m_btd release];
m_btd = nil;
DisablePowerAssertionInternal();
}
// Connect to a wiimote with a known address.
bool WiimoteDarwin::ConnectInternal()
{
if (IsConnected())
return false;
ConnectBT *cbt = [[ConnectBT alloc] init];
m_cchan = m_ichan = nil;
IOReturn ret = [m_btd openConnection];
if (ret)
{
ERROR_LOG(WIIMOTE, "Unable to open Bluetooth connection to wiimote %i: %x",
m_index + 1, ret);
[cbt release];
return false;
}
ret = [m_btd openL2CAPChannelSync: &m_cchan
withPSM: kBluetoothL2CAPPSMHIDControl delegate: cbt];
if (ret)
{
ERROR_LOG(WIIMOTE, "Unable to open control channel for wiimote %i: %x",
m_index + 1, ret);
goto bad;
}
// Apple docs claim:
// "The L2CAP channel object is already retained when this function returns
// success; the channel must be released when the caller is done with it."
// But without this, the channels get over-autoreleased, even though the
// refcounting behavior here is clearly correct.
[m_cchan retain];
ret = [m_btd openL2CAPChannelSync: &m_ichan
withPSM: kBluetoothL2CAPPSMHIDInterrupt delegate: cbt];
if (ret)
{
WARN_LOG(WIIMOTE, "Unable to open interrupt channel for wiimote %i: %x",
m_index + 1, ret);
goto bad;
}
[m_ichan retain];
NOTICE_LOG(WIIMOTE, "Connected to wiimote %i at %s",
m_index + 1, [[m_btd addressString] UTF8String]);
m_connected = true;
[cbt release];
m_wiimote_thread_run_loop = (CFRunLoopRef) CFRetain(CFRunLoopGetCurrent());
return true;
bad:
DisconnectInternal();
[cbt release];
return false;
}
// Disconnect a wiimote.
void WiimoteDarwin::DisconnectInternal()
{
[m_ichan closeChannel];
[m_ichan release];
m_ichan = nil;
[m_cchan closeChannel];
[m_cchan release];
m_cchan = nil;
[m_btd closeConnection];
if (!IsConnected())
return;
NOTICE_LOG(WIIMOTE, "Disconnecting wiimote %i", m_index + 1);
m_connected = false;
}
bool WiimoteDarwin::IsConnected() const
{
return m_connected;
}
void WiimoteDarwin::IOWakeup()
{
if (m_wiimote_thread_run_loop)
{
CFRunLoopStop(m_wiimote_thread_run_loop);
}
}
int WiimoteDarwin::IORead(unsigned char *buf)
{
m_input = buf;
m_inputlen = -1;
CFRunLoopRun();
return m_inputlen;
}
int WiimoteDarwin::IOWrite(const unsigned char *buf, size_t len)
{
IOReturn ret;
if (!IsConnected())
return 0;
ret = [m_ichan writeAsync: const_cast<void*>((void *)buf) length: (int)len refcon: nil];
if (ret == kIOReturnSuccess)
return len;
else
return 0;
}
void WiimoteDarwin::EnablePowerAssertionInternal()
{
if (m_pm_assertion == kIOPMNullAssertionID)
{
if (IOReturn ret = IOPMAssertionCreateWithName(kIOPMAssertPreventUserIdleDisplaySleep, kIOPMAssertionLevelOn, CFSTR("Dolphin Wiimote activity"), &m_pm_assertion))
ERROR_LOG(WIIMOTE, "Could not create power management assertion: %08x", ret);
}
}
void WiimoteDarwin::DisablePowerAssertionInternal()
{
if (m_pm_assertion != kIOPMNullAssertionID)
{
if (IOReturn ret = IOPMAssertionRelease(m_pm_assertion))
ERROR_LOG(WIIMOTE, "Could not release power management assertion: %08x", ret);
}
}
} // namespace
@implementation SearchBT
- (void) deviceInquiryComplete: (IOBluetoothDeviceInquiry *) sender
error: (IOReturn) error
aborted: (BOOL) aborted
{
done = true;
CFRunLoopStop(CFRunLoopGetCurrent());
}
- (void) deviceInquiryDeviceFound: (IOBluetoothDeviceInquiry *) sender
device: (IOBluetoothDevice *) device
{
NOTICE_LOG(WIIMOTE, "Discovered bluetooth device at %s: %s",
[[device addressString] UTF8String],
[[device name] UTF8String]);
if ([[sender foundDevices] count] == maxDevices)
[sender stop];
}
@end
@implementation ConnectBT
- (void) l2capChannelData: (IOBluetoothL2CAPChannel *) l2capChannel
data: (unsigned char *) data
length: (NSUInteger) length
{
IOBluetoothDevice *device = [l2capChannel device];
WiimoteReal::WiimoteDarwin *wm = nullptr;
std::lock_guard<std::recursive_mutex> lk(WiimoteReal::g_refresh_lock);
for (int i = 0; i < MAX_WIIMOTES; i++)
{
if (WiimoteReal::g_wiimotes[i] == nullptr)
continue;
wm = static_cast<WiimoteReal::WiimoteDarwin*>(WiimoteReal::g_wiimotes[i]);
if ([device isEqual: wm->m_btd] != TRUE)
wm = nullptr;
}
if (wm == nullptr) {
ERROR_LOG(WIIMOTE, "Received packet for unknown wiimote");
return;
}
if (length > MAX_PAYLOAD) {
WARN_LOG(WIIMOTE, "Dropping packet for wiimote %i, too large",
wm->m_index + 1);
return;
}
if (wm->m_inputlen != -1) {
WARN_LOG(WIIMOTE, "Dropping packet for wiimote %i, queue full",
wm->m_index + 1);
return;
}
memcpy(wm->m_input, data, length);
wm->m_inputlen = length;
CFRunLoopStop(CFRunLoopGetCurrent());
}
- (void) l2capChannelClosed: (IOBluetoothL2CAPChannel *) l2capChannel
{
IOBluetoothDevice *device = [l2capChannel device];
WiimoteReal::WiimoteDarwin *wm = nullptr;
std::lock_guard<std::recursive_mutex> lk(WiimoteReal::g_refresh_lock);
for (int i = 0; i < MAX_WIIMOTES; i++)
{
if (WiimoteReal::g_wiimotes[i] == nullptr)
continue;
wm = static_cast<WiimoteReal::WiimoteDarwin*>(WiimoteReal::g_wiimotes[i]);
if ([device isEqual: wm->m_btd] != TRUE)
wm = nullptr;
}
if (wm == nullptr) {
ERROR_LOG(WIIMOTE, "Channel for unknown wiimote was closed");
return;
}
WARN_LOG(WIIMOTE, "Lost channel to wiimote %i", wm->m_index + 1);
wm->DisconnectInternal();
}
@end

View File

@ -0,0 +1,150 @@
// Copyright 2014 Dolphin Emulator Project
// Licensed under GPLv2
// Refer to the license.txt file included.
#include <hidapi.h>
#include "Common/Common.h"
#include "Core/HW/WiimoteReal/WiimoteReal.h"
namespace WiimoteReal
{
class WiimoteHidapi final : public Wiimote
{
public:
WiimoteHidapi(char* device_path);
~WiimoteHidapi() override;
protected:
bool ConnectInternal() override;
void DisconnectInternal() override;
bool IsConnected() const override;
void IOWakeup() override;
int IORead(u8* buf) override;
int IOWrite(u8 const* buf, size_t len) override;
private:
std::string m_device_path;
hid_device* m_handle;
};
WiimoteScanner::WiimoteScanner()
: m_want_wiimotes()
{
if (hid_init() == -1)
{
ERROR_LOG(WIIMOTE, "Failed to initialize hidapi.");
}
}
bool WiimoteScanner::IsReady() const
{
return true;
}
WiimoteScanner::~WiimoteScanner()
{
if (hid_exit() == -1)
{
ERROR_LOG(WIIMOTE, "Failed to clean up hidapi.");
}
}
void WiimoteScanner::Update()
{}
void WiimoteScanner::FindWiimotes(std::vector<Wiimote*>& found_wiimotes, Wiimote*& found_board)
{
// Search for both old and new Wiimotes.
for (uint16_t product_id : {0x0306, 0x0330})
{
hid_device_info* list = hid_enumerate(0x057e, product_id);
hid_device_info* item = list;
while (item)
{
NOTICE_LOG(WIIMOTE, "Found Wiimote at %s: %ls %ls", item->path, item->manufacturer_string, item->product_string);
Wiimote* wiimote = new WiimoteHidapi(item->path);
found_wiimotes.push_back(wiimote);
item = item->next;
}
hid_free_enumeration(list);
}
}
WiimoteHidapi::WiimoteHidapi(char* device_path) : m_device_path(device_path), m_handle(nullptr)
{}
WiimoteHidapi::~WiimoteHidapi()
{
Shutdown();
}
// Connect to a wiimote with a known address.
bool WiimoteHidapi::ConnectInternal()
{
m_handle = hid_open_path(m_device_path.c_str());
if (!m_handle)
{
ERROR_LOG(WIIMOTE, "Could not connect to Wiimote at \"%s\". "
"Do you have permission to access the device?", m_device_path.c_str());
}
return !!m_handle;
}
void WiimoteHidapi::DisconnectInternal()
{
hid_close(m_handle);
m_handle = nullptr;
}
bool WiimoteHidapi::IsConnected() const
{
return !!m_handle;
}
void WiimoteHidapi::IOWakeup()
{}
// positive = read packet
// negative = didn't read packet
// zero = error
int WiimoteHidapi::IORead(u8* buf)
{
int timeout = 200; // ms
int result = hid_read_timeout(m_handle, buf + 1, MAX_PAYLOAD - 1, timeout);
// TODO: If and once we use hidapi across plaforms, change our internal API to clean up this mess.
if (result == -1)
{
ERROR_LOG(WIIMOTE, "Failed to read from %s.", m_device_path.c_str());
result = 0;
}
else if (result == 0)
{
result = -1;
}
else
{
buf[0] = WM_SET_REPORT | WM_BT_INPUT;
result += 1;
}
return result;
}
int WiimoteHidapi::IOWrite(u8 const* buf, size_t len)
{
_dbg_assert_(WIIMOTE, buf[0] == (WM_SET_REPORT | WM_BT_OUTPUT));
int result = hid_write(m_handle, buf + 1, len - 1);
if (result == -1)
{
ERROR_LOG(WIIMOTE, "Failed to write to %s.", m_device_path.c_str());
result = 0;
}
else
{
result += 1;
}
return result;
}
}; // WiimoteReal

View File

@ -135,13 +135,6 @@ private:
volatile bool m_run_thread;
volatile bool m_want_wiimotes;
volatile bool m_want_bb;
#if defined(_WIN32)
void CheckDeviceType(std::basic_string<TCHAR> &devicepath, bool &real_wiimote, bool &is_bb);
#elif defined(__linux__) && HAVE_BLUEZ
int device_id;
int device_sock;
#endif
};
extern std::recursive_mutex g_refresh_lock;

View File

@ -4,22 +4,6 @@
#pragma once
#ifdef _WIN32
#include <windows.h>
#elif defined(__APPLE__)
// Work around an Apple bug: for some reason, IOBluetooth.h errors on
// inclusion in Mavericks, but only in Objective-C++ C++11 mode. I filed
// this as <rdar://15312520>; in the meantime...
#import <Foundation/Foundation.h>
#undef NS_ENUM_AVAILABLE
#define NS_ENUM_AVAILABLE(...)
// end hack
#import <IOBluetooth/IOBluetooth.h>
#include <IOKit/pwr_mgt/IOPMLib.h>
#elif defined(__linux__) && HAVE_BLUEZ
#include <bluetooth/bluetooth.h>
#endif
// Wiimote internal codes
// Communication channels