mirror of
https://github.com/cemu-project/Cemu.git
synced 2025-07-04 05:51:19 +12:00
866 lines
28 KiB
C++
866 lines
28 KiB
C++
#include "Cafe/OS/common/OSCommon.h"
|
|
#include "Cafe/HW/Espresso/PPCCallback.h"
|
|
#include <bitset>
|
|
#include "nsyshid.h"
|
|
|
|
#if BOOST_OS_WINDOWS
|
|
|
|
#include <setupapi.h>
|
|
#include <initguid.h>
|
|
#include <hidsdi.h>
|
|
|
|
#include "Cafe/OS/libs/coreinit/coreinit_Thread.h"
|
|
|
|
#pragma comment(lib,"Setupapi.lib")
|
|
#pragma comment(lib,"hid.lib")
|
|
|
|
DEFINE_GUID(GUID_DEVINTERFACE_HID, 0x4D1E55B2L, 0xF16F, 0x11CF, 0x88, 0xCB, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30);
|
|
|
|
namespace nsyshid
|
|
{
|
|
|
|
typedef struct
|
|
{
|
|
/* +0x00 */ uint32be handle;
|
|
/* +0x04 */ uint32 ukn04;
|
|
/* +0x08 */ uint16 vendorId; // little-endian ?
|
|
/* +0x0A */ uint16 productId; // little-endian ?
|
|
/* +0x0C */ uint8 ifIndex;
|
|
/* +0x0D */ uint8 subClass;
|
|
/* +0x0E */ uint8 protocol;
|
|
/* +0x0F */ uint8 paddingGuessed0F;
|
|
/* +0x10 */ uint16be maxPacketSizeRX;
|
|
/* +0x12 */ uint16be maxPacketSizeTX;
|
|
}HIDDevice_t;
|
|
|
|
static_assert(offsetof(HIDDevice_t, vendorId) == 0x8, "");
|
|
static_assert(offsetof(HIDDevice_t, productId) == 0xA, "");
|
|
static_assert(offsetof(HIDDevice_t, ifIndex) == 0xC, "");
|
|
static_assert(offsetof(HIDDevice_t, protocol) == 0xE, "");
|
|
|
|
typedef struct _HIDDeviceInfo_t
|
|
{
|
|
uint32 handle;
|
|
uint32 physicalDeviceInstance;
|
|
uint16 vendorId;
|
|
uint16 productId;
|
|
uint8 interfaceIndex;
|
|
uint8 interfaceSubClass;
|
|
uint8 protocol;
|
|
HIDDevice_t* hidDevice; // this info is passed to applications and must remain intact
|
|
wchar_t* devicePath;
|
|
_HIDDeviceInfo_t* next;
|
|
// host
|
|
HANDLE hFile;
|
|
}HIDDeviceInfo_t;
|
|
|
|
HIDDeviceInfo_t* firstDevice = nullptr;
|
|
|
|
|
|
typedef struct _HIDClient_t
|
|
{
|
|
MEMPTR<_HIDClient_t> next;
|
|
uint32be callbackFunc; // attach/detach callback
|
|
}HIDClient_t;
|
|
|
|
HIDClient_t* firstHIDClient = nullptr;
|
|
|
|
HANDLE openDevice(wchar_t* devicePath)
|
|
{
|
|
return CreateFile(devicePath,
|
|
GENERIC_READ | GENERIC_WRITE,
|
|
FILE_SHARE_READ |
|
|
FILE_SHARE_WRITE,
|
|
NULL,
|
|
OPEN_EXISTING,
|
|
FILE_FLAG_OVERLAPPED,
|
|
NULL);
|
|
}
|
|
|
|
void attachClientToList(HIDClient_t* hidClient)
|
|
{
|
|
// todo - append at the beginning or end of the list? List order matters because it also controls the order in which attach callbacks are called
|
|
if (firstHIDClient)
|
|
{
|
|
hidClient->next = firstHIDClient;
|
|
firstHIDClient = hidClient;
|
|
}
|
|
else
|
|
{
|
|
hidClient->next = nullptr;
|
|
firstHIDClient = hidClient;
|
|
}
|
|
}
|
|
|
|
void attachDeviceToList(HIDDeviceInfo_t* hidDeviceInfo)
|
|
{
|
|
if (firstDevice)
|
|
{
|
|
hidDeviceInfo->next = firstDevice;
|
|
firstDevice = hidDeviceInfo;
|
|
}
|
|
else
|
|
{
|
|
hidDeviceInfo->next = nullptr;
|
|
firstDevice = hidDeviceInfo;
|
|
}
|
|
}
|
|
|
|
HIDDeviceInfo_t* getHIDDeviceInfoByHandle(uint32 handle, bool openFileHandle = false)
|
|
{
|
|
HIDDeviceInfo_t* deviceItr = firstDevice;
|
|
while (deviceItr)
|
|
{
|
|
if (deviceItr->handle == handle)
|
|
{
|
|
if (openFileHandle && deviceItr->hFile == INVALID_HANDLE_VALUE)
|
|
{
|
|
deviceItr->hFile = openDevice(deviceItr->devicePath);
|
|
if (deviceItr->hFile == INVALID_HANDLE_VALUE)
|
|
{
|
|
cemuLog_log(LogType::Force, "HID: Failed to open device \"{}\"", boost::nowide::narrow(std::wstring(deviceItr->devicePath)));
|
|
return nullptr;
|
|
}
|
|
HidD_SetNumInputBuffers(deviceItr->hFile, 2); // dont cache too many reports
|
|
}
|
|
return deviceItr;
|
|
}
|
|
deviceItr = deviceItr->next;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
uint32 _lastGeneratedHidHandle = 1;
|
|
|
|
uint32 generateHIDHandle()
|
|
{
|
|
_lastGeneratedHidHandle++;
|
|
return _lastGeneratedHidHandle;
|
|
}
|
|
|
|
const int HID_MAX_NUM_DEVICES = 128;
|
|
|
|
SysAllocator<HIDDevice_t, HID_MAX_NUM_DEVICES> _devicePool;
|
|
std::bitset<HID_MAX_NUM_DEVICES> _devicePoolMask;
|
|
|
|
HIDDevice_t* getFreeDevice()
|
|
{
|
|
for (sint32 i = 0; i < HID_MAX_NUM_DEVICES; i++)
|
|
{
|
|
if (_devicePoolMask.test(i) == false)
|
|
{
|
|
_devicePoolMask.set(i);
|
|
return _devicePool.GetPtr() + i;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void checkAndAddDevice(wchar_t* devicePath, HANDLE hDevice)
|
|
{
|
|
HIDD_ATTRIBUTES hidAttr;
|
|
hidAttr.Size = sizeof(HIDD_ATTRIBUTES);
|
|
if (HidD_GetAttributes(hDevice, &hidAttr) == FALSE)
|
|
return;
|
|
HIDDevice_t* hidDevice = getFreeDevice();
|
|
if (hidDevice == nullptr)
|
|
{
|
|
cemuLog_log(LogType::Force, "HID: Maximum number of supported devices exceeded");
|
|
return;
|
|
}
|
|
|
|
HIDDeviceInfo_t* deviceInfo = (HIDDeviceInfo_t*)malloc(sizeof(HIDDeviceInfo_t));
|
|
memset(deviceInfo, 0, sizeof(HIDDeviceInfo_t));
|
|
deviceInfo->devicePath = _wcsdup(devicePath);
|
|
deviceInfo->vendorId = hidAttr.VendorID;
|
|
deviceInfo->productId = hidAttr.ProductID;
|
|
deviceInfo->hFile = INVALID_HANDLE_VALUE;
|
|
// generate handle
|
|
deviceInfo->handle = generateHIDHandle();
|
|
// get additional device info
|
|
sint32 maxPacketInputLength = -1;
|
|
sint32 maxPacketOutputLength = -1;
|
|
PHIDP_PREPARSED_DATA ppData = nullptr;
|
|
if (HidD_GetPreparsedData(hDevice, &ppData))
|
|
{
|
|
HIDP_CAPS caps;
|
|
if (HidP_GetCaps(ppData, &caps) == HIDP_STATUS_SUCCESS)
|
|
{
|
|
// length includes the report id byte
|
|
maxPacketInputLength = caps.InputReportByteLength - 1;
|
|
maxPacketOutputLength = caps.OutputReportByteLength - 1;
|
|
}
|
|
HidD_FreePreparsedData(ppData);
|
|
}
|
|
if (maxPacketInputLength <= 0 || maxPacketInputLength >= 0xF000)
|
|
{
|
|
cemuLog_log(LogType::Force, "HID: Input packet length not available or out of range (length = {})", maxPacketInputLength);
|
|
maxPacketInputLength = 0x20;
|
|
}
|
|
if (maxPacketOutputLength <= 0 || maxPacketOutputLength >= 0xF000)
|
|
{
|
|
cemuLog_log(LogType::Force, "HID: Output packet length not available or out of range (length = {})", maxPacketOutputLength);
|
|
maxPacketOutputLength = 0x20;
|
|
}
|
|
// setup HIDDevice struct
|
|
deviceInfo->hidDevice = hidDevice;
|
|
memset(hidDevice, 0, sizeof(HIDDevice_t));
|
|
hidDevice->handle = deviceInfo->handle;
|
|
hidDevice->vendorId = deviceInfo->vendorId;
|
|
hidDevice->productId = deviceInfo->productId;
|
|
hidDevice->maxPacketSizeRX = maxPacketInputLength;
|
|
hidDevice->maxPacketSizeTX = maxPacketOutputLength;
|
|
|
|
hidDevice->ukn04 = 0x11223344;
|
|
|
|
hidDevice->ifIndex = 1;
|
|
hidDevice->protocol = 0;
|
|
hidDevice->subClass = 2;
|
|
|
|
// todo - other values
|
|
//hidDevice->ifIndex = 1;
|
|
|
|
|
|
attachDeviceToList(deviceInfo);
|
|
|
|
}
|
|
|
|
void initDeviceList()
|
|
{
|
|
if (firstDevice)
|
|
return;
|
|
HDEVINFO hDevInfo;
|
|
SP_DEVICE_INTERFACE_DATA DevIntfData;
|
|
PSP_DEVICE_INTERFACE_DETAIL_DATA DevIntfDetailData;
|
|
SP_DEVINFO_DATA DevData;
|
|
|
|
DWORD dwSize, dwMemberIdx;
|
|
|
|
hDevInfo = SetupDiGetClassDevs(&GUID_DEVINTERFACE_HID, NULL, 0, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT);
|
|
|
|
if (hDevInfo != INVALID_HANDLE_VALUE)
|
|
{
|
|
DevIntfData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
|
|
dwMemberIdx = 0;
|
|
|
|
SetupDiEnumDeviceInterfaces(hDevInfo, NULL, &GUID_DEVINTERFACE_HID,
|
|
dwMemberIdx, &DevIntfData);
|
|
|
|
while (GetLastError() != ERROR_NO_MORE_ITEMS)
|
|
{
|
|
DevData.cbSize = sizeof(DevData);
|
|
SetupDiGetDeviceInterfaceDetail(
|
|
hDevInfo, &DevIntfData, NULL, 0, &dwSize, NULL);
|
|
|
|
DevIntfDetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwSize);
|
|
DevIntfDetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
|
|
|
|
if (SetupDiGetDeviceInterfaceDetail(hDevInfo, &DevIntfData,
|
|
DevIntfDetailData, dwSize, &dwSize, &DevData))
|
|
{
|
|
HANDLE hHIDDevice = openDevice(DevIntfDetailData->DevicePath);
|
|
if (hHIDDevice != INVALID_HANDLE_VALUE)
|
|
{
|
|
checkAndAddDevice(DevIntfDetailData->DevicePath, hHIDDevice);
|
|
CloseHandle(hHIDDevice);
|
|
}
|
|
}
|
|
HeapFree(GetProcessHeap(), 0, DevIntfDetailData);
|
|
// next
|
|
SetupDiEnumDeviceInterfaces(hDevInfo, NULL, &GUID_DEVINTERFACE_HID, ++dwMemberIdx, &DevIntfData);
|
|
}
|
|
SetupDiDestroyDeviceInfoList(hDevInfo);
|
|
}
|
|
}
|
|
|
|
const int HID_CALLBACK_DETACH = 0;
|
|
const int HID_CALLBACK_ATTACH = 1;
|
|
|
|
uint32 doAttachCallback(HIDClient_t* hidClient, HIDDeviceInfo_t* deviceInfo)
|
|
{
|
|
return PPCCoreCallback(hidClient->callbackFunc, memory_getVirtualOffsetFromPointer(hidClient), memory_getVirtualOffsetFromPointer(deviceInfo->hidDevice), HID_CALLBACK_ATTACH);
|
|
}
|
|
|
|
void doDetachCallback(HIDClient_t* hidClient, HIDDeviceInfo_t* deviceInfo)
|
|
{
|
|
PPCCoreCallback(hidClient->callbackFunc, memory_getVirtualOffsetFromPointer(hidClient), memory_getVirtualOffsetFromPointer(deviceInfo->hidDevice), HID_CALLBACK_DETACH);
|
|
}
|
|
|
|
void export_HIDAddClient(PPCInterpreter_t* hCPU)
|
|
{
|
|
ppcDefineParamTypePtr(hidClient, HIDClient_t, 0);
|
|
ppcDefineParamMPTR(callbackFuncMPTR, 1);
|
|
cemuLog_logDebug(LogType::Force, "nsyshid.HIDAddClient(0x{:08x},0x{:08x})", hCPU->gpr[3], hCPU->gpr[4]);
|
|
hidClient->callbackFunc = callbackFuncMPTR;
|
|
attachClientToList(hidClient);
|
|
initDeviceList();
|
|
// do attach callbacks
|
|
HIDDeviceInfo_t* deviceItr = firstDevice;
|
|
while (deviceItr)
|
|
{
|
|
if (doAttachCallback(hidClient, deviceItr) != 0)
|
|
break;
|
|
deviceItr = deviceItr->next;
|
|
}
|
|
|
|
osLib_returnFromFunction(hCPU, 0);
|
|
}
|
|
|
|
void export_HIDDelClient(PPCInterpreter_t* hCPU)
|
|
{
|
|
ppcDefineParamTypePtr(hidClient, HIDClient_t, 0);
|
|
cemuLog_logDebug(LogType::Force, "nsyshid.HIDDelClient(0x{:08x})", hCPU->gpr[3]);
|
|
|
|
// todo
|
|
// do detach callbacks
|
|
HIDDeviceInfo_t* deviceItr = firstDevice;
|
|
while (deviceItr)
|
|
{
|
|
doDetachCallback(hidClient, deviceItr);
|
|
deviceItr = deviceItr->next;
|
|
}
|
|
|
|
osLib_returnFromFunction(hCPU, 0);
|
|
}
|
|
|
|
void export_HIDGetDescriptor(PPCInterpreter_t* hCPU)
|
|
{
|
|
ppcDefineParamU32(hidHandle, 0); // r3
|
|
ppcDefineParamU8(descType, 1); // r4
|
|
ppcDefineParamU8(descIndex, 2); // r5
|
|
ppcDefineParamU8(lang, 3); // r6
|
|
ppcDefineParamUStr(output, 4); // r7
|
|
ppcDefineParamU32(outputMaxLength, 5); // r8
|
|
ppcDefineParamMPTR(cbFuncMPTR, 6); // r9
|
|
ppcDefineParamMPTR(cbParamMPTR, 7); // r10
|
|
|
|
HIDDeviceInfo_t* hidDeviceInfo = getHIDDeviceInfoByHandle(hidHandle);
|
|
if (hidDeviceInfo)
|
|
{
|
|
HANDLE hHIDDevice = openDevice(hidDeviceInfo->devicePath);
|
|
if (hHIDDevice != INVALID_HANDLE_VALUE)
|
|
{
|
|
if (descType == 0x02)
|
|
{
|
|
uint8 configurationDescriptor[0x29];
|
|
|
|
uint8* currentWritePtr;
|
|
|
|
// configuration descriptor
|
|
currentWritePtr = configurationDescriptor + 0;
|
|
*(uint8*)(currentWritePtr + 0) = 9; // bLength
|
|
*(uint8*)(currentWritePtr + 1) = 2; // bDescriptorType
|
|
*(uint16be*)(currentWritePtr + 2) = 0x0029; // wTotalLength
|
|
*(uint8*)(currentWritePtr + 4) = 1; // bNumInterfaces
|
|
*(uint8*)(currentWritePtr + 5) = 1; // bConfigurationValue
|
|
*(uint8*)(currentWritePtr + 6) = 0; // iConfiguration
|
|
*(uint8*)(currentWritePtr + 7) = 0x80; // bmAttributes
|
|
*(uint8*)(currentWritePtr + 8) = 0xFA; // MaxPower
|
|
currentWritePtr = currentWritePtr + 9;
|
|
// configuration descriptor
|
|
*(uint8*)(currentWritePtr + 0) = 9; // bLength
|
|
*(uint8*)(currentWritePtr + 1) = 0x04; // bDescriptorType
|
|
*(uint8*)(currentWritePtr + 2) = 0; // bInterfaceNumber
|
|
*(uint8*)(currentWritePtr + 3) = 0; // bAlternateSetting
|
|
*(uint8*)(currentWritePtr + 4) = 2; // bNumEndpoints
|
|
*(uint8*)(currentWritePtr + 5) = 3; // bInterfaceClass
|
|
*(uint8*)(currentWritePtr + 6) = 0; // bInterfaceSubClass
|
|
*(uint8*)(currentWritePtr + 7) = 0; // bInterfaceProtocol
|
|
*(uint8*)(currentWritePtr + 8) = 0; // iInterface
|
|
currentWritePtr = currentWritePtr + 9;
|
|
// configuration descriptor
|
|
*(uint8*)(currentWritePtr + 0) = 9; // bLength
|
|
*(uint8*)(currentWritePtr + 1) = 0x21; // bDescriptorType
|
|
*(uint16be*)(currentWritePtr + 2) = 0x0111; // bcdHID
|
|
*(uint8*)(currentWritePtr + 4) = 0x00; // bCountryCode
|
|
*(uint8*)(currentWritePtr + 5) = 0x01; // bNumDescriptors
|
|
*(uint8*)(currentWritePtr + 6) = 0x22; // bDescriptorType
|
|
*(uint16be*)(currentWritePtr + 7) = 0x001D; // wDescriptorLength
|
|
currentWritePtr = currentWritePtr + 9;
|
|
// endpoint descriptor 1
|
|
*(uint8*)(currentWritePtr + 0) = 7; // bLength
|
|
*(uint8*)(currentWritePtr + 1) = 0x05; // bDescriptorType
|
|
*(uint8*)(currentWritePtr + 1) = 0x81; // bEndpointAddress
|
|
*(uint8*)(currentWritePtr + 2) = 0x03; // bmAttributes
|
|
*(uint16be*)(currentWritePtr + 3) = 0x40; // wMaxPacketSize
|
|
*(uint8*)(currentWritePtr + 5) = 0x01; // bInterval
|
|
currentWritePtr = currentWritePtr + 7;
|
|
// endpoint descriptor 2
|
|
*(uint8*)(currentWritePtr + 0) = 7; // bLength
|
|
*(uint8*)(currentWritePtr + 1) = 0x05; // bDescriptorType
|
|
*(uint8*)(currentWritePtr + 1) = 0x02; // bEndpointAddress
|
|
*(uint8*)(currentWritePtr + 2) = 0x03; // bmAttributes
|
|
*(uint16be*)(currentWritePtr + 3) = 0x40; // wMaxPacketSize
|
|
*(uint8*)(currentWritePtr + 5) = 0x01; // bInterval
|
|
currentWritePtr = currentWritePtr + 7;
|
|
|
|
cemu_assert_debug((currentWritePtr - configurationDescriptor) == 0x29);
|
|
|
|
memcpy(output, configurationDescriptor, std::min<uint32>(outputMaxLength, sizeof(configurationDescriptor)));
|
|
}
|
|
else
|
|
{
|
|
cemu_assert_unimplemented();
|
|
}
|
|
CloseHandle(hHIDDevice);
|
|
}
|
|
else
|
|
{
|
|
cemu_assert_unimplemented();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
cemu_assert_suspicious();
|
|
}
|
|
osLib_returnFromFunction(hCPU, 0);
|
|
}
|
|
|
|
void _debugPrintHex(std::string prefix, uint8* data, size_t len)
|
|
{
|
|
char debugOutput[1024] = { 0 };
|
|
len = std::min(len, (size_t)100);
|
|
for (sint32 i = 0; i < len; i++)
|
|
{
|
|
sprintf(debugOutput + i * 3, "%02x ", data[i]);
|
|
}
|
|
cemuLog_logDebug(LogType::Force, "[{}] Data: {}", prefix, debugOutput);
|
|
}
|
|
|
|
void doHIDTransferCallback(MPTR callbackFuncMPTR, MPTR callbackParamMPTR, uint32 hidHandle, uint32 errorCode, MPTR buffer, sint32 length)
|
|
{
|
|
coreinitAsyncCallback_add(callbackFuncMPTR, 5, hidHandle, errorCode, buffer, length, callbackParamMPTR);
|
|
}
|
|
|
|
void export_HIDSetIdle(PPCInterpreter_t* hCPU)
|
|
{
|
|
ppcDefineParamU32(hidHandle, 0); // r3
|
|
ppcDefineParamU32(ifIndex, 1); // r4
|
|
ppcDefineParamU32(ukn, 2); // r5
|
|
ppcDefineParamU32(duration, 3); // r6
|
|
ppcDefineParamMPTR(callbackFuncMPTR, 4); // r7
|
|
ppcDefineParamMPTR(callbackParamMPTR, 5); // r8
|
|
cemuLog_logDebug(LogType::Force, "nsyshid.HIDSetIdle(...)");
|
|
|
|
// todo
|
|
if (callbackFuncMPTR)
|
|
{
|
|
doHIDTransferCallback(callbackFuncMPTR, callbackParamMPTR, hidHandle, 0, MPTR_NULL, 0);
|
|
}
|
|
else
|
|
{
|
|
cemu_assert_unimplemented();
|
|
}
|
|
osLib_returnFromFunction(hCPU, 0); // for non-async version, return number of bytes transferred
|
|
}
|
|
|
|
void export_HIDSetProtocol(PPCInterpreter_t* hCPU)
|
|
{
|
|
ppcDefineParamU32(hidHandle, 0); // r3
|
|
ppcDefineParamU32(ifIndex, 1); // r4
|
|
ppcDefineParamU32(protocol, 2); // r5
|
|
ppcDefineParamMPTR(callbackFuncMPTR, 3); // r6
|
|
ppcDefineParamMPTR(callbackParamMPTR, 4); // r7
|
|
cemuLog_logDebug(LogType::Force, "nsyshid.HIDSetProtocol(...)");
|
|
|
|
if (callbackFuncMPTR)
|
|
{
|
|
doHIDTransferCallback(callbackFuncMPTR, callbackParamMPTR, hidHandle, 0, MPTR_NULL, 0);
|
|
}
|
|
else
|
|
{
|
|
cemu_assert_unimplemented();
|
|
}
|
|
osLib_returnFromFunction(hCPU, 0); // for non-async version, return number of bytes transferred
|
|
}
|
|
|
|
// handler for async HIDSetReport transfers
|
|
void _hidSetReportAsync(HIDDeviceInfo_t* hidDeviceInfo, uint8* reportData, sint32 length, uint8* originalData, sint32 originalLength, MPTR callbackFuncMPTR, MPTR callbackParamMPTR)
|
|
{
|
|
sint32 retryCount = 0;
|
|
while (true)
|
|
{
|
|
BOOL r = HidD_SetOutputReport(hidDeviceInfo->hFile, reportData, length);
|
|
if (r != FALSE)
|
|
break;
|
|
Sleep(20); // retry
|
|
retryCount++;
|
|
if (retryCount >= 40)
|
|
{
|
|
cemuLog_log(LogType::Force, "HID async SetReport failed");
|
|
sint32 errorCode = -1;
|
|
doHIDTransferCallback(callbackFuncMPTR, callbackParamMPTR, hidDeviceInfo->handle, errorCode, memory_getVirtualOffsetFromPointer(originalData), 0);
|
|
free(reportData);
|
|
return;
|
|
}
|
|
}
|
|
doHIDTransferCallback(callbackFuncMPTR, callbackParamMPTR, hidDeviceInfo->handle, 0, memory_getVirtualOffsetFromPointer(originalData), originalLength);
|
|
free(reportData);
|
|
}
|
|
|
|
// handler for synchronous HIDSetReport transfers
|
|
sint32 _hidSetReportSync(HIDDeviceInfo_t* hidDeviceInfo, uint8* reportData, sint32 length, uint8* originalData, sint32 originalLength, OSThread_t* osThread)
|
|
{
|
|
//cemuLog_logDebug(LogType::Force, "_hidSetReportSync begin");
|
|
_debugPrintHex("_hidSetReportSync Begin", reportData, length);
|
|
sint32 retryCount = 0;
|
|
sint32 returnCode = 0;
|
|
while (true)
|
|
{
|
|
BOOL r = HidD_SetOutputReport(hidDeviceInfo->hFile, reportData, length);
|
|
if (r != FALSE)
|
|
{
|
|
returnCode = originalLength;
|
|
break;
|
|
}
|
|
Sleep(100); // retry
|
|
retryCount++;
|
|
if (retryCount >= 10)
|
|
assert_dbg();
|
|
}
|
|
free(reportData);
|
|
cemuLog_logDebug(LogType::Force, "_hidSetReportSync end. returnCode: {}", returnCode);
|
|
coreinit_resumeThread(osThread, 1000);
|
|
return returnCode;
|
|
}
|
|
|
|
void export_HIDSetReport(PPCInterpreter_t* hCPU)
|
|
{
|
|
ppcDefineParamU32(hidHandle, 0); // r3
|
|
ppcDefineParamU32(reportRelatedUkn, 1); // r4
|
|
ppcDefineParamU32(reportId, 2); // r5
|
|
ppcDefineParamUStr(data, 3); // r6
|
|
ppcDefineParamU32(dataLength, 4); // r7
|
|
ppcDefineParamMPTR(callbackFuncMPTR, 5); // r8
|
|
ppcDefineParamMPTR(callbackParamMPTR, 6); // r9
|
|
cemuLog_logDebug(LogType::Force, "nsyshid.HIDSetReport({},0x{:02x},0x{:02x},...)", hidHandle, reportRelatedUkn, reportId);
|
|
|
|
_debugPrintHex("HIDSetReport", data, dataLength);
|
|
|
|
#ifdef CEMU_DEBUG_ASSERT
|
|
if (reportRelatedUkn != 2 || reportId != 0)
|
|
assert_dbg();
|
|
#endif
|
|
|
|
HIDDeviceInfo_t* hidDeviceInfo = getHIDDeviceInfoByHandle(hidHandle, true);
|
|
if (hidDeviceInfo == nullptr)
|
|
{
|
|
cemuLog_log(LogType::Force, "nsyshid.HIDSetReport(): Unable to find device with hid handle {}", hidHandle);
|
|
osLib_returnFromFunction(hCPU, -1);
|
|
return;
|
|
}
|
|
|
|
// prepare report data
|
|
// note: Currently we need to pad the data to 0x20 bytes for it to work (plus one extra byte for HidD_SetOutputReport)
|
|
// Does IOSU pad data to 0x20 byte? Also check if this is specific to Skylanders portal
|
|
sint32 paddedLength = (dataLength +0x1F)&~0x1F;
|
|
uint8* reportData = (uint8*)malloc(paddedLength+1);
|
|
memset(reportData, 0, paddedLength+1);
|
|
reportData[0] = 0;
|
|
memcpy(reportData + 1, data, dataLength);
|
|
|
|
|
|
// issue request (synchronous or asynchronous)
|
|
sint32 returnCode = 0;
|
|
if (callbackFuncMPTR == MPTR_NULL)
|
|
{
|
|
std::future<sint32> res = std::async(std::launch::async, &_hidSetReportSync, hidDeviceInfo, reportData, paddedLength + 1, data, dataLength, coreinitThread_getCurrentThreadDepr(hCPU));
|
|
coreinit_suspendThread(coreinitThread_getCurrentThreadDepr(hCPU), 1000);
|
|
PPCCore_switchToScheduler();
|
|
returnCode = res.get();
|
|
}
|
|
else
|
|
{
|
|
// asynchronous
|
|
std::thread(&_hidSetReportAsync, hidDeviceInfo, reportData, paddedLength+1, data, dataLength, callbackFuncMPTR, callbackParamMPTR).detach();
|
|
returnCode = 0;
|
|
}
|
|
osLib_returnFromFunction(hCPU, returnCode);
|
|
}
|
|
|
|
sint32 _hidReadInternalSync(HIDDeviceInfo_t* hidDeviceInfo, uint8* data, sint32 maxLength)
|
|
{
|
|
DWORD bt;
|
|
OVERLAPPED ovlp = { 0 };
|
|
ovlp.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
|
|
|
|
uint8* tempBuffer = (uint8*)malloc(maxLength + 1);
|
|
sint32 transferLength = 0; // minus report byte
|
|
|
|
_debugPrintHex("HID_READ_BEFORE", data, maxLength);
|
|
|
|
cemuLog_logDebug(LogType::Force, "HidRead Begin (Length 0x{:08x})", maxLength);
|
|
BOOL readResult = ReadFile(hidDeviceInfo->hFile, tempBuffer, maxLength + 1, &bt, &ovlp);
|
|
if (readResult != FALSE)
|
|
{
|
|
// sometimes we get the result immediately
|
|
if (bt == 0)
|
|
transferLength = 0;
|
|
else
|
|
transferLength = bt - 1;
|
|
cemuLog_logDebug(LogType::Force, "HidRead Result received immediately (error 0x{:08x}) Length 0x{:08x}", GetLastError(), transferLength);
|
|
}
|
|
else
|
|
{
|
|
// wait for result
|
|
cemuLog_logDebug(LogType::Force, "HidRead WaitForResult (error 0x{:08x})", GetLastError());
|
|
// async hid read is never supposed to return unless there is an response? Lego Dimensions stops HIDRead calls as soon as one of them fails with a non-zero error (which includes time out)
|
|
DWORD r = WaitForSingleObject(ovlp.hEvent, 2000*100);
|
|
if (r == WAIT_TIMEOUT)
|
|
{
|
|
cemuLog_logDebug(LogType::Force, "HidRead internal timeout (error 0x{:08x})", GetLastError());
|
|
// return -108 in case of timeout
|
|
free(tempBuffer);
|
|
CloseHandle(ovlp.hEvent);
|
|
return -108;
|
|
}
|
|
|
|
|
|
cemuLog_logDebug(LogType::Force, "HidRead WaitHalfComplete");
|
|
GetOverlappedResult(hidDeviceInfo->hFile, &ovlp, &bt, false);
|
|
if (bt == 0)
|
|
transferLength = 0;
|
|
else
|
|
transferLength = bt - 1;
|
|
cemuLog_logDebug(LogType::Force, "HidRead WaitComplete Length: 0x{:08x}", transferLength);
|
|
}
|
|
sint32 returnCode = 0;
|
|
if (bt != 0)
|
|
{
|
|
memcpy(data, tempBuffer + 1, transferLength);
|
|
sint32 hidReadLength = transferLength;
|
|
|
|
char debugOutput[1024] = { 0 };
|
|
for (sint32 i = 0; i < transferLength; i++)
|
|
{
|
|
sprintf(debugOutput + i * 3, "%02x ", tempBuffer[1 + i]);
|
|
}
|
|
cemuLog_logDebug(LogType::Force, "HIDRead data: {}", debugOutput);
|
|
|
|
returnCode = transferLength;
|
|
}
|
|
else
|
|
{
|
|
cemuLog_log(LogType::Force, "Failed HID read");
|
|
returnCode = -1;
|
|
}
|
|
free(tempBuffer);
|
|
CloseHandle(ovlp.hEvent);
|
|
return returnCode;
|
|
}
|
|
|
|
void _hidReadAsync(HIDDeviceInfo_t* hidDeviceInfo, uint8* data, sint32 maxLength, MPTR callbackFuncMPTR, MPTR callbackParamMPTR)
|
|
{
|
|
sint32 returnCode = _hidReadInternalSync(hidDeviceInfo, data, maxLength);
|
|
sint32 errorCode = 0;
|
|
if (returnCode < 0)
|
|
errorCode = returnCode; // dont return number of bytes in error code
|
|
doHIDTransferCallback(callbackFuncMPTR, callbackParamMPTR, hidDeviceInfo->handle, errorCode, memory_getVirtualOffsetFromPointer(data), (returnCode>0)?returnCode:0);
|
|
}
|
|
|
|
sint32 _hidReadSync(HIDDeviceInfo_t* hidDeviceInfo, uint8* data, sint32 maxLength, OSThread_t* osThread)
|
|
{
|
|
sint32 returnCode = _hidReadInternalSync(hidDeviceInfo, data, maxLength);
|
|
coreinit_resumeThread(osThread, 1000);
|
|
return returnCode;
|
|
}
|
|
|
|
void export_HIDRead(PPCInterpreter_t* hCPU)
|
|
{
|
|
ppcDefineParamU32(hidHandle, 0); // r3
|
|
ppcDefineParamUStr(data, 1); // r4
|
|
ppcDefineParamU32(maxLength, 2); // r5
|
|
ppcDefineParamMPTR(callbackFuncMPTR, 3); // r6
|
|
ppcDefineParamMPTR(callbackParamMPTR, 4); // r7
|
|
cemuLog_logDebug(LogType::Force, "nsyshid.HIDRead(0x{:x},0x{:08x},0x{:08x},0x{:08x},0x{:08x})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7]);
|
|
|
|
HIDDeviceInfo_t* hidDeviceInfo = getHIDDeviceInfoByHandle(hidHandle, true);
|
|
if (hidDeviceInfo == nullptr)
|
|
{
|
|
cemuLog_log(LogType::Force, "nsyshid.HIDRead(): Unable to find device with hid handle {}", hidHandle);
|
|
osLib_returnFromFunction(hCPU, -1);
|
|
return;
|
|
}
|
|
sint32 returnCode = 0;
|
|
if (callbackFuncMPTR != MPTR_NULL)
|
|
{
|
|
// asynchronous transfer
|
|
std::thread(&_hidReadAsync, hidDeviceInfo, data, maxLength, callbackFuncMPTR, callbackParamMPTR).detach();
|
|
returnCode = 0;
|
|
}
|
|
else
|
|
{
|
|
// synchronous transfer
|
|
std::future<sint32> res = std::async(std::launch::async, &_hidReadSync, hidDeviceInfo, data, maxLength, coreinitThread_getCurrentThreadDepr(hCPU));
|
|
coreinit_suspendThread(coreinitThread_getCurrentThreadDepr(hCPU), 1000);
|
|
PPCCore_switchToScheduler();
|
|
returnCode = res.get();
|
|
}
|
|
|
|
osLib_returnFromFunction(hCPU, returnCode);
|
|
}
|
|
|
|
sint32 _hidWriteInternalSync(HIDDeviceInfo_t* hidDeviceInfo, uint8* data, sint32 maxLength)
|
|
{
|
|
DWORD bt;
|
|
OVERLAPPED ovlp = { 0 };
|
|
ovlp.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
|
|
|
|
uint8* tempBuffer = (uint8*)malloc(maxLength + 1);
|
|
memcpy(tempBuffer + 1, data, maxLength);
|
|
tempBuffer[0] = 0; // report byte?
|
|
|
|
cemuLog_logDebug(LogType::Force, "HidWrite Begin (Length 0x{:08x})", maxLength);
|
|
BOOL WriteResult = WriteFile(hidDeviceInfo->hFile, tempBuffer, maxLength + 1, &bt, &ovlp);
|
|
if (WriteResult != FALSE)
|
|
{
|
|
// sometimes we get the result immediately
|
|
cemuLog_logDebug(LogType::Force, "HidWrite Result received immediately (error 0x{:08x}) Length 0x{:08x}", GetLastError());
|
|
}
|
|
else
|
|
{
|
|
// wait for result
|
|
cemuLog_logDebug(LogType::Force, "HidWrite WaitForResult (error 0x{:08x})", GetLastError());
|
|
// todo - check for error type
|
|
DWORD r = WaitForSingleObject(ovlp.hEvent, 2000);
|
|
if (r == WAIT_TIMEOUT)
|
|
{
|
|
cemuLog_logDebug(LogType::Force, "HidWrite internal timeout");
|
|
// return -108 in case of timeout
|
|
free(tempBuffer);
|
|
CloseHandle(ovlp.hEvent);
|
|
return -108;
|
|
}
|
|
|
|
|
|
cemuLog_logDebug(LogType::Force, "HidWrite WaitHalfComplete");
|
|
GetOverlappedResult(hidDeviceInfo->hFile, &ovlp, &bt, false);
|
|
cemuLog_logDebug(LogType::Force, "HidWrite WaitComplete");
|
|
}
|
|
sint32 returnCode = 0;
|
|
if (bt != 0)
|
|
returnCode = maxLength;
|
|
else
|
|
returnCode = -1;
|
|
|
|
free(tempBuffer);
|
|
CloseHandle(ovlp.hEvent);
|
|
return returnCode;
|
|
}
|
|
|
|
void _hidWriteAsync(HIDDeviceInfo_t* hidDeviceInfo, uint8* data, sint32 maxLength, MPTR callbackFuncMPTR, MPTR callbackParamMPTR)
|
|
{
|
|
sint32 returnCode = _hidWriteInternalSync(hidDeviceInfo, data, maxLength);
|
|
sint32 errorCode = 0;
|
|
if (returnCode < 0)
|
|
errorCode = returnCode; // dont return number of bytes in error code
|
|
doHIDTransferCallback(callbackFuncMPTR, callbackParamMPTR, hidDeviceInfo->handle, errorCode, memory_getVirtualOffsetFromPointer(data), (returnCode > 0) ? returnCode : 0);
|
|
}
|
|
|
|
sint32 _hidWriteSync(HIDDeviceInfo_t* hidDeviceInfo, uint8* data, sint32 maxLength, OSThread_t* osThread)
|
|
{
|
|
sint32 returnCode = _hidWriteInternalSync(hidDeviceInfo, data, maxLength);
|
|
coreinit_resumeThread(osThread, 1000);
|
|
return returnCode;
|
|
}
|
|
|
|
void export_HIDWrite(PPCInterpreter_t* hCPU)
|
|
{
|
|
ppcDefineParamU32(hidHandle, 0); // r3
|
|
ppcDefineParamUStr(data, 1); // r4
|
|
ppcDefineParamU32(maxLength, 2); // r5
|
|
ppcDefineParamMPTR(callbackFuncMPTR, 3); // r6
|
|
ppcDefineParamMPTR(callbackParamMPTR, 4); // r7
|
|
cemuLog_logDebug(LogType::Force, "nsyshid.HIDWrite(0x{:x},0x{:08x},0x{:08x},0x{:08x},0x{:08x})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7]);
|
|
|
|
HIDDeviceInfo_t* hidDeviceInfo = getHIDDeviceInfoByHandle(hidHandle, true);
|
|
if (hidDeviceInfo == nullptr)
|
|
{
|
|
cemuLog_log(LogType::Force, "nsyshid.HIDWrite(): Unable to find device with hid handle {}", hidHandle);
|
|
osLib_returnFromFunction(hCPU, -1);
|
|
return;
|
|
}
|
|
sint32 returnCode = 0;
|
|
if (callbackFuncMPTR != MPTR_NULL)
|
|
{
|
|
// asynchronous transfer
|
|
std::thread(&_hidWriteAsync, hidDeviceInfo, data, maxLength, callbackFuncMPTR, callbackParamMPTR).detach();
|
|
returnCode = 0;
|
|
}
|
|
else
|
|
{
|
|
// synchronous transfer
|
|
std::future<sint32> res = std::async(std::launch::async, &_hidWriteSync, hidDeviceInfo, data, maxLength, coreinitThread_getCurrentThreadDepr(hCPU));
|
|
coreinit_suspendThread(coreinitThread_getCurrentThreadDepr(hCPU), 1000);
|
|
PPCCore_switchToScheduler();
|
|
returnCode = res.get();
|
|
}
|
|
|
|
osLib_returnFromFunction(hCPU, returnCode);
|
|
}
|
|
|
|
void export_HIDDecodeError(PPCInterpreter_t* hCPU)
|
|
{
|
|
ppcDefineParamU32(errorCode, 0);
|
|
ppcDefineParamTypePtr(ukn0, uint32be, 1);
|
|
ppcDefineParamTypePtr(ukn1, uint32be, 2);
|
|
cemuLog_logDebug(LogType::Force, "nsyshid.HIDDecodeError(0x{:08x},0x{:08x},0x{:08x})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]);
|
|
|
|
// todo
|
|
*ukn0 = 0x3FF;
|
|
*ukn1 = (uint32)-0x7FFF;
|
|
|
|
osLib_returnFromFunction(hCPU, 0);
|
|
}
|
|
|
|
void save(MemStreamWriter& s)
|
|
{
|
|
s.writeSection("nsyshid");
|
|
s.writeData(firstDevice, sizeof(HIDDeviceInfo_t));
|
|
s.writeData(firstHIDClient, sizeof(HIDClient_t));
|
|
s.writeBE(_lastGeneratedHidHandle);
|
|
s.writeMPTR(_devicePool);
|
|
s.writeBE(_devicePoolMask.count());
|
|
}
|
|
|
|
void restore(MemStreamReader& s)
|
|
{
|
|
s.readSection("nsyshid");
|
|
s.readData(firstDevice, sizeof(HIDDeviceInfo_t));
|
|
s.readData(firstHIDClient, sizeof(HIDClient_t));
|
|
s.readBE(_lastGeneratedHidHandle);
|
|
s.readMPTR(_devicePool);
|
|
_devicePoolMask.reset();
|
|
for (size_t i = 0; i < s.readBE<size_t>(); i++)
|
|
_devicePoolMask.set(i);
|
|
}
|
|
|
|
void load()
|
|
{
|
|
osLib_addFunction("nsyshid", "HIDAddClient", export_HIDAddClient);
|
|
osLib_addFunction("nsyshid", "HIDDelClient", export_HIDDelClient);
|
|
osLib_addFunction("nsyshid", "HIDGetDescriptor", export_HIDGetDescriptor);
|
|
osLib_addFunction("nsyshid", "HIDSetIdle", export_HIDSetIdle);
|
|
osLib_addFunction("nsyshid", "HIDSetProtocol", export_HIDSetProtocol);
|
|
osLib_addFunction("nsyshid", "HIDSetReport", export_HIDSetReport);
|
|
|
|
osLib_addFunction("nsyshid", "HIDRead", export_HIDRead);
|
|
osLib_addFunction("nsyshid", "HIDWrite", export_HIDWrite);
|
|
|
|
osLib_addFunction("nsyshid", "HIDDecodeError", export_HIDDecodeError);
|
|
firstHIDClient = nullptr;
|
|
}
|
|
}
|
|
|
|
#else
|
|
|
|
namespace nsyshid
|
|
{
|
|
void load()
|
|
{
|
|
// unimplemented
|
|
};
|
|
};
|
|
|
|
|
|
#endif
|