2014-03-28 02:50:40 +01:00
|
|
|
|
using System;
|
|
|
|
|
using System.Runtime.InteropServices;
|
|
|
|
|
using System.Threading;
|
|
|
|
|
using System.IO;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
using Microsoft.Win32.SafeHandles;
|
2015-02-08 22:51:52 +01:00
|
|
|
|
namespace DS4Windows
|
2014-03-28 02:50:40 +01:00
|
|
|
|
{
|
|
|
|
|
public class HidDevice : IDisposable
|
|
|
|
|
{
|
|
|
|
|
public enum ReadStatus
|
|
|
|
|
{
|
|
|
|
|
Success = 0,
|
|
|
|
|
WaitTimedOut = 1,
|
|
|
|
|
WaitFail = 2,
|
|
|
|
|
NoDataRead = 3,
|
|
|
|
|
ReadError = 4,
|
|
|
|
|
NotConnected = 5
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private readonly string _description;
|
|
|
|
|
private readonly string _devicePath;
|
|
|
|
|
private readonly HidDeviceAttributes _deviceAttributes;
|
|
|
|
|
|
|
|
|
|
private readonly HidDeviceCapabilities _deviceCapabilities;
|
|
|
|
|
private bool _monitorDeviceEvents;
|
|
|
|
|
private string serial = null;
|
|
|
|
|
internal HidDevice(string devicePath, string description = null)
|
|
|
|
|
{
|
|
|
|
|
_devicePath = devicePath;
|
|
|
|
|
_description = description;
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var hidHandle = OpenHandle(_devicePath, false);
|
|
|
|
|
|
|
|
|
|
_deviceAttributes = GetDeviceAttributes(hidHandle);
|
|
|
|
|
_deviceCapabilities = GetDeviceCapabilities(hidHandle);
|
|
|
|
|
|
|
|
|
|
hidHandle.Close();
|
|
|
|
|
}
|
|
|
|
|
catch (Exception exception)
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine(exception.Message);
|
|
|
|
|
throw new Exception(string.Format("Error querying HID device '{0}'.", devicePath), exception);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public SafeFileHandle safeReadHandle { get; private set; }
|
|
|
|
|
public FileStream fileStream { get; private set; }
|
|
|
|
|
public bool IsOpen { get; private set; }
|
2014-03-29 06:29:08 +01:00
|
|
|
|
public bool IsExclusive { get; private set; }
|
2014-03-28 02:50:40 +01:00
|
|
|
|
public bool IsConnected { get { return HidDevices.IsConnected(_devicePath); } }
|
|
|
|
|
public string Description { get { return _description; } }
|
|
|
|
|
public HidDeviceCapabilities Capabilities { get { return _deviceCapabilities; } }
|
|
|
|
|
public HidDeviceAttributes Attributes { get { return _deviceAttributes; } }
|
|
|
|
|
public string DevicePath { get { return _devicePath; } }
|
|
|
|
|
|
|
|
|
|
public override string ToString()
|
|
|
|
|
{
|
|
|
|
|
return string.Format("VendorID={0}, ProductID={1}, Version={2}, DevicePath={3}",
|
|
|
|
|
_deviceAttributes.VendorHexId,
|
|
|
|
|
_deviceAttributes.ProductHexId,
|
|
|
|
|
_deviceAttributes.Version,
|
|
|
|
|
_devicePath);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void OpenDevice(bool isExclusive)
|
|
|
|
|
{
|
|
|
|
|
if (IsOpen) return;
|
|
|
|
|
try
|
|
|
|
|
{
|
2014-03-29 06:29:08 +01:00
|
|
|
|
if (safeReadHandle == null || safeReadHandle.IsInvalid)
|
2014-03-28 02:50:40 +01:00
|
|
|
|
safeReadHandle = OpenHandle(_devicePath, isExclusive);
|
|
|
|
|
}
|
|
|
|
|
catch (Exception exception)
|
|
|
|
|
{
|
|
|
|
|
IsOpen = false;
|
|
|
|
|
throw new Exception("Error opening HID device.", exception);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
IsOpen = !safeReadHandle.IsInvalid;
|
2014-03-29 06:29:08 +01:00
|
|
|
|
IsExclusive = isExclusive;
|
2014-03-28 02:50:40 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void CloseDevice()
|
|
|
|
|
{
|
|
|
|
|
if (!IsOpen) return;
|
|
|
|
|
closeFileStreamIO();
|
|
|
|
|
|
|
|
|
|
IsOpen = false;
|
|
|
|
|
}
|
|
|
|
|
|
2014-04-27 21:32:09 +02:00
|
|
|
|
public void Dispose()
|
|
|
|
|
{
|
|
|
|
|
CancelIO();
|
|
|
|
|
CloseDevice();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void CancelIO()
|
|
|
|
|
{
|
|
|
|
|
if (IsOpen)
|
|
|
|
|
NativeMethods.CancelIoEx(safeReadHandle.DangerousGetHandle(), IntPtr.Zero);
|
|
|
|
|
}
|
|
|
|
|
|
2014-03-28 02:50:40 +01:00
|
|
|
|
public bool ReadInputReport(byte[] data)
|
|
|
|
|
{
|
|
|
|
|
if (safeReadHandle == null)
|
|
|
|
|
safeReadHandle = OpenHandle(_devicePath, true);
|
|
|
|
|
return NativeMethods.HidD_GetInputReport(safeReadHandle, data, data.Length);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static HidDeviceAttributes GetDeviceAttributes(SafeFileHandle hidHandle)
|
|
|
|
|
{
|
|
|
|
|
var deviceAttributes = default(NativeMethods.HIDD_ATTRIBUTES);
|
|
|
|
|
deviceAttributes.Size = Marshal.SizeOf(deviceAttributes);
|
|
|
|
|
NativeMethods.HidD_GetAttributes(hidHandle.DangerousGetHandle(), ref deviceAttributes);
|
|
|
|
|
return new HidDeviceAttributes(deviceAttributes);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static HidDeviceCapabilities GetDeviceCapabilities(SafeFileHandle hidHandle)
|
|
|
|
|
{
|
|
|
|
|
var capabilities = default(NativeMethods.HIDP_CAPS);
|
|
|
|
|
var preparsedDataPointer = default(IntPtr);
|
|
|
|
|
|
|
|
|
|
if (NativeMethods.HidD_GetPreparsedData(hidHandle.DangerousGetHandle(), ref preparsedDataPointer))
|
|
|
|
|
{
|
|
|
|
|
NativeMethods.HidP_GetCaps(preparsedDataPointer, ref capabilities);
|
|
|
|
|
NativeMethods.HidD_FreePreparsedData(preparsedDataPointer);
|
|
|
|
|
}
|
|
|
|
|
return new HidDeviceCapabilities(capabilities);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void closeFileStreamIO()
|
|
|
|
|
{
|
|
|
|
|
if (fileStream != null)
|
2017-03-25 16:49:03 +01:00
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
fileStream.Close();
|
|
|
|
|
}
|
|
|
|
|
catch (IOException)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-03-28 02:50:40 +01:00
|
|
|
|
fileStream = null;
|
|
|
|
|
Console.WriteLine("Close fs");
|
|
|
|
|
if (safeReadHandle != null && !safeReadHandle.IsInvalid)
|
|
|
|
|
{
|
|
|
|
|
safeReadHandle.Close();
|
|
|
|
|
Console.WriteLine("Close sh");
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
safeReadHandle = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void flush_Queue()
|
|
|
|
|
{
|
|
|
|
|
if (safeReadHandle != null)
|
|
|
|
|
{
|
|
|
|
|
NativeMethods.HidD_FlushQueue(safeReadHandle);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private ReadStatus ReadWithFileStreamTask(byte[] inputBuffer)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
if (fileStream.Read(inputBuffer, 0, inputBuffer.Length) > 0)
|
|
|
|
|
{
|
|
|
|
|
return ReadStatus.Success;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
return ReadStatus.NoDataRead;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (Exception)
|
|
|
|
|
{
|
|
|
|
|
return ReadStatus.ReadError;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
public ReadStatus ReadFile(byte[] inputBuffer)
|
|
|
|
|
{
|
|
|
|
|
if (safeReadHandle == null)
|
|
|
|
|
safeReadHandle = OpenHandle(_devicePath, true);
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
uint bytesRead;
|
|
|
|
|
if (NativeMethods.ReadFile(safeReadHandle.DangerousGetHandle(), inputBuffer, (uint)inputBuffer.Length, out bytesRead, IntPtr.Zero))
|
|
|
|
|
{
|
|
|
|
|
return ReadStatus.Success;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
return ReadStatus.NoDataRead;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (Exception)
|
|
|
|
|
{
|
|
|
|
|
return ReadStatus.ReadError;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public ReadStatus ReadWithFileStream(byte[] inputBuffer, int timeout)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
if (safeReadHandle == null)
|
|
|
|
|
safeReadHandle = OpenHandle(_devicePath, true);
|
|
|
|
|
if (fileStream == null && !safeReadHandle.IsInvalid)
|
2017-03-22 08:52:54 +01:00
|
|
|
|
fileStream = new FileStream(safeReadHandle, FileAccess.ReadWrite, inputBuffer.Length, true);
|
2014-03-28 02:50:40 +01:00
|
|
|
|
if (!safeReadHandle.IsInvalid && fileStream.CanRead)
|
|
|
|
|
{
|
|
|
|
|
Task<ReadStatus> readFileTask = new Task<ReadStatus>(() => ReadWithFileStreamTask(inputBuffer));
|
|
|
|
|
readFileTask.Start();
|
|
|
|
|
bool success = readFileTask.Wait(timeout);
|
|
|
|
|
if (success)
|
|
|
|
|
{
|
|
|
|
|
if (readFileTask.Result == ReadStatus.Success)
|
|
|
|
|
{
|
|
|
|
|
return ReadStatus.Success;
|
|
|
|
|
}
|
|
|
|
|
else if (readFileTask.Result == ReadStatus.ReadError)
|
|
|
|
|
{
|
|
|
|
|
return ReadStatus.ReadError;
|
|
|
|
|
}
|
|
|
|
|
else if (readFileTask.Result == ReadStatus.NoDataRead)
|
|
|
|
|
{
|
|
|
|
|
return ReadStatus.NoDataRead;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
return ReadStatus.WaitTimedOut;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
catch (Exception e)
|
|
|
|
|
{
|
|
|
|
|
if (e is AggregateException)
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine(e.Message);
|
|
|
|
|
return ReadStatus.WaitFail;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
return ReadStatus.ReadError;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2017-03-22 08:52:54 +01:00
|
|
|
|
return ReadStatus.ReadError;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public ReadStatus ReadAsyncWithFileStream(byte[] inputBuffer, int timeout)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
if (safeReadHandle == null)
|
|
|
|
|
safeReadHandle = OpenHandle(_devicePath, true);
|
|
|
|
|
if (fileStream == null && !safeReadHandle.IsInvalid)
|
|
|
|
|
fileStream = new FileStream(safeReadHandle, FileAccess.ReadWrite, inputBuffer.Length, true);
|
|
|
|
|
if (!safeReadHandle.IsInvalid && fileStream.CanRead)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
Task<int> readTask = fileStream.ReadAsync(inputBuffer, 0, inputBuffer.Length);
|
|
|
|
|
readTask.Wait(timeout);
|
|
|
|
|
if (readTask.Result > 0)
|
|
|
|
|
{
|
|
|
|
|
return ReadStatus.Success;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
return ReadStatus.NoDataRead;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
catch (Exception e)
|
|
|
|
|
{
|
|
|
|
|
if (e is AggregateException)
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine(e.Message);
|
|
|
|
|
return ReadStatus.WaitFail;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
return ReadStatus.ReadError;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-03-28 02:50:40 +01:00
|
|
|
|
return ReadStatus.ReadError;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool WriteOutputReportViaControl(byte[] outputBuffer)
|
|
|
|
|
{
|
|
|
|
|
if (safeReadHandle == null)
|
|
|
|
|
{
|
|
|
|
|
safeReadHandle = OpenHandle(_devicePath, true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (NativeMethods.HidD_SetOutputReport(safeReadHandle, outputBuffer, outputBuffer.Length))
|
|
|
|
|
return true;
|
|
|
|
|
else
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private bool WriteOutputReportViaInterruptTask(byte[] outputBuffer)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
fileStream.Write(outputBuffer, 0, outputBuffer.Length);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
catch (Exception e)
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine(e.Message);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-03-29 06:29:08 +01:00
|
|
|
|
public bool WriteOutputReportViaInterrupt(byte[] outputBuffer, int timeout)
|
2014-03-28 02:50:40 +01:00
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
if (safeReadHandle == null)
|
|
|
|
|
{
|
|
|
|
|
safeReadHandle = OpenHandle(_devicePath, true);
|
|
|
|
|
}
|
|
|
|
|
if (fileStream == null && !safeReadHandle.IsInvalid)
|
|
|
|
|
{
|
2017-03-22 08:52:54 +01:00
|
|
|
|
fileStream = new FileStream(safeReadHandle, FileAccess.ReadWrite, outputBuffer.Length, true);
|
2014-03-28 02:50:40 +01:00
|
|
|
|
}
|
|
|
|
|
if (fileStream != null && fileStream.CanWrite && !safeReadHandle.IsInvalid)
|
|
|
|
|
{
|
|
|
|
|
fileStream.Write(outputBuffer, 0, outputBuffer.Length);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (Exception)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-22 08:52:54 +01:00
|
|
|
|
public bool WriteAsyncOutputReportViaInterrupt(byte[] outputBuffer)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
if (safeReadHandle == null)
|
|
|
|
|
{
|
|
|
|
|
safeReadHandle = OpenHandle(_devicePath, true);
|
|
|
|
|
}
|
|
|
|
|
if (fileStream == null && !safeReadHandle.IsInvalid)
|
|
|
|
|
{
|
|
|
|
|
fileStream = new FileStream(safeReadHandle, FileAccess.ReadWrite, outputBuffer.Length, true);
|
|
|
|
|
}
|
|
|
|
|
if (fileStream != null && fileStream.CanWrite && !safeReadHandle.IsInvalid)
|
|
|
|
|
{
|
|
|
|
|
Task writeTask = fileStream.WriteAsync(outputBuffer, 0, outputBuffer.Length);
|
|
|
|
|
//fileStream.Write(outputBuffer, 0, outputBuffer.Length);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (Exception)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2014-03-28 02:50:40 +01:00
|
|
|
|
private SafeFileHandle OpenHandle(String devicePathName, Boolean isExclusive)
|
|
|
|
|
{
|
|
|
|
|
SafeFileHandle hidHandle;
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
if (isExclusive)
|
|
|
|
|
{
|
2017-03-22 08:52:54 +01:00
|
|
|
|
hidHandle = NativeMethods.CreateFile(devicePathName, NativeMethods.GENERIC_READ | NativeMethods.GENERIC_WRITE, 0, IntPtr.Zero, NativeMethods.OpenExisting, 0x20000000 | 0x80000000 | NativeMethods.FILE_FLAG_OVERLAPPED, 0);
|
2014-03-28 02:50:40 +01:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2017-03-22 08:52:54 +01:00
|
|
|
|
hidHandle = NativeMethods.CreateFile(devicePathName, NativeMethods.GENERIC_READ | NativeMethods.GENERIC_WRITE, NativeMethods.FILE_SHARE_READ | NativeMethods.FILE_SHARE_WRITE, IntPtr.Zero, NativeMethods.OpenExisting, 0x20000000 | 0x80000000 | NativeMethods.FILE_FLAG_OVERLAPPED, 0);
|
2014-03-28 02:50:40 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (Exception)
|
|
|
|
|
{
|
|
|
|
|
throw;
|
|
|
|
|
}
|
|
|
|
|
return hidHandle;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool readFeatureData(byte[] inputBuffer)
|
|
|
|
|
{
|
|
|
|
|
return NativeMethods.HidD_GetFeature(safeReadHandle.DangerousGetHandle(), inputBuffer, inputBuffer.Length);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public string readSerial()
|
|
|
|
|
{
|
|
|
|
|
if (serial != null)
|
|
|
|
|
return serial;
|
|
|
|
|
|
|
|
|
|
if (Capabilities.InputReportByteLength == 64)
|
|
|
|
|
{
|
|
|
|
|
byte[] buffer = new byte[16];
|
|
|
|
|
buffer[0] = 18;
|
|
|
|
|
readFeatureData(buffer);
|
|
|
|
|
serial = String.Format("{0:X02}:{1:X02}:{2:X02}:{3:X02}:{4:X02}:{5:X02}", buffer[6], buffer[5], buffer[4], buffer[3], buffer[2], buffer[1]);
|
|
|
|
|
return serial;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
byte[] buffer = new byte[126];
|
|
|
|
|
NativeMethods.HidD_GetSerialNumberString(safeReadHandle.DangerousGetHandle(), buffer, (ulong)buffer.Length);
|
|
|
|
|
string MACAddr = System.Text.Encoding.Unicode.GetString(buffer).Replace("\0", string.Empty).ToUpper();
|
Version 1.4.266
Flash Lightbar when at high latency now has the option to choose what
you decide is high latency
Show Notifications now has the option to only show warnings, such as
when a controller cannot be grabbed exclusively
Speaking of bad news for Windows 10 users: Hide DS4 has now been
disabled, until i can figure out why this is, it will be disabled, this
means some games that rely on this may not work properly or at all,
sorry about that
As for good news for Windows 10, did you know you can press Windows + G
to open a game bar which can record games. For Windows 10 users, there's
a new special action: Xbox Game DVR. Pick a trigger (only one button)
and tapping/holding/or double tapping does various things, such as
start/stop recording, save an ongoing recording, take a screenshot (via
the xbox app's option or your own hotkey ie form steam), or just open
the gamebar
Much of the code has been updated with c# 6.0
Added manifest so DS4Windows can notice Windows 10 and high DPIs, also
reorganized files
2015-07-31 05:34:22 +02:00
|
|
|
|
MACAddr = $"{MACAddr[0]}{MACAddr[1]}:{MACAddr[2]}{MACAddr[3]}:{MACAddr[4]}{MACAddr[5]}:{MACAddr[6]}{MACAddr[7]}:{MACAddr[8]}{MACAddr[9]}:{MACAddr[10]}{MACAddr[11]}";
|
2014-03-28 02:50:40 +01:00
|
|
|
|
serial = MACAddr;
|
|
|
|
|
return serial;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|