Merge pull request #3245 from jloehr/RealWiimote-Windows-Fix
[RFC]Real Wiimote Windows "-TR" Fix
This commit is contained in:
commit
8acf8cf4d0
|
@ -12,6 +12,10 @@
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
// The following Windows headers must be included AFTER windows.h.
|
// The following Windows headers must be included AFTER windows.h.
|
||||||
#include <BluetoothAPIs.h> //NOLINT
|
#include <BluetoothAPIs.h> //NOLINT
|
||||||
|
#include <Cfgmgr32.h> //NOLINT
|
||||||
|
// initguid.h must be included before Devpkey.h
|
||||||
|
#include <initguid.h> //NOLINT
|
||||||
|
#include <Devpkey.h> //NOLINT
|
||||||
#include <dbt.h> //NOLINT
|
#include <dbt.h> //NOLINT
|
||||||
#include <setupapi.h> //NOLINT
|
#include <setupapi.h> //NOLINT
|
||||||
|
|
||||||
|
@ -192,7 +196,7 @@ namespace WiimoteReal
|
||||||
class WiimoteWindows final : public Wiimote
|
class WiimoteWindows final : public Wiimote
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
WiimoteWindows(const std::basic_string<TCHAR>& path);
|
WiimoteWindows(const std::basic_string<TCHAR>& path, WinWriteMethod initial_write_method);
|
||||||
~WiimoteWindows() override;
|
~WiimoteWindows() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
@ -208,11 +212,11 @@ private:
|
||||||
HANDLE m_dev_handle; // HID handle
|
HANDLE m_dev_handle; // HID handle
|
||||||
OVERLAPPED m_hid_overlap_read; // Overlap handles
|
OVERLAPPED m_hid_overlap_read; // Overlap handles
|
||||||
OVERLAPPED m_hid_overlap_write;
|
OVERLAPPED m_hid_overlap_write;
|
||||||
enum win_bt_stack_t m_stack; // Type of Bluetooth stack to use
|
WinWriteMethod m_write_method; // Type of Write Method 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 IOWrite(HANDLE &dev_handle, OVERLAPPED &hid_overlap_write, enum WinWriteMethod &stack, const u8* buf, size_t len, DWORD* written);
|
||||||
int _IORead(HANDLE &dev_handle, OVERLAPPED &hid_overlap_read, u8* buf, int index);
|
int IORead(HANDLE &dev_handle, OVERLAPPED &hid_overlap_read, u8* buf, int index);
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
void ProcessWiimotes(bool new_scan, T& callback);
|
void ProcessWiimotes(bool new_scan, T& callback);
|
||||||
|
@ -255,6 +259,100 @@ void WiimoteScanner::Update()
|
||||||
Common::SleepCurrentThread(100);
|
Common::SleepCurrentThread(100);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Moves up one node in the device tree and returns its device info data along with an info set only including that device for further processing
|
||||||
|
// See https://msdn.microsoft.com/en-us/library/windows/hardware/ff549417(v=vs.85).aspx
|
||||||
|
static bool GetParentDevice(const DEVINST &child_device_instance, HDEVINFO *parent_device_info, PSP_DEVINFO_DATA parent_device_data)
|
||||||
|
{
|
||||||
|
ULONG status;
|
||||||
|
ULONG problem_number;
|
||||||
|
CONFIGRET result;
|
||||||
|
|
||||||
|
// Check if that device instance has device node present
|
||||||
|
result = CM_Get_DevNode_Status(&status, &problem_number, child_device_instance, 0);
|
||||||
|
if (result != CR_SUCCESS)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
DEVINST parent_device;
|
||||||
|
|
||||||
|
// Get the device instance of the parent
|
||||||
|
result = CM_Get_Parent(&parent_device, child_device_instance, 0);
|
||||||
|
if (result != CR_SUCCESS)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<WCHAR> parent_device_id(MAX_DEVICE_ID_LEN);;
|
||||||
|
|
||||||
|
// Get the device id of the parent, required to open the device info
|
||||||
|
result = CM_Get_Device_ID(parent_device, parent_device_id.data(), (ULONG)parent_device_id.size(), 0);
|
||||||
|
if (result != CR_SUCCESS)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new empty device info set for the device info data
|
||||||
|
(*parent_device_info) = SetupDiCreateDeviceInfoList(nullptr, nullptr);
|
||||||
|
|
||||||
|
// Open the device info data of the parent and put it in the emtpy info set
|
||||||
|
if (!SetupDiOpenDeviceInfo((*parent_device_info), parent_device_id.data(), nullptr, 0, parent_device_data))
|
||||||
|
{
|
||||||
|
SetupDiDestroyDeviceInfoList(parent_device_info);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::wstring GetDeviceProperty(const HDEVINFO &device_info, const PSP_DEVINFO_DATA device_data, const DEVPROPKEY *requested_property)
|
||||||
|
{
|
||||||
|
DWORD required_size = 0;
|
||||||
|
DEVPROPTYPE device_property_type;
|
||||||
|
|
||||||
|
SetupDiGetDeviceProperty(device_info, device_data, requested_property, &device_property_type, nullptr, 0, &required_size, 0);
|
||||||
|
|
||||||
|
std::vector<BYTE> unicode_buffer(required_size, 0);
|
||||||
|
|
||||||
|
BOOL result = SetupDiGetDeviceProperty(device_info, device_data, requested_property, &device_property_type, unicode_buffer.data(), required_size, nullptr, 0);
|
||||||
|
if (!result)
|
||||||
|
{
|
||||||
|
return std::wstring();
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::wstring((PWCHAR)unicode_buffer.data());
|
||||||
|
}
|
||||||
|
|
||||||
|
// The enumerated device nodes/instances are "empty" PDO's that act as interfaces for the HID Class Driver.
|
||||||
|
// Since those PDO's normaly don't have a FDO and therefore no driver loaded, we need to move one device node up in the device tree.
|
||||||
|
// Then check the provider of the device driver, which will be "Microsoft" in case of the default HID Class Driver
|
||||||
|
// or "TOSHIBA" in case of the Toshiba Bluetooth Stack, because it provides its own Class Driver.
|
||||||
|
static bool CheckForToshibaStack(const DEVINST &hid_interface_device_instance)
|
||||||
|
{
|
||||||
|
HDEVINFO parent_device_info = nullptr;
|
||||||
|
SP_DEVINFO_DATA parent_device_data = {};
|
||||||
|
parent_device_data.cbSize = sizeof(SP_DEVINFO_DATA);
|
||||||
|
|
||||||
|
if (GetParentDevice(hid_interface_device_instance, &parent_device_info, &parent_device_data))
|
||||||
|
{
|
||||||
|
std::wstring class_driver_provider = GetDeviceProperty(parent_device_info, &parent_device_data, &DEVPKEY_Device_DriverProvider);
|
||||||
|
|
||||||
|
SetupDiDestroyDeviceInfoList(parent_device_info);
|
||||||
|
|
||||||
|
return (class_driver_provider == L"TOSHIBA");
|
||||||
|
}
|
||||||
|
|
||||||
|
DEBUG_LOG(WIIMOTE, "Unable to detect class driver provider!");
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static WinWriteMethod GetInitialWriteMethod(bool IsUsingToshibaStack)
|
||||||
|
{
|
||||||
|
// Currently Toshiba Bluetooth Stack needs the Output buffer to be the size of the largest output report
|
||||||
|
return (IsUsingToshibaStack ? WWM_WRITE_FILE_LARGEST_REPORT_SIZE : WWM_WRITE_FILE_ACTUAL_REPORT_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
// Find and connect Wiimotes.
|
// Find and connect Wiimotes.
|
||||||
// Does not replace already found Wiimotes even if they are disconnected.
|
// Does not replace already found Wiimotes even if they are disconnected.
|
||||||
// wm is an array of max_wiimotes Wiimotes
|
// wm is an array of max_wiimotes Wiimotes
|
||||||
|
@ -277,7 +375,7 @@ void WiimoteScanner::FindWiimotes(std::vector<Wiimote*> & found_wiimotes, Wiimot
|
||||||
// Get all hid devices connected
|
// Get all hid devices connected
|
||||||
HDEVINFO const device_info = SetupDiGetClassDevs(&device_id, nullptr, nullptr, (DIGCF_DEVICEINTERFACE | DIGCF_PRESENT));
|
HDEVINFO const device_info = SetupDiGetClassDevs(&device_id, nullptr, nullptr, (DIGCF_DEVICEINTERFACE | DIGCF_PRESENT));
|
||||||
|
|
||||||
SP_DEVICE_INTERFACE_DATA device_data;
|
SP_DEVICE_INTERFACE_DATA device_data = {};
|
||||||
device_data.cbSize = sizeof(device_data);
|
device_data.cbSize = sizeof(device_data);
|
||||||
PSP_DEVICE_INTERFACE_DETAIL_DATA detail_data = nullptr;
|
PSP_DEVICE_INTERFACE_DETAIL_DATA detail_data = nullptr;
|
||||||
|
|
||||||
|
@ -289,25 +387,33 @@ void WiimoteScanner::FindWiimotes(std::vector<Wiimote*> & found_wiimotes, Wiimot
|
||||||
detail_data = (PSP_DEVICE_INTERFACE_DETAIL_DATA)malloc(len);
|
detail_data = (PSP_DEVICE_INTERFACE_DETAIL_DATA)malloc(len);
|
||||||
detail_data->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
|
detail_data->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
|
||||||
|
|
||||||
|
SP_DEVINFO_DATA device_info_data = {};
|
||||||
|
device_info_data.cbSize = sizeof(SP_DEVINFO_DATA);
|
||||||
|
|
||||||
// Query the data for this device
|
// Query the data for this device
|
||||||
if (SetupDiGetDeviceInterfaceDetail(device_info, &device_data, detail_data, len, nullptr, nullptr))
|
if (SetupDiGetDeviceInterfaceDetail(device_info, &device_data, detail_data, len, nullptr, &device_info_data))
|
||||||
{
|
{
|
||||||
std::basic_string<TCHAR> device_path(detail_data->DevicePath);
|
std::basic_string<TCHAR> device_path(detail_data->DevicePath);
|
||||||
Wiimote* wm = new WiimoteWindows(device_path);
|
bool real_wiimote = false;
|
||||||
bool real_wiimote = false, is_bb = false;
|
bool is_bb = false;
|
||||||
|
bool IsUsingToshibaStack = CheckForToshibaStack(device_info_data.DevInst);
|
||||||
|
|
||||||
CheckDeviceType(device_path, real_wiimote, is_bb);
|
WinWriteMethod write_method = GetInitialWriteMethod(IsUsingToshibaStack);
|
||||||
if (is_bb)
|
|
||||||
|
CheckDeviceType(device_path, write_method, real_wiimote, is_bb);
|
||||||
|
|
||||||
|
if (real_wiimote)
|
||||||
{
|
{
|
||||||
found_board = wm;
|
Wiimote* wm = new WiimoteWindows(device_path, write_method);
|
||||||
}
|
|
||||||
else if (real_wiimote)
|
if (is_bb)
|
||||||
{
|
{
|
||||||
found_wiimotes.push_back(wm);
|
found_board = wm;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
delete wm;
|
found_wiimotes.push_back(wm);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -317,17 +423,16 @@ void WiimoteScanner::FindWiimotes(std::vector<Wiimote*> & found_wiimotes, Wiimot
|
||||||
SetupDiDestroyDeviceInfoList(device_info);
|
SetupDiDestroyDeviceInfoList(device_info);
|
||||||
}
|
}
|
||||||
|
|
||||||
int CheckDeviceType_Write(HANDLE &dev_handle, const u8* buf, size_t size, int attempts)
|
int CheckDeviceType_Write(HANDLE &dev_handle, WinWriteMethod &write_method, const u8* buf, size_t size, int attempts)
|
||||||
{
|
{
|
||||||
OVERLAPPED hid_overlap_write = OVERLAPPED();
|
OVERLAPPED hid_overlap_write = OVERLAPPED();
|
||||||
hid_overlap_write.hEvent = CreateEvent(nullptr, true, false, nullptr);
|
hid_overlap_write.hEvent = CreateEvent(nullptr, true, false, nullptr);
|
||||||
enum win_bt_stack_t stack = MSBT_STACK_UNKNOWN;
|
|
||||||
|
|
||||||
DWORD written = 0;
|
DWORD written = 0;
|
||||||
|
|
||||||
for (; attempts>0; --attempts)
|
for (; attempts>0; --attempts)
|
||||||
{
|
{
|
||||||
if (_IOWrite(dev_handle, hid_overlap_write, stack, buf, size, &written))
|
if (IOWrite(dev_handle, hid_overlap_write, write_method, buf, size, &written))
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -343,7 +448,7 @@ int CheckDeviceType_Read(HANDLE &dev_handle, u8* buf, int attempts)
|
||||||
int read = 0;
|
int read = 0;
|
||||||
for (; attempts>0; --attempts)
|
for (; attempts>0; --attempts)
|
||||||
{
|
{
|
||||||
read = _IORead(dev_handle, hid_overlap_read, buf, 1);
|
read = IORead(dev_handle, hid_overlap_read, buf, 1);
|
||||||
if (read > 0)
|
if (read > 0)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -356,7 +461,7 @@ int CheckDeviceType_Read(HANDLE &dev_handle, u8* buf, int attempts)
|
||||||
// A convoluted way of checking if a device is a Wii Balance Board and if it is a connectible Wiimote.
|
// 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.
|
// Because nothing on Windows should be easy.
|
||||||
// (We can't seem to easily identify the Bluetooth device an HID device belongs to...)
|
// (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)
|
void WiimoteScanner::CheckDeviceType(std::basic_string<TCHAR> &devicepath, WinWriteMethod &write_method, bool &real_wiimote, bool &is_bb)
|
||||||
{
|
{
|
||||||
real_wiimote = false;
|
real_wiimote = false;
|
||||||
is_bb = false;
|
is_bb = false;
|
||||||
|
@ -395,15 +500,18 @@ void WiimoteScanner::CheckDeviceType(std::basic_string<TCHAR> &devicepath, bool
|
||||||
u8 const disable_enc_pt2_report[MAX_PAYLOAD] = {WM_SET_REPORT | WM_BT_OUTPUT, WM_WRITE_DATA, 0x04, 0xa4, 0x00, 0xfb, 0x01, 0x00};
|
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,
|
CheckDeviceType_Write(dev_handle,
|
||||||
|
write_method,
|
||||||
disable_enc_pt1_report,
|
disable_enc_pt1_report,
|
||||||
sizeof(disable_enc_pt1_report),
|
sizeof(disable_enc_pt1_report),
|
||||||
1);
|
1);
|
||||||
CheckDeviceType_Write(dev_handle,
|
CheckDeviceType_Write(dev_handle,
|
||||||
|
write_method,
|
||||||
disable_enc_pt2_report,
|
disable_enc_pt2_report,
|
||||||
sizeof(disable_enc_pt2_report),
|
sizeof(disable_enc_pt2_report),
|
||||||
1);
|
1);
|
||||||
|
|
||||||
int rc = CheckDeviceType_Write(dev_handle,
|
int rc = CheckDeviceType_Write(dev_handle,
|
||||||
|
write_method,
|
||||||
req_status_report,
|
req_status_report,
|
||||||
sizeof(req_status_report),
|
sizeof(req_status_report),
|
||||||
1);
|
1);
|
||||||
|
@ -434,7 +542,7 @@ void WiimoteScanner::CheckDeviceType(std::basic_string<TCHAR> &devicepath, bool
|
||||||
*(u32*)&read_ext[2] = Common::swap32(0x4a400fa);
|
*(u32*)&read_ext[2] = Common::swap32(0x4a400fa);
|
||||||
// Size.
|
// Size.
|
||||||
*(u16*)&read_ext[6] = Common::swap16(6);
|
*(u16*)&read_ext[6] = Common::swap16(6);
|
||||||
rc = CheckDeviceType_Write(dev_handle, read_ext, 8, 1);
|
rc = CheckDeviceType_Write(dev_handle, write_method, read_ext, 8, 1);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -600,10 +708,10 @@ void WiimoteWindows::DisconnectInternal()
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
WiimoteWindows::WiimoteWindows(const std::basic_string<TCHAR>& path) : m_devicepath(path)
|
WiimoteWindows::WiimoteWindows(const std::basic_string<TCHAR>& path, WinWriteMethod initial_write_method)
|
||||||
|
: m_devicepath(path), m_write_method(initial_write_method)
|
||||||
{
|
{
|
||||||
m_dev_handle = nullptr;
|
m_dev_handle = nullptr;
|
||||||
m_stack = MSBT_STACK_UNKNOWN;
|
|
||||||
|
|
||||||
m_hid_overlap_read = OVERLAPPED();
|
m_hid_overlap_read = OVERLAPPED();
|
||||||
m_hid_overlap_read.hEvent = CreateEvent(nullptr, true, false, nullptr);
|
m_hid_overlap_read.hEvent = CreateEvent(nullptr, true, false, nullptr);
|
||||||
|
@ -627,7 +735,7 @@ bool WiimoteWindows::IsConnected() const
|
||||||
// positive = read packet
|
// positive = read packet
|
||||||
// negative = didn't read packet
|
// negative = didn't read packet
|
||||||
// zero = error
|
// zero = error
|
||||||
int _IORead(HANDLE &dev_handle, OVERLAPPED &hid_overlap_read, u8* buf, int index)
|
int IORead(HANDLE &dev_handle, OVERLAPPED &hid_overlap_read, u8* buf, int index)
|
||||||
{
|
{
|
||||||
// Add data report indicator byte (here, 0xa1)
|
// Add data report indicator byte (here, 0xa1)
|
||||||
buf[0] = 0xa1;
|
buf[0] = 0xa1;
|
||||||
|
@ -642,29 +750,26 @@ int _IORead(HANDLE &dev_handle, OVERLAPPED &hid_overlap_read, u8* buf, int index
|
||||||
|
|
||||||
if (ERROR_IO_PENDING == read_err)
|
if (ERROR_IO_PENDING == read_err)
|
||||||
{
|
{
|
||||||
auto const wait_result = WaitForSingleObject(hid_overlap_read.hEvent, INFINITE);
|
if (!GetOverlappedResult(dev_handle, &hid_overlap_read, &bytes, TRUE))
|
||||||
|
|
||||||
// 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();
|
auto const overlapped_err = GetLastError();
|
||||||
|
|
||||||
|
// In case it was aborted by someone else
|
||||||
if (ERROR_OPERATION_ABORTED == overlapped_err)
|
if (ERROR_OPERATION_ABORTED == overlapped_err)
|
||||||
{
|
{
|
||||||
// It was.
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
WARN_LOG(WIIMOTE, "GetOverlappedResult error %d on Wiimote %i.", overlapped_err, index + 1);
|
WARN_LOG(WIIMOTE, "GetOverlappedResult error %d on Wiimote %i.", overlapped_err, index + 1);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
// If IOWakeup sets the event so GetOverlappedResult returns prematurely, but the request is still pending
|
||||||
|
else if (hid_overlap_read.Internal == STATUS_PENDING)
|
||||||
|
{
|
||||||
|
// Don't forget to cancel it.
|
||||||
|
CancelIo(dev_handle);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -687,100 +792,136 @@ void WiimoteWindows::IOWakeup()
|
||||||
// zero = error
|
// zero = error
|
||||||
int WiimoteWindows::IORead(u8* buf)
|
int WiimoteWindows::IORead(u8* buf)
|
||||||
{
|
{
|
||||||
return _IORead(m_dev_handle, m_hid_overlap_read, buf, m_index);
|
return WiimoteReal::IORead(m_dev_handle, m_hid_overlap_read, buf, m_index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int IOWritePerSetOutputReport(HANDLE &dev_handle, const u8* buf, size_t len, DWORD* written)
|
||||||
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)
|
BOOLEAN result = pHidD_SetOutputReport(dev_handle, const_cast<u8*>(buf) + 1, (ULONG)(len - 1));
|
||||||
|
if (!result)
|
||||||
{
|
{
|
||||||
case MSBT_STACK_UNKNOWN:
|
DWORD err = GetLastError();
|
||||||
|
if (err == 121)
|
||||||
{
|
{
|
||||||
// Try to auto-detect the stack type
|
// Semaphore timeout
|
||||||
stack = MSBT_STACK_BLUESOLEIL;
|
NOTICE_LOG(WIIMOTE, "IOWrite[WWM_SET_OUTPUT_REPORT]: Unable to send data to the Wiimote");
|
||||||
if (_IOWrite(dev_handle, hid_overlap_write, stack, buf, len, written))
|
}
|
||||||
return 1;
|
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[WWM_SET_OUTPUT_REPORT]: Error: %08x", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
stack = MSBT_STACK_MS;
|
if (written)
|
||||||
if (_IOWrite(dev_handle, hid_overlap_write, stack, buf, len, written))
|
{
|
||||||
return 1;
|
*written = (result ? (DWORD)len : 0);
|
||||||
|
}
|
||||||
|
|
||||||
stack = MSBT_STACK_UNKNOWN;
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int IOWritePerWriteFile(HANDLE &dev_handle, OVERLAPPED &hid_overlap_write, WinWriteMethod &write_method, const u8* buf, size_t len, DWORD* written)
|
||||||
|
{
|
||||||
|
DWORD bytes_written;
|
||||||
|
LPCVOID write_buffer = buf + 1;
|
||||||
|
DWORD bytes_to_write = (DWORD)(len - 1);
|
||||||
|
|
||||||
|
u8 resized_buffer[MAX_PAYLOAD];
|
||||||
|
|
||||||
|
// Resize the buffer, if the underlying HID Class driver needs the buffer to be the size of HidCaps.OuputReportSize
|
||||||
|
// In case of Wiimote HidCaps.OuputReportSize is 22 Byte.
|
||||||
|
// This is currently needed by the Toshiba Bluetooth Stack.
|
||||||
|
if ((write_method == WWM_WRITE_FILE_LARGEST_REPORT_SIZE) && (MAX_PAYLOAD > len))
|
||||||
|
{
|
||||||
|
std::copy(buf, buf + len, resized_buffer);
|
||||||
|
std::fill(resized_buffer + len, resized_buffer + MAX_PAYLOAD, 0);
|
||||||
|
write_buffer = resized_buffer + 1;
|
||||||
|
bytes_to_write = MAX_PAYLOAD - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResetEvent(hid_overlap_write.hEvent);
|
||||||
|
BOOLEAN result = WriteFile(dev_handle, write_buffer, bytes_to_write, &bytes_written, &hid_overlap_write);
|
||||||
|
if (!result)
|
||||||
|
{
|
||||||
|
const DWORD error = GetLastError();
|
||||||
|
|
||||||
|
switch (error)
|
||||||
|
{
|
||||||
|
case ERROR_INVALID_USER_BUFFER:
|
||||||
|
INFO_LOG(WIIMOTE, "IOWrite[WWM_WRITE_FILE]: Falling back to SetOutputReport");
|
||||||
|
write_method = WWM_SET_OUTPUT_REPORT;
|
||||||
|
return IOWritePerSetOutputReport(dev_handle, buf, len, written);
|
||||||
|
case ERROR_IO_PENDING:
|
||||||
|
// Pending is no error!
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
WARN_LOG(WIIMOTE, "IOWrite[WWM_WRITE_FILE]: Error on WriteFile: %08x", error);
|
||||||
|
CancelIo(dev_handle);
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
case MSBT_STACK_MS:
|
}
|
||||||
|
|
||||||
|
if (written)
|
||||||
|
{
|
||||||
|
*written = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for completion
|
||||||
|
DWORD wait_result = WaitForSingleObject(hid_overlap_write.hEvent, WIIMOTE_DEFAULT_TIMEOUT);
|
||||||
|
|
||||||
|
if (WAIT_TIMEOUT == wait_result)
|
||||||
|
{
|
||||||
|
WARN_LOG(WIIMOTE, "IOWrite[WWM_WRITE_FILE]: A timeout occurred on writing to Wiimote.");
|
||||||
|
CancelIo(dev_handle);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
else if (WAIT_FAILED == wait_result)
|
||||||
|
{
|
||||||
|
WARN_LOG(WIIMOTE, "IOWrite[WWM_WRITE_FILE]: A wait error occurred on writing to Wiimote.");
|
||||||
|
CancelIo(dev_handle);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (written)
|
||||||
|
{
|
||||||
|
if (!GetOverlappedResult(dev_handle, &hid_overlap_write, written, TRUE))
|
||||||
{
|
{
|
||||||
auto result = pHidD_SetOutputReport(dev_handle, const_cast<u8*>(buf) + 1, (ULONG)(len - 1));
|
*written = 0;
|
||||||
//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);
|
return 1;
|
||||||
DWORD bytes = 0;
|
}
|
||||||
if (WriteFile(dev_handle, buf + 1, MAX_PAYLOAD - 1, &bytes, &hid_overlap_write))
|
|
||||||
{
|
// As of https://msdn.microsoft.com/en-us/library/windows/hardware/ff543402(v=vs.85).aspx, WriteFile is the preferred method
|
||||||
// If the number of written bytes is requested, block until we can provide
|
// to send output reports to the HID. WriteFile sends an IRP_MJ_WRITE to the HID Class Driver (https://msdn.microsoft.com/en-us/library/windows/hardware/ff543402(v=vs.85).aspx).
|
||||||
// this information to the called.
|
// https://msdn.microsoft.com/en-us/library/windows/hardware/ff541027(v=vs.85).aspx & https://msdn.microsoft.com/en-us/library/windows/hardware/ff543402(v=vs.85).aspx
|
||||||
if (written)
|
// state that the used buffer shall be the size of HidCaps.OutputReportSize (the largest output report).
|
||||||
{
|
// However as it seems only the Toshiba Bluetooth Stack, which provides its own HID Class Driver, as well as the HID Class Driver
|
||||||
auto const wait_result = WaitForSingleObject(hid_overlap_write.hEvent, WIIMOTE_DEFAULT_TIMEOUT);
|
// on Windows 7 enforce this requirement. Whereas on Windows 8/8.1/10 the buffer size can be the actual used report size.
|
||||||
if (WAIT_TIMEOUT == wait_result)
|
// On Windows 7 when sending a smaller report to the device all bytes of the largest report are sent, which results in
|
||||||
{
|
// an error on the Wiimote. Toshiba Bluetooth Stack in contrast only sends the neccessary bytes of the report to the device.
|
||||||
WARN_LOG(WIIMOTE, "_IOWrite: A timeout occurred on writing to Wiimote.");
|
// Therefore it is not possible to use WriteFile on Windows 7 to send data to the Wiimote and the fallback
|
||||||
CancelIo(dev_handle);
|
// to HidP_SetOutputReport is implemented, which in turn does not support "-TR" Wiimotes.
|
||||||
*written = 0;
|
// As to why on the later Windows' WriteFile or the HID Class Driver doesn't follow the documentation, it may be a bug or a feature.
|
||||||
}
|
// This leads to the following:
|
||||||
else if (WAIT_FAILED == wait_result)
|
// - Toshiba Bluetooth Stack: Use WriteFile with resized output buffer
|
||||||
{
|
// - Windows Default HID Class: Try WriteFile with actual output buffer (will work in Win8/8.1/10)
|
||||||
WARN_LOG(WIIMOTE, "_IOWrite: A wait error occurred on writing to Wiimote.");
|
// - When WriteFile fails, fallback to HidP_SetOutputReport (for Win7)
|
||||||
CancelIo(dev_handle);
|
// Besides the documentation, WriteFile shall be the preferred method to send data, because it seems to use the Bluetooth Interrupt/Data Channel,
|
||||||
*written = 0;
|
// whereas SetOutputReport uses the Control Channel. This leads to the advantage, that "-TR" Wiimotes work with WriteFile
|
||||||
}
|
// as they don't accept output reports via the Control Channel.
|
||||||
else if (!GetOverlappedResult(dev_handle, &hid_overlap_write, written, TRUE))
|
int IOWrite(HANDLE &dev_handle, OVERLAPPED &hid_overlap_write, WinWriteMethod &write_method, const u8* buf, size_t len, DWORD* written)
|
||||||
*written = 0;
|
{
|
||||||
}
|
switch (write_method)
|
||||||
return 1;
|
{
|
||||||
}
|
case WWM_WRITE_FILE_LARGEST_REPORT_SIZE:
|
||||||
else
|
case WWM_WRITE_FILE_ACTUAL_REPORT_SIZE:
|
||||||
{
|
return IOWritePerWriteFile(dev_handle, hid_overlap_write, write_method, buf, len, written);
|
||||||
auto const err = GetLastError();
|
case WWM_SET_OUTPUT_REPORT:
|
||||||
if (ERROR_IO_PENDING == err)
|
return IOWritePerSetOutputReport(dev_handle, buf, len, written);
|
||||||
{
|
|
||||||
CancelIo(dev_handle);
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -788,7 +929,7 @@ int _IOWrite(HANDLE &dev_handle, OVERLAPPED &hid_overlap_write, enum win_bt_stac
|
||||||
|
|
||||||
int WiimoteWindows::IOWrite(const u8* buf, size_t len)
|
int WiimoteWindows::IOWrite(const u8* buf, size_t len)
|
||||||
{
|
{
|
||||||
return _IOWrite(m_dev_handle, m_hid_overlap_write, m_stack, buf, len, nullptr);
|
return WiimoteReal::IOWrite(m_dev_handle, m_hid_overlap_write, m_write_method, buf, len, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
// invokes callback for each found Wiimote Bluetooth device
|
// invokes callback for each found Wiimote Bluetooth device
|
||||||
|
|
|
@ -142,7 +142,7 @@ private:
|
||||||
std::atomic<bool> m_want_bb {false};
|
std::atomic<bool> m_want_bb {false};
|
||||||
|
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
void CheckDeviceType(std::basic_string<TCHAR> &devicepath, bool &real_wiimote, bool &is_bb);
|
void CheckDeviceType(std::basic_string<TCHAR> &devicepath, WinWriteMethod &write_method, bool &real_wiimote, bool &is_bb);
|
||||||
#elif defined(__linux__) && HAVE_BLUEZ
|
#elif defined(__linux__) && HAVE_BLUEZ
|
||||||
int device_id;
|
int device_id;
|
||||||
int device_sock;
|
int device_sock;
|
||||||
|
|
|
@ -53,11 +53,11 @@
|
||||||
#define WIIMOTE_DEFAULT_TIMEOUT 1000
|
#define WIIMOTE_DEFAULT_TIMEOUT 1000
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
// Available bluetooth stacks for Windows.
|
// Different methods to send data Wiimote on Windows depending on OS and Bluetooth Stack
|
||||||
enum win_bt_stack_t
|
enum WinWriteMethod
|
||||||
{
|
{
|
||||||
MSBT_STACK_UNKNOWN,
|
WWM_WRITE_FILE_LARGEST_REPORT_SIZE,
|
||||||
MSBT_STACK_MS,
|
WWM_WRITE_FILE_ACTUAL_REPORT_SIZE,
|
||||||
MSBT_STACK_BLUESOLEIL
|
WWM_SET_OUTPUT_REPORT
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
Loading…
Reference in New Issue