ScpControl/Usb/Ds4/UsbDs4.cs (252 lines of code) (raw):
using System;
using System.Net.NetworkInformation;
using System.Threading;
using ScpControl.ScpCore;
using ScpControl.Shared.Core;
using ScpControl.Utilities;
namespace ScpControl.Usb.Ds4
{
/// <summary>
/// Represents a DualShock 4 controller connected via Usb.
/// </summary>
public sealed class UsbDs4 : UsbDevice
{
#region HID Report
private readonly byte[] _hidReport =
{
0x05,
0xFF, 0x00, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00,
0x00
};
#endregion
#region Private vars
private const int R = 6; // Led Offsets
private const int G = 7; // Led Offsets
private const int B = 8; // Led Offsets
private byte _brightness = GlobalConfiguration.Instance.Brightness;
#endregion
#region Ctors
public UsbDs4() : base(DeviceClassGuid)
{
}
#endregion
#region Properties
public static Guid DeviceClassGuid
{
get { return Guid.Parse("{2ED90CE1-376F-4982-8F7F-E056CBC3CA71}"); }
}
public override DsPadId PadId { get; set; }
private void SetLightBarColor(DsPadId value)
{
switch (value)
{
case DsPadId.One: // Blue
_hidReport[R] = 0x00;
_hidReport[G] = 0x00;
_hidReport[B] = _brightness;
break;
case DsPadId.Two: // Green
_hidReport[R] = 0x00;
_hidReport[G] = _brightness;
_hidReport[B] = 0x00;
break;
case DsPadId.Three: // Yellow
_hidReport[R] = _brightness;
_hidReport[G] = _brightness;
_hidReport[B] = 0x00;
break;
case DsPadId.Four: // Cyan
_hidReport[R] = 0x00;
_hidReport[G] = _brightness;
_hidReport[B] = _brightness;
break;
case DsPadId.None: // Red
_hidReport[R] = _brightness;
_hidReport[G] = 0x00;
_hidReport[B] = 0x00;
break;
}
}
#endregion
#region Actions
private static byte MapBattery(byte value)
{
var mapped = (byte) DsBattery.None;
switch (value)
{
case 0x10:
case 0x11:
case 0x12:
case 0x13:
case 0x14:
case 0x15:
case 0x16:
case 0x17:
case 0x18:
case 0x19:
case 0x1A:
mapped = (byte) DsBattery.Charging;
break;
case 0x1B:
mapped = (byte) DsBattery.Charged;
break;
}
return mapped;
}
public override bool Open(string devicePath)
{
if (base.Open(devicePath))
{
State = DsState.Reserved;
GetDeviceInstance(ref m_Instance);
var transfered = 0;
if (SendTransfer(UsbHidRequestType.DeviceToHost, UsbHidRequest.GetReport, 0x0312, m_Buffer,
ref transfered))
{
HostAddress =
new PhysicalAddress(new[]
{m_Buffer[15], m_Buffer[14], m_Buffer[13], m_Buffer[12], m_Buffer[11], m_Buffer[10]});
DeviceAddress =
new PhysicalAddress(new[]
{m_Buffer[6], m_Buffer[5], m_Buffer[4], m_Buffer[3], m_Buffer[2], m_Buffer[1]});
}
}
return State == DsState.Reserved;
}
public override bool Start()
{
Model = DsModel.DS4;
// skip repairing if disabled in global configuration
if (!GlobalConfiguration.Instance.Repair) return base.Start();
var transfered = 0;
var hostMac = HostAddress.GetAddressBytes();
byte[] buffer =
{
0x13, hostMac[5], hostMac[4], hostMac[3], hostMac[2], hostMac[1], hostMac[0],
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
Buffer.BlockCopy(GlobalConfiguration.Instance.BdLink, 0, buffer, 7,
GlobalConfiguration.Instance.BdLink.Length);
if (SendTransfer(UsbHidRequestType.HostToDevice, UsbHidRequest.SetReport, 0x0313, buffer, ref transfered))
{
Log.DebugFormat("++ Repaired DS4 [{0}] Link Key For BTH Dongle [{1}]", DeviceAddress.AsFriendlyName(), HostAddress.AsFriendlyName());
}
else
{
Log.DebugFormat("++ Repair DS4 [{0}] Link Key For BTH Dongle [{1}] Failed!", DeviceAddress.AsFriendlyName(), HostAddress.AsFriendlyName());
}
return base.Start();
}
/// <summary>
/// Send Rumble request to controller.
/// </summary>
/// <param name="large">Larg motor.</param>
/// <param name="small">Small motor.</param>
/// <returns>Always true.</returns>
public override bool Rumble(byte large, byte small)
{
lock (_hidReport)
{
var transfered = 0;
_hidReport[4] = small;
_hidReport[5] = large;
// TODO: this is a blocking call in a locked region, fix
return WriteIntPipe(_hidReport, _hidReport.Length, ref transfered);
}
}
public override bool Pair(PhysicalAddress master)
{
var transfered = 0;
var host = master.GetAddressBytes();
byte[] buffer =
{
0x13, host[5], host[4], host[3], host[2], host[1], host[0], 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
Buffer.BlockCopy(GlobalConfiguration.Instance.BdLink, 0, buffer, 7,
GlobalConfiguration.Instance.BdLink.Length);
if (SendTransfer(UsbHidRequestType.HostToDevice, UsbHidRequest.SetReport, 0x0313, buffer, ref transfered))
{
HostAddress = master;
Log.DebugFormat("++ Paired DS4 [{0}] To BTH Dongle [{1}]", DeviceAddress.AsFriendlyName(), HostAddress.AsFriendlyName());
return true;
}
Log.DebugFormat("++ Pair Failed [{0}]", DeviceAddress.AsFriendlyName());
return false;
}
/// <summary>
/// Interprets a HID report sent by a DualShock 4 device.
/// </summary>
/// <param name="report">The HID report as byte array.</param>
protected override void ParseHidReport(byte[] report)
{
if (report[0] != 0x01) return;
PacketCounter++;
var inputReport = NewHidReport();
Battery = (DsBattery) MapBattery(report[30]);
inputReport.PacketCounter = PacketCounter;
var buttons = (report[5] << 0) | (report[6] << 8) | (report[7] << 16);
#region Convert HAT to DPAD
report[5] &= 0xF0;
switch ((uint) buttons & 0xF)
{
case 0:
report[5] |= (byte) Ds4Button.Up.Offset;
break;
case 1:
report[5] |= (byte) (Ds4Button.Up.Offset | Ds4Button.Right.Offset);
break;
case 2:
report[5] |= (byte) Ds4Button.Right.Offset;
break;
case 3:
report[5] |= (byte) (Ds4Button.Right.Offset | Ds4Button.Down.Offset);
break;
case 4:
report[5] |= (byte) Ds4Button.Down.Offset;
break;
case 5:
report[5] |= (byte) (Ds4Button.Down.Offset | Ds4Button.Left.Offset);
break;
case 6:
report[5] |= (byte) Ds4Button.Left.Offset;
break;
case 7:
report[5] |= (byte) (Ds4Button.Left.Offset | Ds4Button.Up.Offset);
break;
}
#endregion
// copy controller data to report packet
Buffer.BlockCopy(report, 0, inputReport.RawBytes, 8, 64);
OnHidReportReceived(inputReport);
}
protected override void Process(DateTime now)
{
if (!Monitor.TryEnter(_hidReport)) return;
try
{
if (!((now - m_Last).TotalMilliseconds >= 500)) return;
var transfered = 0;
m_Last = now;
// skip enabling charging animation if bar is disabled
if (!GlobalConfiguration.Instance.IsLightBarDisabled)
{
// if current brightness doesn't match global value, overwrite
if (GlobalConfiguration.Instance.Brightness != _brightness)
{
_brightness = GlobalConfiguration.Instance.Brightness;
}
// enable/disable charging animation (flash)
if (Battery != DsBattery.Charged)
{
_hidReport[9] = _hidReport[10] = 0x80;
}
else
{
_hidReport[9] = _hidReport[10] = 0x00;
}
}
else
{
_brightness = 0x00;
}
// set light bar color reflecting pad ID
if (XInputSlot.HasValue)
{
SetLightBarColor((DsPadId) XInputSlot);
}
// send report to controller
WriteIntPipe(_hidReport, _hidReport.Length, ref transfered);
}
finally
{
Monitor.Exit(_hidReport);
}
}
#endregion
}
}