ScpControl/Bluetooth/Ds3/BthDs3.cs (257 lines of code) (raw):

using System; using System.Net.NetworkInformation; using System.Threading; using ScpControl.ScpCore; using ScpControl.Shared.Core; namespace ScpControl.Bluetooth.Ds3 { /// <summary> /// Represents a DualShock 3 controller connected via Bluetooth. /// </summary> public class BthDs3 : BthDevice { #region Private fields private byte _counterForLeds; private byte _ledStatus; #endregion #region Public methods public override bool Start() { CanStartHid = false; State = DsState.Connected; m_Queued = 1; m_Blocked = true; m_Last = DateTime.Now; BluetoothDevice.HID_Command(HciHandle.Bytes, Get_SCID(L2CAP.PSM.HID_Command), _hidCommandEnable); return base.Start(); } /// <summary> /// Interprets a HID report sent by a DualShock 3 device. /// </summary> /// <param name="report">The HID report as byte array.</param> public override void ParseHidReport(byte[] report) { if (report[10] == 0xFF) return; m_PlugStatus = report[38]; Battery = (DsBattery) report[39]; m_CableStatus = report[40]; if (m_Packet == 0) Rumble(0, 0); m_Packet++; var inputReport = NewHidReport(); inputReport.PacketCounter = m_Packet; // copy controller data to report packet Buffer.BlockCopy(report, 9, inputReport.RawBytes, 8, 49); var trigger = false; // Quick Disconnect if (inputReport[Ds3Button.L1].IsPressed && inputReport[Ds3Button.R1].IsPressed && inputReport[Ds3Button.Ps].IsPressed) { trigger = true; // unset PS button inputReport.Unset(Ds3Button.Ps); } if (inputReport.IsPadActive) { m_IsIdle = false; } else if (!m_IsIdle) { m_IsIdle = true; m_Idle = DateTime.Now; } if (trigger && !m_IsDisconnect) { m_IsDisconnect = true; m_Disconnect = DateTime.Now; } else if (!trigger && m_IsDisconnect) { m_IsDisconnect = false; } OnHidReportReceived(inputReport); } /// <summary> /// Send a rumble request to the controller. /// </summary> /// <param name="large">Rumble with large (left) motor.</param> /// <param name="small">Rumble with small (right) motor.</param> /// <returns></returns> public override bool Rumble(byte large, byte small) { lock (_hidOutputReport) { if (GlobalConfiguration.Instance.DisableRumble) { _hidOutputReport[4] = 0; _hidOutputReport[6] = 0; } else { _hidOutputReport[4] = (byte) (small > 0 ? 0x01 : 0x00); _hidOutputReport[6] = large; } if (!m_Blocked && GlobalConfiguration.Instance.Latency == 0) { m_Last = DateTime.Now; m_Blocked = true; BluetoothDevice.HID_Command(HciHandle.Bytes, Get_SCID(L2CAP.PSM.HID_Command), _hidOutputReport); } else { m_Queued = 1; } } return true; } public override bool InitHidReport(byte[] report) { var retVal = false; if (m_Init < _hidInitReport.Length) { BluetoothDevice.HID_Command(HciHandle.Bytes, Get_SCID(L2CAP.PSM.HID_Service), _hidInitReport[m_Init++]); } else if (m_Init == _hidInitReport.Length) { m_Init++; retVal = true; } return retVal; } #endregion #region Protected methods protected override void Process(DateTime now) { if (!Monitor.TryEnter(_hidOutputReport) || State != DsState.Connected) return; try { #region LED manipulation if ((now - m_Tick).TotalMilliseconds >= 500 && m_Packet > 0 && XInputSlot.HasValue) { m_Tick = now; if (m_Queued == 0) m_Queued = 1; _ledStatus = 0; switch (GlobalConfiguration.Instance.Ds3LEDsFunc) { case 0: _ledStatus = 0; break; case 1: if (GlobalConfiguration.Instance.Ds3PadIDLEDsFlashCharging && Battery == DsBattery.Low) { _counterForLeds++; _counterForLeds %= 2; if (_counterForLeds == 1) _ledStatus = _ledOffsets[(int) XInputSlot]; } else _ledStatus = _ledOffsets[(int) XInputSlot]; break; case 2: switch (Battery) { case DsBattery.None: _ledStatus = (byte) (_ledOffsets[0] | _ledOffsets[3]); break; case DsBattery.Dying: _ledStatus = (byte) (_ledOffsets[1] | _ledOffsets[2]); break; case DsBattery.Low: _counterForLeds++; _counterForLeds %= 2; if (_counterForLeds == 1) _ledStatus = _ledOffsets[0]; break; case DsBattery.Medium: _ledStatus = (byte) (_ledOffsets[0] | _ledOffsets[1]); break; case DsBattery.High: _ledStatus = (byte) (_ledOffsets[0] | _ledOffsets[1] | _ledOffsets[2]); break; case DsBattery.Full: _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; } _hidOutputReport[11] = _ledStatus; } #endregion #region Fake DS3 workaround // TODO: this works for some but breaks others, so... dafuq >_< if (IsFake) { //_hidOutputReport[0] = 0xA2; //_hidOutputReport[3] = 0xFF; //_hidOutputReport[5] = 0x00; } #endregion if (m_Blocked || m_Queued <= 0) return; if (!((now - m_Last).TotalMilliseconds >= GlobalConfiguration.Instance.Latency)) return; m_Last = now; m_Blocked = true; m_Queued--; BluetoothDevice.HID_Command(HciHandle.Bytes, Get_SCID(L2CAP.PSM.HID_Command), _hidOutputReport); } finally { Monitor.Exit(_hidOutputReport); } } #endregion #region HID Reports private readonly byte[] _hidCommandEnable = {0x53, 0xF4, 0x42, 0x03, 0x00, 0x00}; private readonly byte[][] _hidInitReport = { new byte[] {0x02, 0x00, 0x0F, 0x00, 0x08, 0x35, 0x03, 0x19, 0x12, 0x00, 0x00, 0x03, 0x00}, new byte[] { 0x04, 0x00, 0x10, 0x00, 0x0F, 0x00, 0x01, 0x00, 0x01, 0x00, 0x10, 0x35, 0x06, 0x09, 0x02, 0x01, 0x09, 0x02, 0x02, 0x00 }, new byte[] {0x06, 0x00, 0x11, 0x00, 0x0D, 0x35, 0x03, 0x19, 0x11, 0x24, 0x01, 0x90, 0x35, 0x03, 0x09, 0x02, 0x06, 0x00}, new byte[] { 0x06, 0x00, 0x12, 0x00, 0x0F, 0x35, 0x03, 0x19, 0x11, 0x24, 0x01, 0x90, 0x35, 0x03, 0x09, 0x02, 0x06, 0x02, 0x00, 0x7F }, new byte[] { 0x06, 0x00, 0x13, 0x00, 0x0F, 0x35, 0x03, 0x19, 0x11, 0x24, 0x01, 0x90, 0x35, 0x03, 0x09, 0x02, 0x06, 0x02, 0x00, 0x59 }, new byte[] { 0x06, 0x00, 0x14, 0x00, 0x0F, 0x35, 0x03, 0x19, 0x11, 0x24, 0x01, 0x80, 0x35, 0x03, 0x09, 0x02, 0x06, 0x02, 0x00, 0x33 }, new byte[] { 0x06, 0x00, 0x15, 0x00, 0x0F, 0x35, 0x03, 0x19, 0x11, 0x24, 0x01, 0x90, 0x35, 0x03, 0x09, 0x02, 0x06, 0x02, 0x00, 0x0D } }; private readonly byte[] _ledOffsets = {0x02, 0x04, 0x08, 0x10}; private readonly byte[] _hidOutputReport = { 0x52, 0x01, 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 BthDs3(IBthDevice device, PhysicalAddress master, byte lsb, byte msb) : base(device, master, lsb, msb) { } #endregion } }