ScpControl/Usb/Ds3/UsbDs3.cs (286 lines of code) (raw):
using System;
using System.ComponentModel;
using System.Linq;
using System.Net.NetworkInformation;
using System.Runtime.InteropServices;
using System.Threading;
using ScpControl.ScpCore;
using ScpControl.Shared.Core;
using ScpControl.Utilities;
namespace ScpControl.Usb.Ds3
{
/// <summary>
/// Represents a DualShock 3 controller connected via Usb.
/// </summary>
public class UsbDs3 : UsbDevice
{
#region HID Reports
private readonly byte[] _hidCommandEnable = { 0x42, 0x0C, 0x00, 0x00 };
private readonly byte[] _ledOffsets = { 0x02, 0x04, 0x08, 0x10 };
private readonly byte[] _hidReport =
{
0x00, 0xFF, 0x00, 0xFF, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00,
0xFF, 0x27, 0x10, 0x00, 0x32,
0xFF, 0x27, 0x10, 0x00, 0x32,
0xFF, 0x27, 0x10, 0x00, 0x32,
0xFF, 0x27, 0x10, 0x00, 0x32,
0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00
};
#endregion
#region Ctors
public UsbDs3()
: base(DeviceClassGuid)
{
}
#endregion
#region Private fields
private byte _counterForLeds = 0;
private byte _ledStatus = 0;
#endregion
#region Properties
/// <summary>
/// Device class GUID for DualShock 3 devices.
/// </summary>
public static Guid DeviceClassGuid
{
get { return Guid.Parse("{E2824A09-DBAA-4407-85CA-C8E8FF5F6FFA}"); }
}
private bool IsFake { get; set; }
public override DsPadId PadId { get; set; }
#endregion
#region Actions
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, 0x03F5, m_Buffer,
ref transfered))
{
HostAddress =
new PhysicalAddress(new[]
{m_Buffer[2], m_Buffer[3], m_Buffer[4], m_Buffer[5], m_Buffer[6], m_Buffer[7]});
}
else
{
Log.ErrorFormat("Couldn't request Bluetooth host address for device {0}, error: {1}", devicePath,
new Win32Exception(Marshal.GetLastWin32Error()));
State = DsState.Disconnected;
return false;
}
if (SendTransfer(UsbHidRequestType.DeviceToHost, UsbHidRequest.GetReport, 0x03F2, m_Buffer,
ref transfered))
{
DeviceAddress =
new PhysicalAddress(new[]
{m_Buffer[4], m_Buffer[5], m_Buffer[6], m_Buffer[7], m_Buffer[8], m_Buffer[9]});
}
else
{
Log.ErrorFormat("Couldn't request Bluetooth device address for device {0}, error: {1}", devicePath,
new Win32Exception(Marshal.GetLastWin32Error()));
State = DsState.Disconnected;
return false;
}
Log.InfoFormat("Successfully opened device with MAC address {0}", DeviceAddress.AsFriendlyName());
if (!IniConfig.Instance.Hci.GenuineMacAddresses.Any(m => DeviceAddress.AsFriendlyName().StartsWith(m)))
{
Log.WarnFormat("Fake DualShock 3 detected [{0}]", DeviceAddress.AsFriendlyName());
var bthCompany = IniConfig.Instance.BthChipManufacturers.FirstOrDefault(
m =>
DeviceAddress.AsFriendlyName().StartsWith(m.PartialMacAddress.ToUpper()));
if (bthCompany != null && bthCompany.Name.Equals("AirohaTechnologyCorp"))
{
Log.WarnFormat("Controller uses Bluetooth chip by Airoha Technology Corp., suppressing workaround");
IsFake = false;
}
else
{
IsFake = true;
}
}
else
{
Log.Info("Genuine Sony DualShock 3 detected");
}
}
return State == DsState.Reserved;
}
public override bool Start()
{
Model = DsModel.DS3;
if (IsActive)
{
var transfered = 0;
if (SendTransfer(UsbHidRequestType.HostToDevice, UsbHidRequest.SetReport, 0x03F4, _hidCommandEnable, ref transfered))
{
base.Start();
}
else
{
Log.ErrorFormat("Couldn't send control request to device {0}, error: {1}", DeviceAddress.AsFriendlyName(),
new Win32Exception(Marshal.GetLastWin32Error()));
return false;
}
}
return State == DsState.Connected;
}
/// <summary>
/// Send Rumble request to controller.
/// </summary>
/// <param name="large">Larg motor.</param>
/// <param name="small">Small motor.</param>
/// <returns>The result of the send request, true if sent successfully, false otherwise.</returns>
public override bool Rumble(byte large, byte small)
{
lock (_hidReport)
{
var transfered = 0;
if (GlobalConfiguration.Instance.DisableRumble)
{
_hidReport[2] = 0;
_hidReport[4] = 0;
}
else
{
_hidReport[2] = (byte)(small > 0 ? 0x01 : 0x00);
_hidReport[4] = large;
}
_hidReport[9] = _ledStatus;
// TODO: this is a blocking call in a locked region, fix
return SendTransfer(UsbHidRequestType.HostToDevice, UsbHidRequest.SetReport,
ToValue(UsbHidReportRequestType.Output, UsbHidReportRequestId.One),
_hidReport, ref transfered);
}
}
/// <summary>
/// Pairs the current device to the provided Bluetooth host.
/// </summary>
/// <param name="master">The MAC address of the host.</param>
/// <returns>True on success, false otherwise.</returns>
public override bool Pair(PhysicalAddress master)
{
var transfered = 0;
var host = master.GetAddressBytes();
byte[] buffer = { 0x00, 0x00, host[0], host[1], host[2], host[3], host[4], host[5] };
if (!SendTransfer(UsbHidRequestType.HostToDevice, UsbHidRequest.SetReport, 0x03F5, buffer, ref transfered))
return false;
HostAddress = master;
return true;
}
/// <summary>
/// Interprets a HID report sent by a DualShock 3 device.
/// </summary>
/// <param name="report">The HID report as byte array.</param>
protected override void ParseHidReport(byte[] report)
{
// report ID must be 1
if (report[0] != 0x01) return;
PacketCounter++;
var inputReport = NewHidReport();
// set battery level
Battery = (DsBattery) report[30];
// set packet counter
inputReport.PacketCounter = PacketCounter;
// copy controller data to report packet
Buffer.BlockCopy(report, 0, inputReport.RawBytes, 8, 49);
var trigger = false;
// detect Quick Disconnect combo (L1, R1 and PS buttons pressed at the same time)
if (inputReport[Ds3Button.L1].IsPressed
&& inputReport[Ds3Button.R1].IsPressed
&& inputReport[Ds3Button.Ps].IsPressed)
{
trigger = true;
// unset PS button
inputReport.RawBytes[12] ^= 0x01;
}
if (trigger && !IsShutdown)
{
IsShutdown = true;
m_Disconnect = DateTime.Now;
}
else if (!trigger && IsShutdown)
{
IsShutdown = false;
}
OnHidReportReceived(inputReport);
}
/// <summary>
/// Sends periodic status updates to the controller (HID Output reports).
/// </summary>
/// <param name="now">The current timestamp.</param>
protected override void Process(DateTime now)
{
if (!Monitor.TryEnter(_hidReport)) return;
try
{
#region Quick Disconnect handling
if (IsShutdown)
{
if ((now - m_Disconnect).TotalMilliseconds >= 2000)
{
Log.InfoFormat("Pad {0} disconnected due to quick disconnect combo", PadId);
Shutdown();
return;
}
}
#endregion
#region LED control
if ((now - m_Last).TotalMilliseconds >= GlobalConfiguration.Instance.Ds3LEDsPeriod
&& PacketCounter > 0
&& XInputSlot.HasValue)
{
m_Last = now;
_ledStatus = 0;
switch (GlobalConfiguration.Instance.Ds3LEDsFunc)
{
case 0:
_ledStatus = 0;
break;
case 1:
if (GlobalConfiguration.Instance.Ds3PadIDLEDsFlashCharging &&
Battery == DsBattery.Charging)
{
_counterForLeds++;
_counterForLeds %= 2;
if (_counterForLeds == 1)
_ledStatus = _ledOffsets[(int) XInputSlot];
}
else _ledStatus = _ledOffsets[(int) XInputSlot];
break;
case 2:
switch (Battery)
{
case DsBattery.None:
_ledStatus = 0;
break;
case DsBattery.Charging:
_counterForLeds++;
_counterForLeds %= (byte) _ledOffsets.Length;
for (byte i = 0; i <= _counterForLeds; i++)
_ledStatus |= _ledOffsets[i];
break;
case DsBattery.Charged:
_ledStatus =
(byte) (_ledOffsets[0] | _ledOffsets[1] | _ledOffsets[2] | _ledOffsets[3]);
break;
default:
;
break;
}
break;
case 3:
if (GlobalConfiguration.Instance.Ds3LEDsCustom1) _ledStatus |= _ledOffsets[0];
if (GlobalConfiguration.Instance.Ds3LEDsCustom2) _ledStatus |= _ledOffsets[1];
if (GlobalConfiguration.Instance.Ds3LEDsCustom3) _ledStatus |= _ledOffsets[2];
if (GlobalConfiguration.Instance.Ds3LEDsCustom4) _ledStatus |= _ledOffsets[3];
break;
default:
_ledStatus = 0;
break;
}
_hidReport[9] = _ledStatus;
}
#endregion
#region send HID Output Report
var transfered = 0;
if (!IsFake)
{
SendTransfer(UsbHidRequestType.HostToDevice, UsbHidRequest.SetReport,
ToValue(UsbHidReportRequestType.Output, UsbHidReportRequestId.One),
_hidReport, ref transfered);
}
else
{
var outReport = ReportDescriptor.OutputReports.FirstOrDefault();
if (outReport == null)
return;
var buffer = new byte[outReport.Length + 1];
Buffer.BlockCopy(_hidReport, 0, buffer, 1, _hidReport.Length);
buffer[0] = outReport.ID;
WriteIntPipe(buffer, buffer.Length, ref transfered);
}
#endregion
}
finally
{
Monitor.Exit(_hidReport);
}
}
#endregion
}
}