├── mi ├── Images │ └── micontroller.png ├── Resources │ └── micontroller.ico ├── HidLibrary │ ├── InsertedEventHandler.cs │ ├── RemovedEventHandler.cs │ ├── WriteCallback.cs │ ├── ReadCallback.cs │ ├── ReadReportCallback.cs │ ├── DeviceMode.cs │ ├── ShareMode.cs │ ├── Extensions.cs │ ├── HidAsyncState.cs │ ├── IHidEnumerator.cs │ ├── HidDeviceData.cs │ ├── HidDeviceAttributes.cs │ ├── HidEnumerator.cs │ ├── HidDeviceEventMonitor.cs │ ├── IHidDevice.cs │ ├── HidReport.cs │ ├── HidDeviceCapabilities.cs │ ├── HidFastReadDevice.cs │ ├── HidDevices.cs │ ├── NativeMethods.cs │ └── HidDevice.cs ├── App.config ├── readme.txt ├── ScpDriverInterface │ ├── X360Buttons.cs │ ├── NativeMethods.cs │ ├── X360Controller.cs │ └── ScpBus.cs ├── mi.sln ├── LICENSE.txt ├── Properties │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ └── Resources.resx ├── ProcessIcon.cs ├── ContextMenus.cs ├── AboutBox.cs ├── app.manifest ├── mi.csproj ├── AboutBox.resx ├── Xiaomi_gamepad.cs ├── AboutBox.Designer.cs └── Program.cs ├── accelerometer_print ├── readme.txt └── hidraw.c └── readme.txt /mi/Images/micontroller.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aadfPT/Xiaomi_gamepad/HEAD/mi/Images/micontroller.png -------------------------------------------------------------------------------- /mi/Resources/micontroller.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aadfPT/Xiaomi_gamepad/HEAD/mi/Resources/micontroller.ico -------------------------------------------------------------------------------- /mi/HidLibrary/InsertedEventHandler.cs: -------------------------------------------------------------------------------- 1 | namespace HidLibrary 2 | { 3 | public delegate void InsertedEventHandler(); 4 | } -------------------------------------------------------------------------------- /mi/HidLibrary/RemovedEventHandler.cs: -------------------------------------------------------------------------------- 1 | namespace HidLibrary 2 | { 3 | public delegate void RemovedEventHandler(); 4 | } -------------------------------------------------------------------------------- /mi/HidLibrary/WriteCallback.cs: -------------------------------------------------------------------------------- 1 | namespace HidLibrary 2 | { 3 | public delegate void WriteCallback(bool success); 4 | } -------------------------------------------------------------------------------- /mi/HidLibrary/ReadCallback.cs: -------------------------------------------------------------------------------- 1 | namespace HidLibrary 2 | { 3 | public delegate void ReadCallback(HidDeviceData data); 4 | } -------------------------------------------------------------------------------- /mi/HidLibrary/ReadReportCallback.cs: -------------------------------------------------------------------------------- 1 | namespace HidLibrary 2 | { 3 | public delegate void ReadReportCallback(HidReport report); 4 | } -------------------------------------------------------------------------------- /mi/HidLibrary/DeviceMode.cs: -------------------------------------------------------------------------------- 1 | namespace HidLibrary 2 | { 3 | public enum DeviceMode 4 | { 5 | NonOverlapped = 0, 6 | Overlapped = 1 7 | } 8 | } -------------------------------------------------------------------------------- /mi/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /mi/HidLibrary/ShareMode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace HidLibrary 4 | { 5 | [Flags] 6 | public enum ShareMode 7 | { 8 | Exclusive = 0, 9 | ShareRead = NativeMethods.FILE_SHARE_READ, 10 | ShareWrite = NativeMethods.FILE_SHARE_WRITE 11 | } 12 | } -------------------------------------------------------------------------------- /accelerometer_print/readme.txt: -------------------------------------------------------------------------------- 1 | Hidraw program to enable and read accelerometer data. 2 | 3 | Build: 4 | gcc hidraw.c -o hidraw 5 | 6 | Run: 7 | ./hidraw /dev/hidraw5 8 | 9 | replace /dev/hidraw5 with your hidraw device. 10 | 11 | if it gives you a can't open error: sudo chmod a+rw /dev/hidraw5 12 | or whatever your hidraw dev might fix it until you replug it or reboot. 13 | -------------------------------------------------------------------------------- /mi/readme.txt: -------------------------------------------------------------------------------- 1 | Xiaomi Gamepad to Xbox 360 controller Mapper using SCPbus. 2 | 3 | Features: 4 | -Rumble support 5 | -Actually works well 6 | -Tested on windows 10 by an actual gamer (not me) and he said there was no delays and everything is good. 7 | 8 | 9 | 10 | Libs used: 11 | https://github.com/mogzol/ScpDriverInterface 12 | https://github.com/mikeobrien/HidLibrary/ (modified it a bit) 13 | 14 | 15 | This helped a bit: 16 | https://github.com/aadfPT/MiControllerPluginForInputMapper 17 | https://github.com/aadfPT/EAll4Windows 18 | 19 | 20 | -------------------------------------------------------------------------------- /mi/HidLibrary/Extensions.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | 3 | namespace HidLibrary 4 | { 5 | public static class Extensions 6 | { 7 | public static string ToUTF8String(this byte[] buffer) 8 | { 9 | var value = Encoding.UTF8.GetString(buffer); 10 | return value.Remove(value.IndexOf((char)0)); 11 | } 12 | 13 | public static string ToUTF16String(this byte[] buffer) 14 | { 15 | var value = Encoding.Unicode.GetString(buffer); 16 | return value.Remove(value.IndexOf((char)0)); 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /mi/HidLibrary/HidAsyncState.cs: -------------------------------------------------------------------------------- 1 | namespace HidLibrary 2 | { 3 | public class HidAsyncState 4 | { 5 | private readonly object _callerDelegate; 6 | private readonly object _callbackDelegate; 7 | 8 | public HidAsyncState(object callerDelegate, object callbackDelegate) 9 | { 10 | _callerDelegate = callerDelegate; 11 | _callbackDelegate = callbackDelegate; 12 | } 13 | 14 | public object CallerDelegate { get { return _callerDelegate; } } 15 | public object CallbackDelegate { get { return _callbackDelegate; } } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /mi/ScpDriverInterface/X360Buttons.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ScpDriverInterface 4 | { 5 | /// 6 | /// The buttons to be used with an X360Controller object. 7 | /// 8 | [Flags] 9 | public enum X360Buttons 10 | { 11 | None = 0, 12 | 13 | Up = 1 << 0, 14 | Down = 1 << 1, 15 | Left = 1 << 2, 16 | Right = 1 << 3, 17 | 18 | Start = 1 << 4, 19 | Back = 1 << 5, 20 | 21 | LeftStick = 1 << 6, 22 | RightStick = 1 << 7, 23 | 24 | LeftBumper = 1 << 8, 25 | RightBumper = 1 << 9, 26 | 27 | Logo = 1 << 10, 28 | 29 | A = 1 << 12, 30 | B = 1 << 13, 31 | X = 1 << 14, 32 | Y = 1 << 15, 33 | } 34 | } -------------------------------------------------------------------------------- /mi/HidLibrary/IHidEnumerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace HidLibrary 5 | { 6 | public interface IHidEnumerator 7 | { 8 | bool IsConnected(string devicePath); 9 | IHidDevice GetDevice(string devicePath); 10 | IEnumerable Enumerate(); 11 | IEnumerable Enumerate(string devicePath); 12 | IEnumerable Enumerate(int vendorId, params int[] productIds); 13 | IEnumerable Enumerate(int vendorId); 14 | } 15 | 16 | // Instance class that wraps HidDevices 17 | // The purpose of this is to allow consumer classes to create 18 | // their own enumeration abstractions, either for testing or 19 | // for comparing different implementations 20 | } 21 | -------------------------------------------------------------------------------- /mi/HidLibrary/HidDeviceData.cs: -------------------------------------------------------------------------------- 1 | namespace HidLibrary 2 | { 3 | public class HidDeviceData 4 | { 5 | public enum ReadStatus 6 | { 7 | Success = 0, 8 | WaitTimedOut = 1, 9 | WaitFail = 2, 10 | NoDataRead = 3, 11 | ReadError = 4, 12 | NotConnected = 5 13 | } 14 | 15 | public HidDeviceData(ReadStatus status) 16 | { 17 | Data = new byte[] {}; 18 | Status = status; 19 | } 20 | 21 | public HidDeviceData(byte[] data, ReadStatus status) 22 | { 23 | Data = data; 24 | Status = status; 25 | } 26 | 27 | public byte[] Data { get; private set; } 28 | public ReadStatus Status { get; private set; } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /mi/HidLibrary/HidDeviceAttributes.cs: -------------------------------------------------------------------------------- 1 | namespace HidLibrary 2 | { 3 | public class HidDeviceAttributes 4 | { 5 | internal HidDeviceAttributes(NativeMethods.HIDD_ATTRIBUTES attributes) 6 | { 7 | VendorId = attributes.VendorID; 8 | ProductId = attributes.ProductID; 9 | Version = attributes.VersionNumber; 10 | 11 | VendorHexId = "0x" + attributes.VendorID.ToString("X4"); 12 | ProductHexId = "0x" + attributes.ProductID.ToString("X4"); 13 | } 14 | 15 | public int VendorId { get; private set; } 16 | public int ProductId { get; private set; } 17 | public int Version { get; private set; } 18 | public string VendorHexId { get; set; } 19 | public string ProductHexId { get; set; } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /mi/mi.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25420.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "mi", "mi.csproj", "{6C084899-30AD-44EF-9975-699D75B5D61B}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {6C084899-30AD-44EF-9975-699D75B5D61B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {6C084899-30AD-44EF-9975-699D75B5D61B}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {6C084899-30AD-44EF-9975-699D75B5D61B}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {6C084899-30AD-44EF-9975-699D75B5D61B}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /mi/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 irungentoo 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /mi/HidLibrary/HidEnumerator.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace HidLibrary 5 | { 6 | public class HidEnumerator : IHidEnumerator 7 | { 8 | public bool IsConnected(string devicePath) 9 | { 10 | return HidDevices.IsConnected(devicePath); 11 | } 12 | 13 | public IHidDevice GetDevice(string devicePath) 14 | { 15 | return HidDevices.GetDevice(devicePath) as IHidDevice; 16 | } 17 | 18 | public IEnumerable Enumerate() 19 | { 20 | return HidDevices.Enumerate(). 21 | Select(d => d as IHidDevice); 22 | } 23 | 24 | public IEnumerable Enumerate(string devicePath) 25 | { 26 | return HidDevices.Enumerate(devicePath). 27 | Select(d => d as IHidDevice); 28 | } 29 | 30 | public IEnumerable Enumerate(int vendorId, params int[] productIds) 31 | { 32 | return HidDevices.Enumerate(vendorId, productIds). 33 | Select(d => d as IHidDevice); 34 | } 35 | 36 | public IEnumerable Enumerate(int vendorId) 37 | { 38 | return HidDevices.Enumerate(vendorId). 39 | Select(d => d as IHidDevice); 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /mi/HidLibrary/HidDeviceEventMonitor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | 4 | namespace HidLibrary 5 | { 6 | internal class HidDeviceEventMonitor 7 | { 8 | public event InsertedEventHandler Inserted; 9 | public event RemovedEventHandler Removed; 10 | 11 | public delegate void InsertedEventHandler(); 12 | public delegate void RemovedEventHandler(); 13 | 14 | private readonly HidDevice _device; 15 | private bool _wasConnected; 16 | 17 | public HidDeviceEventMonitor(HidDevice device) 18 | { 19 | _device = device; 20 | } 21 | 22 | public void Init() 23 | { 24 | var eventMonitor = new Action(DeviceEventMonitor); 25 | eventMonitor.BeginInvoke(DisposeDeviceEventMonitor, eventMonitor); 26 | } 27 | 28 | private void DeviceEventMonitor() 29 | { 30 | var isConnected = _device.IsConnected; 31 | 32 | if (isConnected != _wasConnected) 33 | { 34 | if (isConnected && Inserted != null) Inserted(); 35 | else if (!isConnected && Removed != null) Removed(); 36 | _wasConnected = isConnected; 37 | } 38 | 39 | Thread.Sleep(500); 40 | 41 | if (_device.MonitorDeviceEvents) Init(); 42 | } 43 | 44 | private static void DisposeDeviceEventMonitor(IAsyncResult ar) 45 | { 46 | ((Action)ar.AsyncState).EndInvoke(ar); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /mi/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Mi controller interface application")] 9 | [assembly: AssemblyDescription("Adds a virtual xbox360 gamepad managed by the mi controller")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Emulator")] 13 | [assembly: AssemblyCopyright("Copyright © 2016")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("6c084899-30ad-44ef-9975-699d75b5d61b")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.3.0.0")] 36 | [assembly: AssemblyFileVersion("1.3.0.0")] 37 | -------------------------------------------------------------------------------- /accelerometer_print/hidraw.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | int main(int argc, char **argv) { 14 | char *device = "/dev/hidraw5"; 15 | if (argc > 1) device = argv[1]; 16 | printf("%i\n", O_RDWR); 17 | int fd = open(device, O_RDWR |O_NONBLOCK); 18 | if (fd < 0) { 19 | printf("error can't open %s\n", device); 20 | return 1; 21 | } 22 | int desc_size = 0; 23 | int res = ioctl(fd, HIDIOCGRDESCSIZE, &desc_size); 24 | if (res < 0) { 25 | printf("error can't get report desc\n"); 26 | return 1; 27 | } 28 | printf("report size %i\n", desc_size); 29 | struct hidraw_report_descriptor rpt_desc; 30 | rpt_desc.size = desc_size; 31 | res = ioctl(fd, HIDIOCGRDESC, &rpt_desc); 32 | if (res < 0) { 33 | perror("HIDIOCGRDESC"); 34 | } else { 35 | printf("Report Descriptor:\n"); 36 | int i; 37 | for (i = 0; i < rpt_desc.size; i++) 38 | printf("%02X ", rpt_desc.value[i]); 39 | puts("\n"); 40 | } 41 | 42 | unsigned char enable_accel[21] = {0x31, 0x01, 0x08}; 43 | res = ioctl(fd, HIDIOCSFEATURE(3), enable_accel); 44 | if (res < 0) { 45 | printf("ignore this error, it's normal\n"); 46 | //return 1; 47 | } 48 | 49 | unsigned char buf[1024]; 50 | while (1) { 51 | int res = read(fd, buf, 1024); 52 | if (res < 0) { 53 | continue; 54 | } 55 | 56 | printf("%i\n", res); 57 | int i; 58 | for (i = 0; i < res; ++i) { 59 | printf("%02X", buf[i]); 60 | } 61 | 62 | signed short x, y, z; 63 | memcpy(&x, buf + 12, 2); 64 | memcpy(&y, buf + 14, 2); 65 | memcpy(&z, buf + 16, 2); 66 | 67 | printf("\n %hi %hi %hi\n", x, y, z); 68 | } 69 | } -------------------------------------------------------------------------------- /mi/ProcessIcon.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Drawing; 4 | using System.IO; 5 | using System.Windows.Forms; 6 | using mi.Properties; 7 | 8 | namespace mi 9 | { 10 | /// 11 | /// 12 | /// 13 | class ProcessIcon : IDisposable 14 | { 15 | /// 16 | /// The NotifyIcon object. 17 | /// 18 | NotifyIcon ni; 19 | 20 | /// 21 | /// Initializes a new instance of the class. 22 | /// 23 | public ProcessIcon() 24 | { 25 | // Instantiate the NotifyIcon object. 26 | ni = new NotifyIcon(); 27 | } 28 | 29 | /// 30 | /// Displays the icon in the system tray. 31 | /// 32 | public void Display() 33 | { 34 | // Put the icon in the system tray and allow it react to mouse clicks. 35 | ni.MouseClick += new MouseEventHandler(ni_MouseClick); 36 | var icon = Resources.micontroller; 37 | ni.Icon = icon; 38 | ni.Text = "Mi controller interface application"; 39 | ni.Visible = true; 40 | 41 | // Attach a context menu. 42 | ni.ContextMenuStrip = new ContextMenus().Create(); 43 | } 44 | 45 | /// 46 | /// Releases unmanaged and - optionally - managed resources 47 | /// 48 | public void Dispose() 49 | { 50 | // When the application closes, this will remove the icon from the system tray immediately. 51 | ni.Dispose(); 52 | } 53 | 54 | /// 55 | /// Handles the MouseClick event of the ni control. 56 | /// 57 | /// The source of the event. 58 | /// The instance containing the event data. 59 | void ni_MouseClick(object sender, MouseEventArgs e) 60 | { 61 | // Handle mouse button clicks. 62 | if (e.Button == MouseButtons.Left) 63 | { 64 | // Start Windows Explorer. 65 | //Process.Start("explorer", null); 66 | } 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /mi/HidLibrary/IHidDevice.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace HidLibrary 5 | { 6 | public interface IHidDevice : IDisposable 7 | { 8 | event InsertedEventHandler Inserted; 9 | event RemovedEventHandler Removed; 10 | 11 | IntPtr Handle { get; } 12 | bool IsOpen { get; } 13 | bool IsConnected { get; } 14 | string Description { get; } 15 | HidDeviceCapabilities Capabilities { get; } 16 | HidDeviceAttributes Attributes { get; } 17 | string DevicePath { get; } 18 | 19 | bool MonitorDeviceEvents { get; set; } 20 | 21 | void OpenDevice(); 22 | 23 | void OpenDevice(DeviceMode readMode, DeviceMode writeMode, ShareMode shareMode); 24 | 25 | void CloseDevice(); 26 | 27 | HidDeviceData Read(); 28 | 29 | void Read(ReadCallback callback); 30 | 31 | void Read(ReadCallback callback, int timeout); 32 | 33 | Task ReadAsync(int timeout = 0); 34 | 35 | HidDeviceData Read(int timeout); 36 | 37 | void ReadReport(ReadReportCallback callback); 38 | 39 | void ReadReport(ReadReportCallback callback, int timeout); 40 | 41 | Task ReadReportAsync(int timeout = 0); 42 | 43 | HidReport ReadReport(int timeout); 44 | HidReport ReadReport(); 45 | 46 | bool ReadFeatureData(out byte[] data, byte reportId = 0); 47 | 48 | bool ReadProduct(out byte[] data); 49 | 50 | bool ReadManufacturer(out byte[] data); 51 | 52 | bool ReadSerialNumber(out byte[] data); 53 | 54 | void Write(byte[] data, WriteCallback callback); 55 | 56 | bool Write(byte[] data); 57 | 58 | bool Write(byte[] data, int timeout); 59 | 60 | void Write(byte[] data, WriteCallback callback, int timeout); 61 | 62 | Task WriteAsync(byte[] data, int timeout = 0); 63 | 64 | void WriteReport(HidReport report, WriteCallback callback); 65 | 66 | bool WriteReport(HidReport report); 67 | 68 | bool WriteReport(HidReport report, int timeout); 69 | 70 | void WriteReport(HidReport report, WriteCallback callback, int timeout); 71 | 72 | Task WriteReportAsync(HidReport report, int timeout = 0); 73 | 74 | HidReport CreateReport(); 75 | 76 | bool WriteFeatureData(byte[] data); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /mi/HidLibrary/HidReport.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace HidLibrary 4 | { 5 | public class HidReport 6 | { 7 | private byte _reportId; 8 | private byte[] _data = new byte[] {}; 9 | 10 | private readonly HidDeviceData.ReadStatus _status; 11 | 12 | public HidReport(int reportSize) 13 | { 14 | Array.Resize(ref _data, reportSize - 1); 15 | } 16 | 17 | public HidReport(int reportSize, HidDeviceData deviceData) 18 | { 19 | _status = deviceData.Status; 20 | 21 | Array.Resize(ref _data, reportSize - 1); 22 | 23 | if ((deviceData.Data != null)) 24 | { 25 | 26 | if (deviceData.Data.Length > 0) 27 | { 28 | _reportId = deviceData.Data[0]; 29 | Exists = true; 30 | 31 | if (deviceData.Data.Length > 1) 32 | { 33 | var dataLength = reportSize - 1; 34 | if (deviceData.Data.Length < reportSize - 1) dataLength = deviceData.Data.Length; 35 | Array.Copy(deviceData.Data, 1, _data, 0, dataLength); 36 | } 37 | } 38 | else Exists = false; 39 | } 40 | else Exists = false; 41 | } 42 | 43 | public bool Exists { get; private set; } 44 | public HidDeviceData.ReadStatus ReadStatus { get { return _status; } } 45 | 46 | public byte ReportId 47 | { 48 | get { return _reportId; } 49 | set 50 | { 51 | _reportId = value; 52 | Exists = true; 53 | } 54 | } 55 | 56 | public byte[] Data 57 | { 58 | get { return _data; } 59 | set 60 | { 61 | _data = value; 62 | Exists = true; 63 | } 64 | } 65 | 66 | public byte[] GetBytes() 67 | { 68 | byte[] data = null; 69 | Array.Resize(ref data, _data.Length + 1); 70 | data[0] = _reportId; 71 | Array.Copy(_data, 0, data, 1, _data.Length); 72 | return data; 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /mi/HidLibrary/HidDeviceCapabilities.cs: -------------------------------------------------------------------------------- 1 | namespace HidLibrary 2 | { 3 | public class HidDeviceCapabilities 4 | { 5 | internal HidDeviceCapabilities(NativeMethods.HIDP_CAPS capabilities) 6 | { 7 | Usage = capabilities.Usage; 8 | UsagePage = capabilities.UsagePage; 9 | InputReportByteLength = capabilities.InputReportByteLength; 10 | OutputReportByteLength = capabilities.OutputReportByteLength; 11 | FeatureReportByteLength = capabilities.FeatureReportByteLength; 12 | Reserved = capabilities.Reserved; 13 | NumberLinkCollectionNodes = capabilities.NumberLinkCollectionNodes; 14 | NumberInputButtonCaps = capabilities.NumberInputButtonCaps; 15 | NumberInputValueCaps = capabilities.NumberInputValueCaps; 16 | NumberInputDataIndices = capabilities.NumberInputDataIndices; 17 | NumberOutputButtonCaps = capabilities.NumberOutputButtonCaps; 18 | NumberOutputValueCaps = capabilities.NumberOutputValueCaps; 19 | NumberOutputDataIndices = capabilities.NumberOutputDataIndices; 20 | NumberFeatureButtonCaps = capabilities.NumberFeatureButtonCaps; 21 | NumberFeatureValueCaps = capabilities.NumberFeatureValueCaps; 22 | NumberFeatureDataIndices = capabilities.NumberFeatureDataIndices; 23 | 24 | } 25 | 26 | public short Usage { get; private set; } 27 | public short UsagePage { get; private set; } 28 | public short InputReportByteLength { get; private set; } 29 | public short OutputReportByteLength { get; private set; } 30 | public short FeatureReportByteLength { get; private set; } 31 | public short[] Reserved { get; private set; } 32 | public short NumberLinkCollectionNodes { get; private set; } 33 | public short NumberInputButtonCaps { get; private set; } 34 | public short NumberInputValueCaps { get; private set; } 35 | public short NumberInputDataIndices { get; private set; } 36 | public short NumberOutputButtonCaps { get; private set; } 37 | public short NumberOutputValueCaps { get; private set; } 38 | public short NumberOutputDataIndices { get; private set; } 39 | public short NumberFeatureButtonCaps { get; private set; } 40 | public short NumberFeatureValueCaps { get; private set; } 41 | public short NumberFeatureDataIndices { get; private set; } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /mi/ScpDriverInterface/NativeMethods.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | using Microsoft.Win32.SafeHandles; 4 | 5 | namespace ScpDriverInterface 6 | { 7 | internal static class NativeMethods 8 | { 9 | [StructLayout(LayoutKind.Sequential)] 10 | internal struct SP_DEVICE_INTERFACE_DATA 11 | { 12 | internal int cbSize; 13 | internal Guid InterfaceClassGuid; 14 | internal int Flags; 15 | internal IntPtr Reserved; 16 | } 17 | 18 | internal const uint FILE_ATTRIBUTE_NORMAL = 0x80; 19 | internal const uint FILE_FLAG_OVERLAPPED = 0x40000000; 20 | internal const uint FILE_SHARE_READ = 1; 21 | internal const uint FILE_SHARE_WRITE = 2; 22 | internal const uint GENERIC_READ = 0x80000000; 23 | internal const uint GENERIC_WRITE = 0x40000000; 24 | internal const uint OPEN_EXISTING = 3; 25 | internal const int DIGCF_PRESENT = 0x0002; 26 | internal const int DIGCF_DEVICEINTERFACE = 0x0010; 27 | 28 | [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] 29 | internal static extern SafeFileHandle CreateFile(string lpFileName, uint dwDesiredAccess, uint dwShareMode, IntPtr lpSecurityAttributes, uint dwCreationDisposition, uint dwFlagsAndAttributes, UIntPtr hTemplateFile); 30 | 31 | [DllImport("kernel32.dll", SetLastError = true)] 32 | [return: MarshalAs(UnmanagedType.Bool)] 33 | internal static extern bool DeviceIoControl(SafeFileHandle hDevice, int dwIoControlCode, byte[] lpInBuffer, int nInBufferSize, byte[] lpOutBuffer, int nOutBufferSize, ref int lpBytesReturned, IntPtr lpOverlapped); 34 | 35 | [DllImport("setupapi.dll", SetLastError = true)] 36 | internal static extern int SetupDiDestroyDeviceInfoList(IntPtr deviceInfoSet); 37 | 38 | [DllImport("setupapi.dll", SetLastError = true)] 39 | [return: MarshalAs(UnmanagedType.Bool)] 40 | internal static extern bool SetupDiEnumDeviceInterfaces(IntPtr hDevInfo, IntPtr devInfo, ref Guid interfaceClassGuid, int memberIndex, ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData); 41 | 42 | [DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Auto)] 43 | internal static extern IntPtr SetupDiGetClassDevs(ref Guid classGuid, IntPtr enumerator, IntPtr hwndParent, int flags); 44 | 45 | [DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Auto)] 46 | [return: MarshalAs(UnmanagedType.Bool)] 47 | internal static extern bool SetupDiGetDeviceInterfaceDetail(IntPtr hDevInfo, ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData, IntPtr deviceInterfaceDetailData, int deviceInterfaceDetailDataSize, ref int requiredSize, ref SP_DEVICE_INTERFACE_DATA deviceInfoData); 48 | } 49 | } -------------------------------------------------------------------------------- /mi/ContextMenus.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Windows.Forms; 4 | using System.Drawing; 5 | 6 | namespace mi 7 | { 8 | /// 9 | /// 10 | /// 11 | class ContextMenus 12 | { 13 | /// 14 | /// Is the About box displayed? 15 | /// 16 | bool isAboutLoaded = false; 17 | 18 | /// 19 | /// Creates this instance. 20 | /// 21 | /// ContextMenuStrip 22 | public ContextMenuStrip Create() 23 | { 24 | // Add the default menu options. 25 | var menu = new ContextMenuStrip(); 26 | ToolStripMenuItem item; 27 | ToolStripSeparator sep; 28 | 29 | //// Windows Explorer. 30 | //item = new ToolStripMenuItem(); 31 | //item.Text = "Explorer"; 32 | //item.Click += new EventHandler(Explorer_Click); 33 | ////item.Image = Resources.Explorer; 34 | //menu.Items.Add(item); 35 | 36 | // About. 37 | item = new ToolStripMenuItem(); 38 | item.Text = "About"; 39 | item.Click += new EventHandler(About_Click); 40 | //item.Image = Resources.About; 41 | menu.Items.Add(item); 42 | 43 | // Separator. 44 | sep = new ToolStripSeparator(); 45 | menu.Items.Add(sep); 46 | 47 | // Exit. 48 | item = new ToolStripMenuItem(); 49 | item.Text = "Exit"; 50 | item.Click += new System.EventHandler(Exit_Click); 51 | //item.Image = Resources.Exit; 52 | menu.Items.Add(item); 53 | 54 | return menu; 55 | } 56 | 57 | /// 58 | /// Handles the Click event of the Explorer control. 59 | /// 60 | /// The source of the event. 61 | /// The instance containing the event data. 62 | //void Explorer_Click(object sender, EventArgs e) 63 | //{ 64 | // Process.Start("explorer", null); 65 | //} 66 | 67 | /// 68 | /// Handles the Click event of the About control. 69 | /// 70 | /// The source of the event. 71 | /// The instance containing the event data. 72 | void About_Click(object sender, EventArgs e) 73 | { 74 | if (!isAboutLoaded) 75 | { 76 | isAboutLoaded = true; 77 | new AboutBox().ShowDialog(); 78 | isAboutLoaded = false; 79 | } 80 | } 81 | 82 | /// 83 | /// Processes a menu item. 84 | /// 85 | /// The sender. 86 | /// The instance containing the event data. 87 | void Exit_Click(object sender, EventArgs e) 88 | { 89 | // Quit without further ado. 90 | Application.Exit(); 91 | } 92 | } 93 | } -------------------------------------------------------------------------------- /mi/HidLibrary/HidFastReadDevice.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace HidLibrary 4 | { 5 | public class HidFastReadDevice : HidDevice 6 | { 7 | internal HidFastReadDevice(string devicePath, string description = null) 8 | : base(devicePath, description) { } 9 | 10 | // FastRead assumes that the device is connected, 11 | // which could cause stability issues if hardware is 12 | // disconnected during a read 13 | public HidDeviceData FastRead() 14 | { 15 | return FastRead(0); 16 | } 17 | 18 | public HidDeviceData FastRead(int timeout) 19 | { 20 | try 21 | { 22 | return ReadData(timeout); 23 | } 24 | catch 25 | { 26 | return new HidDeviceData(HidDeviceData.ReadStatus.ReadError); 27 | } 28 | } 29 | 30 | public void FastRead(ReadCallback callback) 31 | { 32 | FastRead(callback, 0); 33 | } 34 | 35 | public void FastRead(ReadCallback callback, int timeout) 36 | { 37 | var readDelegate = new ReadDelegate(FastRead); 38 | var asyncState = new HidAsyncState(readDelegate, callback); 39 | readDelegate.BeginInvoke(timeout, EndRead, asyncState); 40 | } 41 | 42 | public async Task FastReadAsync(int timeout = 0) 43 | { 44 | var readDelegate = new ReadDelegate(FastRead); 45 | return await Task.Factory.FromAsync(readDelegate.BeginInvoke, readDelegate.EndInvoke, timeout, null); 46 | } 47 | 48 | public HidReport FastReadReport() 49 | { 50 | return FastReadReport(0); 51 | } 52 | 53 | public HidReport FastReadReport(int timeout) 54 | { 55 | return new HidReport(Capabilities.InputReportByteLength, FastRead(timeout)); 56 | } 57 | 58 | public void FastReadReport(ReadReportCallback callback) 59 | { 60 | FastReadReport(callback, 0); 61 | } 62 | 63 | public void FastReadReport(ReadReportCallback callback, int timeout) 64 | { 65 | var readReportDelegate = new ReadReportDelegate(FastReadReport); 66 | var asyncState = new HidAsyncState(readReportDelegate, callback); 67 | readReportDelegate.BeginInvoke(timeout, EndReadReport, asyncState); 68 | } 69 | 70 | public async Task FastReadReportAsync(int timeout = 0) 71 | { 72 | var readReportDelegate = new ReadReportDelegate(FastReadReport); 73 | return await Task.Factory.FromAsync(readReportDelegate.BeginInvoke, readReportDelegate.EndInvoke, timeout, null); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /mi/AboutBox.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using System.Windows.Forms; 4 | 5 | namespace mi 6 | { 7 | partial class AboutBox : Form 8 | { 9 | public AboutBox() 10 | { 11 | InitializeComponent(); 12 | this.Text = String.Format("About {0}", AssemblyTitle); 13 | this.labelProductName.Text = AssemblyProduct; 14 | this.labelVersion.Text = String.Format("Version {0}", AssemblyVersion); 15 | this.labelCopyright.Text = AssemblyCopyright; 16 | this.textBoxDescription.Text = AssemblyDescription; 17 | } 18 | 19 | #region Assembly Attribute Accessors 20 | 21 | public string AssemblyTitle 22 | { 23 | get 24 | { 25 | object[] attributes = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyTitleAttribute), false); 26 | if (attributes.Length > 0) 27 | { 28 | AssemblyTitleAttribute titleAttribute = (AssemblyTitleAttribute)attributes[0]; 29 | if (titleAttribute.Title != "") 30 | { 31 | return titleAttribute.Title; 32 | } 33 | } 34 | return System.IO.Path.GetFileNameWithoutExtension(Assembly.GetExecutingAssembly().CodeBase); 35 | } 36 | } 37 | 38 | public string AssemblyVersion 39 | { 40 | get 41 | { 42 | return Assembly.GetExecutingAssembly().GetName().Version.ToString(); 43 | } 44 | } 45 | 46 | public string AssemblyDescription 47 | { 48 | get 49 | { 50 | object[] attributes = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyDescriptionAttribute), false); 51 | if (attributes.Length == 0) 52 | { 53 | return ""; 54 | } 55 | return ((AssemblyDescriptionAttribute)attributes[0]).Description; 56 | } 57 | } 58 | 59 | public string AssemblyProduct 60 | { 61 | get 62 | { 63 | object[] attributes = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyProductAttribute), false); 64 | if (attributes.Length == 0) 65 | { 66 | return ""; 67 | } 68 | return ((AssemblyProductAttribute)attributes[0]).Product; 69 | } 70 | } 71 | 72 | public string AssemblyCopyright 73 | { 74 | get 75 | { 76 | object[] attributes = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyCopyrightAttribute), false); 77 | if (attributes.Length == 0) 78 | { 79 | return ""; 80 | } 81 | return ((AssemblyCopyrightAttribute)attributes[0]).Copyright; 82 | } 83 | } 84 | 85 | public string AssemblyCompany 86 | { 87 | get 88 | { 89 | object[] attributes = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyCompanyAttribute), false); 90 | if (attributes.Length == 0) 91 | { 92 | return ""; 93 | } 94 | return ((AssemblyCompanyAttribute)attributes[0]).Company; 95 | } 96 | } 97 | #endregion 98 | 99 | private void AboutBox_Load(object sender, EventArgs e) 100 | { 101 | 102 | } 103 | } 104 | } -------------------------------------------------------------------------------- /mi/app.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 52 | 59 | 60 | 61 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /mi/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace mi.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("mi.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | 63 | /// 64 | /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). 65 | /// 66 | internal static System.Drawing.Icon micontroller { 67 | get { 68 | object obj = ResourceManager.GetObject("micontroller", resourceCulture); 69 | return ((System.Drawing.Icon)(obj)); 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /mi/ScpDriverInterface/X360Controller.cs: -------------------------------------------------------------------------------- 1 | namespace ScpDriverInterface 2 | { 3 | /// 4 | /// A virtual XBox 360 Controller. After setting the desired values, use the GetReport() method to generate a controller report that can be used with ScpBus's Report() method. 5 | /// 6 | public class X360Controller 7 | { 8 | /// 9 | /// Generates a new X360Controller object with the default initial state (no buttons pressed, all analog inputs 0). 10 | /// 11 | public X360Controller() 12 | { 13 | Buttons = X360Buttons.None; 14 | LeftTrigger = 0; 15 | RightTrigger = 0; 16 | LeftStickX = 0; 17 | LeftStickY = 0; 18 | RightStickX = 0; 19 | RightStickY = 0; 20 | } 21 | 22 | /// 23 | /// Generates a new X360Controller object. Optionally, you can specify the initial state of the controller. 24 | /// 25 | /// The pressed buttons. Use like flags (i.e. (X360Buttons.A | X360Buttons.X) would be mean both A and X are pressed). 26 | /// Left trigger analog input. 0 to 255. 27 | /// Right trigger analog input. 0 to 255. 28 | /// Left stick X-axis. -32,768 to 32,767. 29 | /// Left stick Y-axis. -32,768 to 32,767. 30 | /// Right stick X-axis. -32,768 to 32,767. 31 | /// Right stick Y-axis. -32,768 to 32,767. 32 | public X360Controller(X360Buttons buttons, byte leftTrigger, byte rightTrigger, short leftStickX, short leftStickY, short rightStickX, short rightStickY) 33 | { 34 | Buttons = buttons; 35 | LeftTrigger = leftTrigger; 36 | RightTrigger = rightTrigger; 37 | LeftStickX = leftStickX; 38 | LeftStickY = leftStickY; 39 | RightStickX = rightStickX; 40 | RightStickY = rightStickY; 41 | } 42 | 43 | /// 44 | /// Generates a new X360Controller object with the same values as the specified X360Controller object. 45 | /// 46 | /// An X360Controller object to copy values from. 47 | public X360Controller(X360Controller controller) 48 | { 49 | Buttons = controller.Buttons; 50 | LeftTrigger = controller.LeftTrigger; 51 | RightTrigger = controller.RightTrigger; 52 | LeftStickX = controller.LeftStickX; 53 | LeftStickY = controller.LeftStickY; 54 | RightStickX = controller.RightStickX; 55 | RightStickY = controller.RightStickY; 56 | } 57 | 58 | /// 59 | /// The controller's currently pressed buttons. Use the X360Button values like flags (i.e. (X360Buttons.A | X360Buttons.X) would be mean both A and X are pressed). 60 | /// 61 | public X360Buttons Buttons { get; set; } 62 | 63 | /// 64 | /// The controller's left trigger analog input. Value can range from 0 to 255. 65 | /// 66 | public byte LeftTrigger { get; set; } 67 | 68 | /// 69 | /// The controller's right trigger analog input. Value can range from 0 to 255. 70 | /// 71 | public byte RightTrigger { get; set; } 72 | 73 | /// 74 | /// The controller's left stick X-axis. Value can range from -32,768 to 32,767. 75 | /// 76 | public short LeftStickX { get; set; } 77 | 78 | /// 79 | /// The controller's left stick Y-axis. Value can range from -32,768 to 32,767. 80 | /// 81 | public short LeftStickY { get; set; } 82 | 83 | /// 84 | /// The controller's right stick X-axis. Value can range from -32,768 to 32,767. 85 | /// 86 | public short RightStickX { get; set; } 87 | 88 | /// 89 | /// The controller's right stick Y-axis. Value can range from -32,768 to 32,767. 90 | /// 91 | public short RightStickY { get; set; } 92 | 93 | /// 94 | /// Generates a XBox 360 controller report as specified here: http://free60.org/wiki/GamePad#Input_report. This can be used with ScpBus's Report() method. 95 | /// 96 | /// A 20-byte XBox 360 controller report. 97 | public byte[] GetReport() 98 | { 99 | byte[] bytes = new byte[20]; 100 | 101 | bytes[0] = 0x00; // Message type (input report) 102 | bytes[1] = 0x14; // Message size (20 bytes) 103 | 104 | bytes[2] = (byte)((ushort)Buttons & 0xFF); // Buttons low 105 | bytes[3] = (byte)((ushort)Buttons >> 8 & 0xFF); // Buttons high 106 | 107 | bytes[4] = LeftTrigger; // Left trigger 108 | bytes[5] = RightTrigger; // Right trigger 109 | 110 | bytes[6] = (byte)(LeftStickX & 0xFF); // Left stick X-axis low 111 | bytes[7] = (byte)(LeftStickX >> 8 & 0xFF); // Left stick X-axis high 112 | bytes[8] = (byte)(LeftStickY & 0xFF); // Left stick Y-axis low 113 | bytes[9] = (byte)(LeftStickY >> 8 & 0xFF); // Left stick Y-axis high 114 | 115 | bytes[10] = (byte)(RightStickX & 0xFF); // Right stick X-axis low 116 | bytes[11] = (byte)(RightStickX >> 8 & 0xFF); // Right stick X-axis high 117 | bytes[12] = (byte)(RightStickY & 0xFF); // Right stick Y-axis low 118 | bytes[13] = (byte)(RightStickY >> 8 & 0xFF); // Right stick Y-axis high 119 | 120 | // Remaining bytes are unused 121 | 122 | return bytes; 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | Xiaomi Gamepad 2 | 3 | It's an excellent controller with an excellent price but it severly lacks documentation and the only place where the accelerometer in it works is when you use it with their T.V boxes. 4 | 5 | The mi folder contains the source of a remapper that uses the SCPbus thing to make windows see it as an xbox controller. The rumble also works perfectly unlike in all the other software that I found. 6 | 7 | the accelerometer_print folder contains a linux program that enables the accelerometer and prints the values. 8 | 9 | Hardware: 10 | It has the exact same layout as an Xbox 360 controller, the same buttons, joysticks, triggers and a dpad. The only thing different is that it has an accelerometer. 11 | 12 | 11 buttons (A, B, X, Y, L1, R1, both joysticks, start, back, MI button in the front) 13 | 14 | 1 dpad 15 | 2 joysticks 16 | 2 triggers 17 | 18 | 2 rumble motors with variable speeds 19 | 1 3 axis accelerometer 20 | 21 | 22 | 23 | Protocol: 24 | 25 | This is reversed engineered using mostly trial and error and looking at the decompiled sensors.gxbaby.so from an image of a Xiaomi tv box I found somewhere. I did this because I wanted to find out how to make the accelerometer work on my computer but I also discovered some other things. I'm posting this here in case it helps someone. 26 | 27 | The only valid packets according to the HID descriptor is the input packet and the set feature rumble packet. 28 | 29 | 30 | There are probably more packet types than these. 31 | 32 | 33 | Set Feature Packets: 34 | 35 | Rumble (length 3): 36 | [byte (0x20)][byte (0 to 0xff) small motor rumble strength][byte (0 to 0xff) big motor rumble strength] 37 | 38 | This is the only packet documented on the Xiaomi site. Used to make the controller rumble. 39 | 40 | ??? (length ???) 41 | [byte (0x21)][bytes ???] 42 | 43 | I managed to stop input from my controller with this packet but I'm not sure what the values I put were. 44 | 45 | 46 | Callibration packet. (length 24 (maybe more)) 47 | [byte (0x22)][byte (each bit seems to denote if a section is enabled. 00000001 would mean only the first section is enabled.)][(8 bytes) section 1, LJoy][(8 bytes) section 2, Rjoy][(3 bytes) section 3, Ltrigger][(3 bytes) section 4, Rtrigger] 48 | 49 | section 1 and 2 are used to callibrate the joysticks. Each contain 2 sub sections of 4 bytes each, one for each axis. 50 | 51 | Each of the 4 bytes are: 52 | [lower bound][lower middle][higher middle][higher bound] 53 | 54 | lower bound, higher bound are used to set what values are the minimum and maximum. the middle values mean that everything between those values the controller will report as being 0x80 or that it it centered. 55 | 56 | to callibrate, set the values to [0x00][0x7f][0x7f][0xff] for all the 4 axis then tweak both bounds until the joystick properly goes from 0 to 0xff instead of 0x15 to 0xee or whatever your controller does. note that the second axis bounds are inversed meaning that if your second axis goes from 0x13 to 0xff, you need to lower the higher bound, not the lower. 57 | 58 | then make sure that the value is 0x80 when the controller is centered. 59 | 60 | sections 3 and 4 are for the triggers. 61 | Each of the 3 bytes are: 62 | [lower bound][???][higher bound] 63 | 64 | I have no idea what the middle ??? byte does, changing its value doesn't seem to do anything. I have set it at 0xff for my controller and it works. 65 | 66 | to callibrate, set the values to [0x00][0x??][0xff] for both triggers then tweak both values until the value is 67 | 68 | 69 | This is used to callibrate the joysticks/triggers on the controller. I found out the hard way by sending a long packet with that id and all 0xff and then realizing that my controller started reporting guarbage values. The values seem to be saved permantely on the controller so I had fun fixing it by figuring out the packet format by hand. 70 | 71 | 72 | Stop input packet??? (length 2?) 73 | [byte (0x23)][byte 0x01] 74 | 75 | stops all input from the controller (except for the mi button). 76 | 77 | [byte (0x23)][byte 0x00] 78 | restarts the input. 79 | 80 | Unpair packet??? (length 2?) 81 | [byte (0x24)][byte 0x??] 82 | Unpairs and closes the controller. 83 | 84 | 85 | Note: for some reason when I tested it with hidraw ioctl(fd, HIDIOCSFEATURE(3), enable_accel) returns a fail for this packet when it returns successes for all the previous ones but don't let that fool you because it actually does enable the accelerometer (it worked here). This is one of two packets I saw in sensors.gxbaby.so 86 | 87 | Unable accelerometer packet. (length 3) 88 | [byte (0x31)][byte 0x01][byte (report sensitivity. the lower the value, the less you have to move the controller before it sends a report)] 89 | 90 | 91 | Disable accelerometer packet. (length 3) 92 | [byte (0x31)][byte 0x00][byte 0x00] 93 | 94 | 95 | ??? (length 21) 96 | [byte (0x04)][???] 97 | 98 | this was the another packet sent with ioctl(fd, HIDIOCSFEATURE(21), ); in sensors.gxbaby.so . it looks like the input packet. I didn't do any tests with it but I'm putting it here because it's probably used for something. 99 | 100 | 101 | Output packet: 102 | 103 | there is one packet that worked with hidraw: write(fd, rumble, 3); it's the rumble packet (0x20) of length 3. One thing that is different from the Set Feature report version is that [0x20][0x00][0x00] does not stop the rumble comletely, [0x20][0x01][0x01] pretty much stops it but you can still hear it a bit if you take your ear and listen very closely. It is better to use this way of sending the rumble packet because "Set feature" stops the input from the controller due to how HID stuff works. 104 | 105 | 106 | Input packet: 107 | 108 | Input (length 21) 109 | [byte (0x04)][byte (1 bit per button)][byte (1 bit per button)][byte 0][byte dpad][4 bytes = 4 joystick axis, 1 byte each axis][byte 0][byte 0][byte Ltrigger][byte Rtrigger][6 bytes accelerometer (2 bytes per axis, looks like signed little endian)][byte battery level][byte (MI button)] 110 | 111 | 112 | -------------------------------------------------------------------------------- /mi/mi.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {6C084899-30AD-44EF-9975-699D75B5D61B} 8 | WinExe 9 | Properties 10 | mi 11 | mi 12 | v4.5.2 13 | 512 14 | true 15 | 16 | 17 | AnyCPU 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | false 26 | 27 | 28 | AnyCPU 29 | none 30 | true 31 | bin\Release\ 32 | TRACE 33 | prompt 34 | 4 35 | false 36 | 37 | 38 | app.manifest 39 | 40 | 41 | mi.Program 42 | 43 | 44 | Resources\micontroller.ico 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | Form 61 | 62 | 63 | AboutBox.cs 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | True 92 | True 93 | Resources.resx 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | AboutBox.cs 108 | 109 | 110 | ResXFileCodeGenerator 111 | Resources.Designer.cs 112 | 113 | 114 | 115 | 116 | 117 | 118 | 125 | -------------------------------------------------------------------------------- /mi/AboutBox.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | -------------------------------------------------------------------------------- /mi/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 122 | ..\Resources\micontroller.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 123 | 124 | -------------------------------------------------------------------------------- /mi/Xiaomi_gamepad.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using HidLibrary; 4 | using ScpDriverInterface; 5 | 6 | namespace mi 7 | { 8 | public class Xiaomi_gamepad 9 | { 10 | public HidDevice Device { get; set; } 11 | public int Index; 12 | private Thread rThread, iThread; 13 | private ScpBus ScpBus; 14 | private byte[] Vibration = { 0x20, 0x00, 0x00 }; 15 | private Mutex rumble_mutex = new Mutex(); 16 | private bool Running = true; 17 | //private byte[] enableAccelerometer = { 0x31, 0x01, 0x08 }; 18 | 19 | public Xiaomi_gamepad(HidDevice device, ScpBus scpBus, int index) 20 | { 21 | Index = index; 22 | ScpBus = scpBus; 23 | Device = device; 24 | Device.WriteFeatureData(Vibration); 25 | 26 | rThread = new Thread(() => rumble_thread(Device)); 27 | // rThread.Priority = ThreadPriority.BelowNormal; 28 | rThread.Start(); 29 | 30 | iThread = new Thread(() => input_thread(Device, scpBus, index)); 31 | iThread.Priority = ThreadPriority.Highest; 32 | iThread.Start(); 33 | } 34 | 35 | public bool check_connected() 36 | { 37 | return Device.WriteFeatureData(Vibration); 38 | } 39 | 40 | public void unplug() 41 | { 42 | Running = false; 43 | rThread.Join(); 44 | iThread.Join(); 45 | ScpBus.Unplug(Index); 46 | Device.CloseDevice(); 47 | } 48 | 49 | private void rumble_thread(HidDevice Device) 50 | { 51 | byte[] local_vibration = { 0x20, 0x00, 0x00 }; 52 | while (Running) 53 | { 54 | rumble_mutex.WaitOne(); 55 | if (local_vibration[2] != Vibration[2] || Vibration[1] != local_vibration[1]) 56 | { 57 | local_vibration[2] = Vibration[2]; 58 | local_vibration[1] = Vibration[1]; 59 | rumble_mutex.ReleaseMutex(); 60 | Device.WriteFeatureData(local_vibration); 61 | //Console.WriteLine("Big Motor: {0}, Small Motor: {1}", Vibration[2], Vibration[1]); 62 | } 63 | else 64 | { 65 | rumble_mutex.ReleaseMutex(); 66 | } 67 | Thread.Sleep(20); 68 | } 69 | } 70 | 71 | private void input_thread(HidDevice Device, ScpBus scpBus, int index) 72 | { 73 | scpBus.PlugIn(index); 74 | X360Controller controller = new X360Controller(); 75 | int timeout = 30; 76 | long last_changed = 0; 77 | long last_mi_button = 0; 78 | while (Running) 79 | { 80 | HidDeviceData data = Device.Read(timeout); 81 | var currentState = data.Data; 82 | bool changed = false; 83 | if (data.Status == HidDeviceData.ReadStatus.Success && currentState.Length >= 21 && currentState[0] == 4) 84 | { 85 | //Console.WriteLine(Program.ByteArrayToHexString(currentState)); 86 | X360Buttons Buttons = X360Buttons.None; 87 | if ((currentState[1] & 1) != 0) Buttons |= X360Buttons.A; 88 | if ((currentState[1] & 2) != 0) Buttons |= X360Buttons.B; 89 | if ((currentState[1] & 8) != 0) Buttons |= X360Buttons.X; 90 | if ((currentState[1] & 16) != 0) Buttons |= X360Buttons.Y; 91 | if ((currentState[1] & 64) != 0) Buttons |= X360Buttons.LeftBumper; 92 | if ((currentState[1] & 128) != 0) Buttons |= X360Buttons.RightBumper; 93 | 94 | if ((currentState[2] & 32) != 0) Buttons |= X360Buttons.LeftStick; 95 | if ((currentState[2] & 64) != 0) Buttons |= X360Buttons.RightStick; 96 | 97 | if (currentState[4] != 15) 98 | { 99 | if (currentState[4] == 0 || currentState[4] == 1 || currentState[4] == 7) Buttons |= X360Buttons.Up; 100 | if (currentState[4] == 4 || currentState[4] == 3 || currentState[4] == 5) Buttons |= X360Buttons.Down; 101 | if (currentState[4] == 6 || currentState[4] == 5 || currentState[4] == 7) Buttons |= X360Buttons.Left; 102 | if (currentState[4] == 2 || currentState[4] == 1 || currentState[4] == 3) Buttons |= X360Buttons.Right; 103 | } 104 | 105 | if ((currentState[2] & 8) != 0) Buttons |= X360Buttons.Start; 106 | if ((currentState[2] & 4) != 0) Buttons |= X360Buttons.Back; 107 | 108 | 109 | 110 | if ((currentState[20] & 1) != 0) 111 | { 112 | last_mi_button = (DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond); 113 | Buttons |= X360Buttons.Logo; 114 | } 115 | if (last_mi_button != 0) Buttons |= X360Buttons.Logo; 116 | 117 | 118 | if (controller.Buttons != Buttons) 119 | { 120 | changed = true; 121 | controller.Buttons = Buttons; 122 | } 123 | 124 | short LeftStickX = (short)((Math.Max(-127.0, currentState[5] - 128) / 127) * 32767); 125 | if (LeftStickX == -32767) 126 | LeftStickX = -32768; 127 | 128 | if (LeftStickX != controller.LeftStickX) 129 | { 130 | changed = true; 131 | controller.LeftStickX = LeftStickX; 132 | } 133 | 134 | short LeftStickY = (short)((Math.Max(-127.0, currentState[6] - 128) / 127) * -32767); 135 | if (LeftStickY == -32767) 136 | LeftStickY = -32768; 137 | 138 | if (LeftStickY != controller.LeftStickY) 139 | { 140 | changed = true; 141 | controller.LeftStickY = LeftStickY; 142 | } 143 | 144 | short RightStickX = (short)((Math.Max(-127.0, currentState[7] - 128) / 127) * 32767); 145 | if (RightStickX == -32767) 146 | RightStickX = -32768; 147 | 148 | if (RightStickX != controller.RightStickX) 149 | { 150 | changed = true; 151 | controller.RightStickX = RightStickX; 152 | } 153 | 154 | short RightStickY = (short)((Math.Max(-127.0, currentState[8] - 128) / 127) * -32767); 155 | if (RightStickY == -32767) 156 | RightStickY = -32768; 157 | 158 | if (RightStickY != controller.RightStickY) 159 | { 160 | changed = true; 161 | controller.RightStickY = RightStickY; 162 | } 163 | 164 | if (controller.LeftTrigger != currentState[11]) 165 | { 166 | changed = true; 167 | controller.LeftTrigger = currentState[11]; 168 | } 169 | 170 | if (controller.RightTrigger != currentState[12]) 171 | { 172 | changed = true; 173 | controller.RightTrigger = currentState[12]; 174 | 175 | } 176 | } 177 | 178 | if (data.Status == HidDeviceData.ReadStatus.WaitTimedOut || (!changed && ((last_changed + timeout) < (DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond)))) 179 | { 180 | changed = true; 181 | } 182 | 183 | if (changed) 184 | { 185 | //Console.WriteLine("changed"); 186 | //Console.WriteLine((DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond)); 187 | byte[] outputReport = new byte[8]; 188 | scpBus.Report(index, controller.GetReport(), outputReport); 189 | 190 | if (outputReport[1] == 0x08) 191 | { 192 | byte bigMotor = outputReport[3]; 193 | byte smallMotor = outputReport[4]; 194 | rumble_mutex.WaitOne(); 195 | if (bigMotor != Vibration[2] || Vibration[1] != smallMotor) 196 | { 197 | Vibration[1] = smallMotor; 198 | Vibration[2] = bigMotor; 199 | } 200 | rumble_mutex.ReleaseMutex(); 201 | } 202 | 203 | if (last_mi_button != 0) 204 | { 205 | if ((last_mi_button + 100) < (DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond)) 206 | { 207 | last_mi_button = 0; 208 | controller.Buttons ^= X360Buttons.Logo; 209 | } 210 | } 211 | 212 | last_changed = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond; 213 | } 214 | } 215 | } 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /mi/HidLibrary/HidDevices.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Runtime.InteropServices; 6 | 7 | namespace HidLibrary 8 | { 9 | public class HidDevices 10 | { 11 | private static Guid _hidClassGuid = Guid.Empty; 12 | 13 | public static bool IsConnected(string devicePath) 14 | { 15 | return EnumerateDevices().Any(x => x.Path == devicePath); 16 | } 17 | 18 | public static HidDevice GetDevice(string devicePath) 19 | { 20 | return Enumerate(devicePath).FirstOrDefault(); 21 | } 22 | 23 | public static IEnumerable Enumerate() 24 | { 25 | return EnumerateDevices().Select(x => new HidDevice(x.Path, x.Description)); 26 | } 27 | 28 | public static IEnumerable Enumerate(string devicePath) 29 | { 30 | return EnumerateDevices().Where(x => x.Path == devicePath).Select(x => new HidDevice(x.Path, x.Description)); 31 | } 32 | 33 | public static IEnumerable Enumerate(int vendorId, params int[] productIds) 34 | { 35 | return EnumerateDevices().Select(x => new HidDevice(x.Path, x.Description)).Where(x => x.Attributes.VendorId == vendorId && 36 | productIds.Contains(x.Attributes.ProductId)); 37 | } 38 | 39 | public static IEnumerable Enumerate(int vendorId) 40 | { 41 | return EnumerateDevices().Select(x => new HidDevice(x.Path, x.Description)).Where(x => x.Attributes.VendorId == vendorId); 42 | } 43 | 44 | private class DeviceInfo { public string Path { get; set; } public string Description { get; set; } } 45 | 46 | private static IEnumerable EnumerateDevices() 47 | { 48 | var devices = new List(); 49 | var hidClass = HidClassGuid; 50 | var deviceInfoSet = NativeMethods.SetupDiGetClassDevs(ref hidClass, null, 0, NativeMethods.DIGCF_PRESENT | NativeMethods.DIGCF_DEVICEINTERFACE); 51 | 52 | if (deviceInfoSet.ToInt64() != NativeMethods.INVALID_HANDLE_VALUE) 53 | { 54 | var deviceInfoData = CreateDeviceInfoData(); 55 | var deviceIndex = 0; 56 | 57 | while (NativeMethods.SetupDiEnumDeviceInfo(deviceInfoSet, deviceIndex, ref deviceInfoData)) 58 | { 59 | deviceIndex += 1; 60 | 61 | var deviceInterfaceData = new NativeMethods.SP_DEVICE_INTERFACE_DATA(); 62 | deviceInterfaceData.cbSize = Marshal.SizeOf(deviceInterfaceData); 63 | var deviceInterfaceIndex = 0; 64 | 65 | while (NativeMethods.SetupDiEnumDeviceInterfaces(deviceInfoSet, ref deviceInfoData, ref hidClass, deviceInterfaceIndex, ref deviceInterfaceData)) 66 | { 67 | deviceInterfaceIndex++; 68 | var devicePath = GetDevicePath(deviceInfoSet, deviceInterfaceData); 69 | var description = GetBusReportedDeviceDescription(deviceInfoSet, ref deviceInfoData) ?? 70 | GetDeviceDescription(deviceInfoSet, ref deviceInfoData); 71 | devices.Add(new DeviceInfo { Path = devicePath, Description = description }); 72 | } 73 | } 74 | NativeMethods.SetupDiDestroyDeviceInfoList(deviceInfoSet); 75 | } 76 | return devices; 77 | } 78 | 79 | private static NativeMethods.SP_DEVINFO_DATA CreateDeviceInfoData() 80 | { 81 | var deviceInfoData = new NativeMethods.SP_DEVINFO_DATA(); 82 | 83 | deviceInfoData.cbSize = Marshal.SizeOf(deviceInfoData); 84 | deviceInfoData.DevInst = 0; 85 | deviceInfoData.ClassGuid = Guid.Empty; 86 | deviceInfoData.Reserved = IntPtr.Zero; 87 | 88 | return deviceInfoData; 89 | } 90 | 91 | private static string GetDevicePath(IntPtr deviceInfoSet, NativeMethods.SP_DEVICE_INTERFACE_DATA deviceInterfaceData) 92 | { 93 | var bufferSize = 0; 94 | var interfaceDetail = new NativeMethods.SP_DEVICE_INTERFACE_DETAIL_DATA { Size = IntPtr.Size == 4 ? 4 + Marshal.SystemDefaultCharSize : 8 }; 95 | 96 | NativeMethods.SetupDiGetDeviceInterfaceDetailBuffer(deviceInfoSet, ref deviceInterfaceData, IntPtr.Zero, 0, ref bufferSize, IntPtr.Zero); 97 | 98 | return NativeMethods.SetupDiGetDeviceInterfaceDetail(deviceInfoSet, ref deviceInterfaceData, ref interfaceDetail, bufferSize, ref bufferSize, IntPtr.Zero) ? 99 | interfaceDetail.DevicePath : null; 100 | } 101 | 102 | private static Guid HidClassGuid 103 | { 104 | get 105 | { 106 | if (_hidClassGuid.Equals(Guid.Empty)) NativeMethods.HidD_GetHidGuid(ref _hidClassGuid); 107 | return _hidClassGuid; 108 | } 109 | } 110 | 111 | private static string GetDeviceDescription(IntPtr deviceInfoSet, ref NativeMethods.SP_DEVINFO_DATA devinfoData) 112 | { 113 | var descriptionBuffer = new byte[1024]; 114 | 115 | var requiredSize = 0; 116 | var type = 0; 117 | 118 | NativeMethods.SetupDiGetDeviceRegistryProperty(deviceInfoSet, 119 | ref devinfoData, 120 | NativeMethods.SPDRP_DEVICEDESC, 121 | ref type, 122 | descriptionBuffer, 123 | descriptionBuffer.Length, 124 | ref requiredSize); 125 | 126 | return descriptionBuffer.ToUTF8String(); 127 | } 128 | 129 | private static string GetBusReportedDeviceDescription(IntPtr deviceInfoSet, ref NativeMethods.SP_DEVINFO_DATA devinfoData) 130 | { 131 | var descriptionBuffer = new byte[1024]; 132 | 133 | if (Environment.OSVersion.Version.Major > 5) 134 | { 135 | ulong propertyType = 0; 136 | var requiredSize = 0; 137 | 138 | var _continue = NativeMethods.SetupDiGetDeviceProperty(deviceInfoSet, 139 | ref devinfoData, 140 | ref NativeMethods.DEVPKEY_Device_BusReportedDeviceDesc, 141 | ref propertyType, 142 | descriptionBuffer, 143 | descriptionBuffer.Length, 144 | ref requiredSize, 145 | 0); 146 | 147 | if (_continue) return descriptionBuffer.ToUTF16String(); 148 | } 149 | return null; 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /mi/AboutBox.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace mi 2 | { 3 | partial class AboutBox 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | protected override void Dispose(bool disposing) 14 | { 15 | if (disposing && (components != null)) 16 | { 17 | components.Dispose(); 18 | } 19 | base.Dispose(disposing); 20 | } 21 | 22 | #region Windows Form Designer generated code 23 | 24 | /// 25 | /// Required method for Designer support - do not modify 26 | /// the contents of this method with the code editor. 27 | /// 28 | private void InitializeComponent() 29 | { 30 | this.tableLayoutPanel = new System.Windows.Forms.TableLayoutPanel(); 31 | this.labelProductName = new System.Windows.Forms.Label(); 32 | this.labelVersion = new System.Windows.Forms.Label(); 33 | this.labelCopyright = new System.Windows.Forms.Label(); 34 | this.textBoxDescription = new System.Windows.Forms.TextBox(); 35 | this.okButton = new System.Windows.Forms.Button(); 36 | this.tableLayoutPanel.SuspendLayout(); 37 | this.SuspendLayout(); 38 | // 39 | // tableLayoutPanel 40 | // 41 | this.tableLayoutPanel.ColumnCount = 2; 42 | this.tableLayoutPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 3.117506F)); 43 | this.tableLayoutPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 96.88249F)); 44 | this.tableLayoutPanel.Controls.Add(this.labelProductName, 1, 0); 45 | this.tableLayoutPanel.Controls.Add(this.labelVersion, 1, 1); 46 | this.tableLayoutPanel.Controls.Add(this.labelCopyright, 1, 2); 47 | this.tableLayoutPanel.Controls.Add(this.textBoxDescription, 1, 4); 48 | this.tableLayoutPanel.Controls.Add(this.okButton, 1, 5); 49 | this.tableLayoutPanel.Dock = System.Windows.Forms.DockStyle.Fill; 50 | this.tableLayoutPanel.Location = new System.Drawing.Point(9, 9); 51 | this.tableLayoutPanel.Name = "tableLayoutPanel"; 52 | this.tableLayoutPanel.RowCount = 6; 53 | this.tableLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 10F)); 54 | this.tableLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 10F)); 55 | this.tableLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 10F)); 56 | this.tableLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 10F)); 57 | this.tableLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F)); 58 | this.tableLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 10F)); 59 | this.tableLayoutPanel.Size = new System.Drawing.Size(417, 265); 60 | this.tableLayoutPanel.TabIndex = 0; 61 | // 62 | // labelProductName 63 | // 64 | this.labelProductName.Dock = System.Windows.Forms.DockStyle.Fill; 65 | this.labelProductName.Location = new System.Drawing.Point(19, 0); 66 | this.labelProductName.Margin = new System.Windows.Forms.Padding(6, 0, 3, 0); 67 | this.labelProductName.MaximumSize = new System.Drawing.Size(0, 17); 68 | this.labelProductName.Name = "labelProductName"; 69 | this.labelProductName.Size = new System.Drawing.Size(395, 17); 70 | this.labelProductName.TabIndex = 19; 71 | this.labelProductName.Text = "Product Name"; 72 | this.labelProductName.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; 73 | // 74 | // labelVersion 75 | // 76 | this.labelVersion.Dock = System.Windows.Forms.DockStyle.Fill; 77 | this.labelVersion.Location = new System.Drawing.Point(19, 26); 78 | this.labelVersion.Margin = new System.Windows.Forms.Padding(6, 0, 3, 0); 79 | this.labelVersion.MaximumSize = new System.Drawing.Size(0, 17); 80 | this.labelVersion.Name = "labelVersion"; 81 | this.labelVersion.Size = new System.Drawing.Size(395, 17); 82 | this.labelVersion.TabIndex = 0; 83 | this.labelVersion.Text = "Version"; 84 | this.labelVersion.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; 85 | // 86 | // labelCopyright 87 | // 88 | this.labelCopyright.Dock = System.Windows.Forms.DockStyle.Fill; 89 | this.labelCopyright.Location = new System.Drawing.Point(19, 52); 90 | this.labelCopyright.Margin = new System.Windows.Forms.Padding(6, 0, 3, 0); 91 | this.labelCopyright.MaximumSize = new System.Drawing.Size(0, 17); 92 | this.labelCopyright.Name = "labelCopyright"; 93 | this.labelCopyright.Size = new System.Drawing.Size(395, 17); 94 | this.labelCopyright.TabIndex = 21; 95 | this.labelCopyright.Text = "Copyright"; 96 | this.labelCopyright.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; 97 | // 98 | // textBoxDescription 99 | // 100 | this.textBoxDescription.Dock = System.Windows.Forms.DockStyle.Fill; 101 | this.textBoxDescription.Location = new System.Drawing.Point(19, 107); 102 | this.textBoxDescription.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3); 103 | this.textBoxDescription.Multiline = true; 104 | this.textBoxDescription.Name = "textBoxDescription"; 105 | this.textBoxDescription.ReadOnly = true; 106 | this.textBoxDescription.ScrollBars = System.Windows.Forms.ScrollBars.Both; 107 | this.textBoxDescription.Size = new System.Drawing.Size(395, 126); 108 | this.textBoxDescription.TabIndex = 23; 109 | this.textBoxDescription.TabStop = false; 110 | this.textBoxDescription.Text = "Description"; 111 | // 112 | // okButton 113 | // 114 | this.okButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); 115 | this.okButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; 116 | this.okButton.Location = new System.Drawing.Point(339, 239); 117 | this.okButton.Name = "okButton"; 118 | this.okButton.Size = new System.Drawing.Size(75, 23); 119 | this.okButton.TabIndex = 24; 120 | this.okButton.Text = "&OK"; 121 | // 122 | // AboutBox 123 | // 124 | this.AcceptButton = this.okButton; 125 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 126 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 127 | this.ClientSize = new System.Drawing.Size(435, 283); 128 | this.Controls.Add(this.tableLayoutPanel); 129 | this.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 130 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; 131 | this.MaximizeBox = false; 132 | this.MinimizeBox = false; 133 | this.Name = "AboutBox"; 134 | this.Padding = new System.Windows.Forms.Padding(9); 135 | this.ShowIcon = false; 136 | this.ShowInTaskbar = false; 137 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; 138 | this.Text = "About Mi controller"; 139 | this.Load += new System.EventHandler(this.AboutBox_Load); 140 | this.tableLayoutPanel.ResumeLayout(false); 141 | this.tableLayoutPanel.PerformLayout(); 142 | this.ResumeLayout(false); 143 | 144 | } 145 | 146 | #endregion 147 | 148 | private System.Windows.Forms.TableLayoutPanel tableLayoutPanel; 149 | private System.Windows.Forms.Label labelProductName; 150 | private System.Windows.Forms.Label labelVersion; 151 | private System.Windows.Forms.Label labelCopyright; 152 | private System.Windows.Forms.TextBox textBoxDescription; 153 | private System.Windows.Forms.Button okButton; 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /mi/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using HidLibrary; 7 | using System.Windows.Forms; 8 | using ScpDriverInterface; 9 | using System.Threading; 10 | using System.Runtime.InteropServices; 11 | 12 | namespace mi 13 | { 14 | class Program 15 | { 16 | private static ScpBus global_scpBus; 17 | 18 | static bool ConsoleEventCallback(int eventType) 19 | { 20 | if (eventType == 2) 21 | { 22 | global_scpBus.UnplugAll(); 23 | } 24 | return false; 25 | } 26 | 27 | static ConsoleEventDelegate handler; // Keeps it from getting garbage collected 28 | // Pinvoke 29 | private delegate bool ConsoleEventDelegate(int eventType); 30 | 31 | [DllImport("kernel32.dll", SetLastError = true)] 32 | private static extern bool SetConsoleCtrlHandler(ConsoleEventDelegate callback, bool add); 33 | 34 | 35 | 36 | public static string ByteArrayToHexString(byte[] bytes) 37 | { 38 | return string.Join(string.Empty, Array.ConvertAll(bytes, b => b.ToString("X2"))); 39 | } 40 | 41 | static Mutex singleInstanceMutex = new Mutex(true, "{298c40ea-b004-4a7f-9910-d3bf3591b18b}"); 42 | 43 | [STAThreadAttribute] 44 | static void Main(string[] args) 45 | { 46 | if (!IsSingleInstance()) Environment.Exit(0); 47 | NIcon = new NotifyIcon(); 48 | ScpBus scpBus = new ScpBus(); 49 | scpBus.UnplugAll(); 50 | global_scpBus = scpBus; 51 | 52 | handler = new ConsoleEventDelegate(ConsoleEventCallback); 53 | SetConsoleCtrlHandler(handler, true); 54 | 55 | Thread.Sleep(400); 56 | var controllersManager = new Thread(() => ManageControllers(scpBus)); 57 | 58 | 59 | Application.EnableVisualStyles(); 60 | Application.SetCompatibleTextRenderingDefault(false); 61 | 62 | try 63 | { 64 | try 65 | { 66 | using (var pi = new ProcessIcon()) 67 | { 68 | pi.Display(); 69 | controllersManager.Start(); 70 | Application.Run(); 71 | } 72 | } 73 | catch (Exception ex) 74 | { 75 | MessageBox.Show(ex.Message, "Program Terminated Unexpectedly", 76 | MessageBoxButtons.OK, MessageBoxIcon.Error); 77 | } 78 | controllersManager.Abort(); 79 | scpBus.UnplugAll(); 80 | foreach (var device in Gamepads.Select(g => g.Device)) 81 | { 82 | device.CloseDevice(); 83 | } 84 | singleInstanceMutex.ReleaseMutex(); 85 | } 86 | finally 87 | { 88 | Environment.Exit(0); 89 | } 90 | } 91 | 92 | public static NotifyIcon NIcon { get; set; } 93 | 94 | private static bool IsSingleInstance() 95 | { 96 | if (singleInstanceMutex.WaitOne(TimeSpan.Zero, true)) 97 | { 98 | return true; 99 | } 100 | else 101 | { 102 | MessageBox.Show("Another copy is already running"); 103 | return false; 104 | } 105 | } 106 | 107 | private static void ManageControllers(ScpBus scpBus) 108 | { 109 | var nrConnected = 0; 110 | while (true) 111 | { 112 | var compatibleDevices = HidDevices.Enumerate(0x2717, 0x3144).ToList(); 113 | var existingDevices = Gamepads.Select(g => g.Device).ToList(); 114 | var newDevices = compatibleDevices.Where(d => !existingDevices.Select(e => e.DevicePath).Contains(d.DevicePath)); 115 | foreach (var gamepad in Gamepads.ToList()) 116 | { 117 | if (!gamepad.check_connected()) 118 | { 119 | gamepad.unplug(); 120 | Gamepads.Remove(gamepad); 121 | } 122 | } 123 | foreach (var deviceInstance in newDevices) 124 | { 125 | var device = deviceInstance; 126 | try 127 | { 128 | device.OpenDevice(DeviceMode.Overlapped, DeviceMode.Overlapped, ShareMode.Exclusive); 129 | } 130 | catch 131 | { 132 | InformUser("Could not open gamepad in exclusive mode. Try reconnecting the device."); 133 | var instanceId = devicePathToInstanceId(deviceInstance.DevicePath); 134 | if (TryReEnableDevice(instanceId)) 135 | { 136 | try 137 | { 138 | device.OpenDevice(DeviceMode.Overlapped, DeviceMode.Overlapped, ShareMode.Exclusive); 139 | //InformUser("Opened in exclusive mode."); 140 | } 141 | catch 142 | { 143 | device.OpenDevice(DeviceMode.Overlapped, DeviceMode.Overlapped, ShareMode.ShareRead | ShareMode.ShareWrite); 144 | //InformUser("Opened in shared mode."); 145 | } 146 | } 147 | else 148 | { 149 | device.OpenDevice(DeviceMode.Overlapped, DeviceMode.Overlapped, ShareMode.ShareRead | ShareMode.ShareWrite); 150 | //InformUser("Opened in shared mode."); 151 | } 152 | } 153 | 154 | byte[] vibration = { 0x20, 0x00, 0x00 }; 155 | if (device.WriteFeatureData(vibration) == false) 156 | { 157 | InformUser("Could not write to gamepad (is it closed?), skipping"); 158 | device.CloseDevice(); 159 | continue; 160 | } 161 | 162 | byte[] serialNumber; 163 | byte[] product; 164 | device.ReadSerialNumber(out serialNumber); 165 | device.ReadProduct(out product); 166 | 167 | 168 | var usedIndexes = Gamepads.Select(g => g.Index); 169 | var index = 1; 170 | while (usedIndexes.Contains(index)) 171 | { 172 | index++; 173 | } 174 | Gamepads.Add(new Xiaomi_gamepad(device, scpBus, index)); 175 | } 176 | if (Gamepads.Count != nrConnected) 177 | { 178 | InformUser($"{Gamepads.Count} controllers connected"); 179 | } 180 | Thread.Sleep(1000); 181 | } 182 | } 183 | 184 | private static void InformUser(string text) 185 | { 186 | NIcon.Text = "Export Datatable Utlity"; 187 | NIcon.Visible = true; 188 | NIcon.BalloonTipTitle = "Mi controller"; 189 | NIcon.BalloonTipText = text; 190 | NIcon.ShowBalloonTip(100); 191 | //var content = new ToastContent() 192 | //{ 193 | // Visual = new ToastVisual 194 | // { 195 | // BindingGeneric = new ToastBindingGeneric() 196 | // { 197 | // AppLogoOverride = new ToastGenericAppLogo 198 | // { 199 | // HintCrop = ToastGenericAppLogoCrop.Circle, 200 | // Source = "http://messageme.com/lei/profile.jpg" 201 | // }, 202 | // Children = 203 | // { 204 | // new AdaptiveText {Text = text }, 205 | // }, 206 | // Attribution = new ToastGenericAttributionText 207 | // { 208 | // Text = "Alert" 209 | // }, 210 | // } 211 | // } 212 | //}; 213 | //var toast = new ToastNotification(content.GetContent()); 214 | 215 | //// Display toast 216 | //ToastNotificationManager.CreateToastNotifier().Show(toast); 217 | } 218 | 219 | public static List Gamepads { get; set; } = new List(); 220 | 221 | private static bool TryReEnableDevice(string deviceInstanceId) 222 | { 223 | try 224 | { 225 | Guid hidGuid = new Guid(); 226 | HidLibrary.NativeMethods.HidD_GetHidGuid(ref hidGuid); 227 | IntPtr deviceInfoSet = HidLibrary.NativeMethods.SetupDiGetClassDevs(ref hidGuid, deviceInstanceId, 0, 228 | HidLibrary.NativeMethods.DIGCF_PRESENT | HidLibrary.NativeMethods.DIGCF_DEVICEINTERFACE); 229 | HidLibrary.NativeMethods.SP_DEVINFO_DATA deviceInfoData = new HidLibrary.NativeMethods.SP_DEVINFO_DATA(); 230 | deviceInfoData.cbSize = Marshal.SizeOf(deviceInfoData); 231 | var success = HidLibrary.NativeMethods.SetupDiEnumDeviceInfo(deviceInfoSet, 0, ref deviceInfoData); 232 | if (!success) 233 | { 234 | InformUser("Error getting device info data, error code = " + Marshal.GetLastWin32Error()); 235 | } 236 | success = HidLibrary.NativeMethods.SetupDiEnumDeviceInfo(deviceInfoSet, 1, ref deviceInfoData); 237 | // Checks that we have a unique device 238 | if (success) 239 | { 240 | InformUser("Can't find unique device"); 241 | } 242 | 243 | HidLibrary.NativeMethods.SP_PROPCHANGE_PARAMS propChangeParams = new HidLibrary.NativeMethods.SP_PROPCHANGE_PARAMS(); 244 | propChangeParams.classInstallHeader.cbSize = Marshal.SizeOf(propChangeParams.classInstallHeader); 245 | propChangeParams.classInstallHeader.installFunction = HidLibrary.NativeMethods.DIF_PROPERTYCHANGE; 246 | propChangeParams.stateChange = HidLibrary.NativeMethods.DICS_DISABLE; 247 | propChangeParams.scope = HidLibrary.NativeMethods.DICS_FLAG_GLOBAL; 248 | propChangeParams.hwProfile = 0; 249 | success = HidLibrary.NativeMethods.SetupDiSetClassInstallParams(deviceInfoSet, ref deviceInfoData, 250 | ref propChangeParams, Marshal.SizeOf(propChangeParams)); 251 | if (!success) 252 | { 253 | InformUser("Error setting class install params, error code = " + Marshal.GetLastWin32Error()); 254 | return false; 255 | } 256 | success = HidLibrary.NativeMethods.SetupDiCallClassInstaller(HidLibrary.NativeMethods.DIF_PROPERTYCHANGE, 257 | deviceInfoSet, ref deviceInfoData); 258 | if (!success) 259 | { 260 | InformUser("Error disabling device, error code = " + Marshal.GetLastWin32Error()); 261 | return false; 262 | 263 | } 264 | propChangeParams.stateChange = HidLibrary.NativeMethods.DICS_ENABLE; 265 | success = HidLibrary.NativeMethods.SetupDiSetClassInstallParams(deviceInfoSet, ref deviceInfoData, 266 | ref propChangeParams, Marshal.SizeOf(propChangeParams)); 267 | if (!success) 268 | { 269 | InformUser("Error setting class install params, error code = " + Marshal.GetLastWin32Error()); 270 | return false; 271 | } 272 | success = HidLibrary.NativeMethods.SetupDiCallClassInstaller(HidLibrary.NativeMethods.DIF_PROPERTYCHANGE, 273 | deviceInfoSet, ref deviceInfoData); 274 | if (!success) 275 | { 276 | InformUser("Error enabling device, error code = " + Marshal.GetLastWin32Error()); 277 | return false; 278 | } 279 | 280 | HidLibrary.NativeMethods.SetupDiDestroyDeviceInfoList(deviceInfoSet); 281 | 282 | return true; 283 | } 284 | catch 285 | { 286 | InformUser("Can't re-enable device"); 287 | return false; 288 | } 289 | } 290 | 291 | private static string devicePathToInstanceId(string devicePath) 292 | { 293 | string deviceInstanceId = devicePath; 294 | deviceInstanceId = deviceInstanceId.Remove(0, deviceInstanceId.LastIndexOf('\\') + 1); 295 | deviceInstanceId = deviceInstanceId.Remove(deviceInstanceId.LastIndexOf('{')); 296 | deviceInstanceId = deviceInstanceId.Replace('#', '\\'); 297 | if (deviceInstanceId.EndsWith("\\")) 298 | { 299 | deviceInstanceId = deviceInstanceId.Remove(deviceInstanceId.Length - 1); 300 | } 301 | return deviceInstanceId; 302 | 303 | } 304 | } 305 | } 306 | -------------------------------------------------------------------------------- /mi/ScpDriverInterface/ScpBus.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * ScpDriverInterface - by Mogzol (and of course Scarlet.Crush) - Jan, 2016 3 | * 4 | * This is a simple little DLL which allows you to use Scarlet.Crush's SCP Virtual 5 | * Bus Driver to emulate XBox 360 Controllers. 6 | * 7 | * Most of the code here has been ripped out of his ScpControl source code, mostly 8 | * from the ScpDevice and BusDevice classes, so obviously credit and major props to 9 | * Scarlet.Crush, without him this wouldn't be possible. You can download his 10 | * original source code from here: 11 | * http://forums.pcsx2.net/Thread-XInput-Wrapper-for-DS3-and-Play-com-USB-Dual-DS2-Controller 12 | * 13 | * Note that for this to work the SCP Virtual Bus Driver must be installed. (Duh) 14 | */ 15 | 16 | using System; 17 | using System.Globalization; 18 | using System.IO; 19 | using System.Runtime.InteropServices; 20 | using Microsoft.Win32.SafeHandles; 21 | [assembly: CLSCompliant(true)] 22 | 23 | namespace ScpDriverInterface 24 | { 25 | /// 26 | /// Emulates XBox 360 controllers via Scarlet.Crush's SCP Virtual Bus Driver. 27 | /// 28 | public class ScpBus : IDisposable 29 | { 30 | private const string SCP_BUS_CLASS_GUID = "{F679F562-3164-42CE-A4DB-E7DDBE723909}"; 31 | private const int ReportSize = 28; 32 | 33 | private readonly SafeFileHandle _deviceHandle; 34 | 35 | /// 36 | /// Creates a new ScpBus object, which will then try to get a handle to the SCP Virtual Bus device. If it is unable to get the handle, an IOException will be thrown. 37 | /// 38 | public ScpBus() : this(0) { } 39 | 40 | /// 41 | /// Creates a new ScpBus object, which will then try to get a handle to the SCP Virtual Bus device. If it is unable to get the handle, an IOException will be thrown. 42 | /// 43 | /// Specifies which SCP Virtual Bus device to use. This is 0-based. 44 | public ScpBus(int instance) 45 | { 46 | string devicePath = ""; 47 | 48 | if (Find(new Guid(SCP_BUS_CLASS_GUID), ref devicePath, instance)) 49 | { 50 | _deviceHandle = GetHandle(devicePath); 51 | } 52 | else 53 | { 54 | throw new IOException("SCP Virtual Bus Device not found"); 55 | } 56 | } 57 | 58 | /// 59 | /// Creates a new ScpBus object, which will then try to get a handle to the specified SCP Virtual Bus device. If it is unable to get the handle, an IOException will be thrown. 60 | /// 61 | /// The path to the SCP Virtual Bus device that you want to use. 62 | public ScpBus(string devicePath) 63 | { 64 | _deviceHandle = GetHandle(devicePath); 65 | } 66 | 67 | /// 68 | /// Closes the handle to the SCP Virtual Bus device. Call this when you are done with your instance of ScpBus. 69 | /// 70 | /// (This method does the same thing as the Dispose() method. Use one or the other.) 71 | /// 72 | public void Close() 73 | { 74 | Dispose(); 75 | } 76 | 77 | /// 78 | /// Closes the handle to the SCP Virtual Bus device. Call this when you are done with your instance of ScpBus. 79 | /// 80 | public void Dispose() 81 | { 82 | Dispose(true); 83 | GC.SuppressFinalize(this); 84 | } 85 | 86 | protected virtual void Dispose(bool disposing) 87 | { 88 | if (_deviceHandle != null && !_deviceHandle.IsInvalid) 89 | { 90 | _deviceHandle.Dispose(); 91 | } 92 | } 93 | 94 | /// 95 | /// Plugs in an emulated XBox 360 controller. 96 | /// 97 | /// Used to identify the controller. Give each controller you plug in a different number. Number must be non-zero. 98 | /// True if the operation was successful, false otherwise. 99 | public bool PlugIn(int controllerNumber) 100 | { 101 | if (_deviceHandle.IsInvalid) 102 | throw new ObjectDisposedException("SCP Virtual Bus device handle is closed"); 103 | 104 | int transfered = 0; 105 | byte[] buffer = new byte[16]; 106 | 107 | buffer[0] = 0x10; 108 | buffer[1] = 0x00; 109 | buffer[2] = 0x00; 110 | buffer[3] = 0x00; 111 | 112 | buffer[4] = (byte)((controllerNumber) & 0xFF); 113 | buffer[5] = (byte)((controllerNumber >> 8) & 0xFF); 114 | buffer[6] = (byte)((controllerNumber >> 16) & 0xFF); 115 | buffer[7] = (byte)((controllerNumber >> 24) & 0xFF); 116 | 117 | return NativeMethods.DeviceIoControl(_deviceHandle, 0x2A4000, buffer, buffer.Length, null, 0, ref transfered, IntPtr.Zero); 118 | } 119 | 120 | /// 121 | /// Unplugs an emulated XBox 360 controller. 122 | /// 123 | /// The controller you want to unplug. 124 | /// True if the operation was successful, false otherwise. 125 | public bool Unplug(int controllerNumber) 126 | { 127 | if (_deviceHandle.IsInvalid) 128 | throw new ObjectDisposedException("SCP Virtual Bus device handle is closed"); 129 | 130 | int transfered = 0; 131 | byte[] buffer = new Byte[16]; 132 | 133 | buffer[0] = 0x10; 134 | buffer[1] = 0x00; 135 | buffer[2] = 0x00; 136 | buffer[3] = 0x00; 137 | 138 | buffer[4] = (byte)((controllerNumber) & 0xFF); 139 | buffer[5] = (byte)((controllerNumber >> 8) & 0xFF); 140 | buffer[6] = (byte)((controllerNumber >> 16) & 0xFF); 141 | buffer[7] = (byte)((controllerNumber >> 24) & 0xFF); 142 | 143 | return NativeMethods.DeviceIoControl(_deviceHandle, 0x2A4004, buffer, buffer.Length, null, 0, ref transfered, IntPtr.Zero); 144 | } 145 | 146 | /// 147 | /// Unplugs all emulated XBox 360 controllers. 148 | /// 149 | /// True if the operation was successful, false otherwise. 150 | public bool UnplugAll() 151 | { 152 | if (_deviceHandle.IsInvalid) 153 | throw new ObjectDisposedException("SCP Virtual Bus device handle is closed"); 154 | 155 | int transfered = 0; 156 | byte[] buffer = new byte[16]; 157 | 158 | buffer[0] = 0x10; 159 | buffer[1] = 0x00; 160 | buffer[2] = 0x00; 161 | buffer[3] = 0x00; 162 | 163 | return NativeMethods.DeviceIoControl(_deviceHandle, 0x2A4004, buffer, buffer.Length, null, 0, ref transfered, IntPtr.Zero); 164 | } 165 | 166 | /// 167 | /// Sends an input report for the current state of the specified emulated XBox 360 controller. Note: Only use this if you don't care about rumble data, otherwise use the 3-parameter version of Report(). 168 | /// 169 | /// The controller to report. 170 | /// The controller report. If using the included X360Controller class, this can be generated with the GetReport() method. Otherwise see http://free60.org/wiki/GamePad#Input_report for details. 171 | /// True if the operation was successful, false otherwise. 172 | public bool Report(int controllerNumber, byte[] controllerReport) 173 | { 174 | return Report(controllerNumber, controllerReport, null); 175 | } 176 | 177 | /// 178 | /// Sends an input report for the current state of the specified emulated XBox 360 controller. If you care about rumble data, make sure you check the output report for rumble data every time you call this. 179 | /// 180 | /// The controller to report. 181 | /// The controller report. If using the included X360Controller class, this can be generated with the GetReport() method. Otherwise see http://free60.org/wiki/GamePad#Input_report for details. 182 | /// The buffer for the output report, which takes the form specified here: http://free60.org/wiki/GamePad#Output_report. Use an 8-byte buffer if you care about rumble data, or null otherwise. 183 | /// True if the operation was successful, false otherwise. 184 | public bool Report(int controllerNumber, byte[] controllerReport, byte[] outputBuffer) 185 | { 186 | if (_deviceHandle.IsInvalid) 187 | throw new ObjectDisposedException("SCP Virtual Bus device handle is closed"); 188 | 189 | byte[] head = new byte[8]; 190 | 191 | head[0] = 0x1C; 192 | head[4] = (byte)((controllerNumber) & 0xFF); 193 | head[5] = (byte)((controllerNumber >> 8) & 0xFF); 194 | head[6] = (byte)((controllerNumber >> 16) & 0xFF); 195 | head[7] = (byte)((controllerNumber >> 24) & 0xFF); 196 | 197 | byte[] fullReport = new byte[28]; 198 | 199 | Buffer.BlockCopy(head, 0, fullReport, 0, head.Length); 200 | Buffer.BlockCopy(controllerReport, 0, fullReport, head.Length, controllerReport.Length); 201 | 202 | int transferred = 0; 203 | return NativeMethods.DeviceIoControl(_deviceHandle, 0x2A400C, fullReport, fullReport.Length, outputBuffer, outputBuffer?.Length ?? 0, ref transferred, IntPtr.Zero) && transferred > 0; 204 | } 205 | 206 | private static bool Find(Guid target, ref string path, int instance = 0) 207 | { 208 | IntPtr detailDataBuffer = IntPtr.Zero; 209 | IntPtr deviceInfoSet = IntPtr.Zero; 210 | 211 | try 212 | { 213 | NativeMethods.SP_DEVICE_INTERFACE_DATA DeviceInterfaceData = new NativeMethods.SP_DEVICE_INTERFACE_DATA(), da = new NativeMethods.SP_DEVICE_INTERFACE_DATA(); 214 | int bufferSize = 0, memberIndex = 0; 215 | 216 | deviceInfoSet = NativeMethods.SetupDiGetClassDevs(ref target, IntPtr.Zero, IntPtr.Zero, NativeMethods.DIGCF_PRESENT | NativeMethods.DIGCF_DEVICEINTERFACE); 217 | 218 | DeviceInterfaceData.cbSize = da.cbSize = Marshal.SizeOf(DeviceInterfaceData); 219 | 220 | while (NativeMethods.SetupDiEnumDeviceInterfaces(deviceInfoSet, IntPtr.Zero, ref target, memberIndex, ref DeviceInterfaceData)) 221 | { 222 | NativeMethods.SetupDiGetDeviceInterfaceDetail(deviceInfoSet, ref DeviceInterfaceData, IntPtr.Zero, 0, ref bufferSize, ref da); 223 | detailDataBuffer = Marshal.AllocHGlobal(bufferSize); 224 | 225 | Marshal.WriteInt32(detailDataBuffer, (IntPtr.Size == 4) ? (4 + Marshal.SystemDefaultCharSize) : 8); 226 | 227 | if (NativeMethods.SetupDiGetDeviceInterfaceDetail(deviceInfoSet, ref DeviceInterfaceData, detailDataBuffer, bufferSize, ref bufferSize, ref da)) 228 | { 229 | IntPtr pDevicePathName = detailDataBuffer + 4; 230 | 231 | path = Marshal.PtrToStringAuto(pDevicePathName).ToUpper(CultureInfo.InvariantCulture); 232 | Marshal.FreeHGlobal(detailDataBuffer); 233 | 234 | if (memberIndex == instance) return true; 235 | } 236 | else Marshal.FreeHGlobal(detailDataBuffer); 237 | 238 | 239 | memberIndex++; 240 | } 241 | } 242 | finally 243 | { 244 | if (deviceInfoSet != IntPtr.Zero) 245 | { 246 | NativeMethods.SetupDiDestroyDeviceInfoList(deviceInfoSet); 247 | } 248 | } 249 | 250 | return false; 251 | } 252 | 253 | private static SafeFileHandle GetHandle(string devicePath) 254 | { 255 | devicePath = devicePath.ToUpper(CultureInfo.InvariantCulture); 256 | 257 | SafeFileHandle handle = NativeMethods.CreateFile(devicePath, (NativeMethods.GENERIC_WRITE | NativeMethods.GENERIC_READ), NativeMethods.FILE_SHARE_READ | NativeMethods.FILE_SHARE_WRITE, IntPtr.Zero, NativeMethods.OPEN_EXISTING, NativeMethods.FILE_ATTRIBUTE_NORMAL | NativeMethods.FILE_FLAG_OVERLAPPED, UIntPtr.Zero); 258 | 259 | if (handle == null || handle.IsInvalid) 260 | { 261 | throw new IOException("Unable to get SCP Virtual Bus Device handle"); 262 | } 263 | 264 | return handle; 265 | } 266 | } 267 | } 268 | 269 | 270 | -------------------------------------------------------------------------------- /mi/HidLibrary/NativeMethods.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace HidLibrary 5 | { 6 | internal static class NativeMethods 7 | { 8 | internal const int FILE_FLAG_OVERLAPPED = 0x40000000; 9 | internal const short FILE_SHARE_READ = 0x1; 10 | internal const short FILE_SHARE_WRITE = 0x2; 11 | internal const uint GENERIC_READ = 0x80000000; 12 | internal const uint GENERIC_WRITE = 0x40000000; 13 | internal const int ACCESS_NONE = 0; 14 | internal const int INVALID_HANDLE_VALUE = -1; 15 | internal const short OPEN_EXISTING = 3; 16 | internal const int WAIT_TIMEOUT = 0x102; 17 | internal const uint WAIT_OBJECT_0 = 0; 18 | internal const uint WAIT_FAILED = 0xffffffff; 19 | 20 | internal const int WAIT_INFINITE = 0xffff; 21 | [StructLayout(LayoutKind.Sequential)] 22 | internal struct OVERLAPPED 23 | { 24 | public int Internal; 25 | public int InternalHigh; 26 | public int Offset; 27 | public int OffsetHigh; 28 | public int hEvent; 29 | } 30 | 31 | [StructLayout(LayoutKind.Sequential)] 32 | internal struct SECURITY_ATTRIBUTES 33 | { 34 | public int nLength; 35 | public IntPtr lpSecurityDescriptor; 36 | public bool bInheritHandle; 37 | } 38 | 39 | [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true, CharSet = CharSet.Auto)] 40 | static internal extern bool CancelIo(IntPtr hFile); 41 | 42 | [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true, CharSet = CharSet.Auto)] 43 | static internal extern bool CancelIoEx(IntPtr hFile, IntPtr lpOverlapped); 44 | 45 | [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true, CharSet = CharSet.Auto)] 46 | static internal extern bool CloseHandle(IntPtr hObject); 47 | 48 | [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true, CharSet = CharSet.Auto)] 49 | static internal extern bool CancelSynchronousIo(IntPtr hObject); 50 | 51 | [DllImport("kernel32.dll", CharSet = CharSet.Auto)] 52 | static internal extern IntPtr CreateEvent(ref SECURITY_ATTRIBUTES securityAttributes, int bManualReset, int bInitialState, string lpName); 53 | 54 | [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] 55 | static internal extern IntPtr CreateFile(string lpFileName, uint dwDesiredAccess, int dwShareMode, ref SECURITY_ATTRIBUTES lpSecurityAttributes, int dwCreationDisposition, int dwFlagsAndAttributes, int hTemplateFile); 56 | 57 | [DllImport("kernel32.dll", SetLastError = true)] 58 | static internal extern bool ReadFile(IntPtr hFile, [Out] byte[] lpBuffer, uint nNumberOfBytesToRead, out uint lpNumberOfBytesRead, [In] ref System.Threading.NativeOverlapped lpOverlapped); 59 | 60 | [DllImport("kernel32.dll")] 61 | static internal extern uint WaitForSingleObject(IntPtr hHandle, int dwMilliseconds); 62 | 63 | [DllImport("kernel32.dll")] 64 | static internal extern bool GetOverlappedResult(IntPtr hFile, [In] ref System.Threading.NativeOverlapped lpOverlapped, out uint lpNumberOfBytesRead, bool bwait); 65 | 66 | [DllImport("kernel32.dll")] 67 | static internal extern bool WriteFile(IntPtr hFile, byte[] lpBuffer, uint nNumberOfBytesToWrite, out uint lpNumberOfBytesWritten, [In] ref System.Threading.NativeOverlapped lpOverlapped); 68 | 69 | internal const int DBT_DEVICEARRIVAL = 0x8000; 70 | internal const int DBT_DEVICEREMOVECOMPLETE = 0x8004; 71 | internal const int DBT_DEVTYP_DEVICEINTERFACE = 5; 72 | internal const int DBT_DEVTYP_HANDLE = 6; 73 | internal const int DEVICE_NOTIFY_ALL_INTERFACE_CLASSES = 4; 74 | internal const int DEVICE_NOTIFY_SERVICE_HANDLE = 1; 75 | internal const int DEVICE_NOTIFY_WINDOW_HANDLE = 0; 76 | internal const int WM_DEVICECHANGE = 0x219; 77 | internal const short DIGCF_PRESENT = 0x2; 78 | internal const short DIGCF_DEVICEINTERFACE = 0x10; 79 | internal const int DIGCF_ALLCLASSES = 0x4; 80 | 81 | internal const int MAX_DEV_LEN = 1000; 82 | internal const int SPDRP_ADDRESS = 0x1c; 83 | internal const int SPDRP_BUSNUMBER = 0x15; 84 | internal const int SPDRP_BUSTYPEGUID = 0x13; 85 | internal const int SPDRP_CAPABILITIES = 0xf; 86 | internal const int SPDRP_CHARACTERISTICS = 0x1b; 87 | internal const int SPDRP_CLASS = 7; 88 | internal const int SPDRP_CLASSGUID = 8; 89 | internal const int SPDRP_COMPATIBLEIDS = 2; 90 | internal const int SPDRP_CONFIGFLAGS = 0xa; 91 | internal const int SPDRP_DEVICE_POWER_DATA = 0x1e; 92 | internal const int SPDRP_DEVICEDESC = 0; 93 | internal const int SPDRP_DEVTYPE = 0x19; 94 | internal const int SPDRP_DRIVER = 9; 95 | internal const int SPDRP_ENUMERATOR_NAME = 0x16; 96 | internal const int SPDRP_EXCLUSIVE = 0x1a; 97 | internal const int SPDRP_FRIENDLYNAME = 0xc; 98 | internal const int SPDRP_HARDWAREID = 1; 99 | internal const int SPDRP_LEGACYBUSTYPE = 0x14; 100 | internal const int SPDRP_LOCATION_INFORMATION = 0xd; 101 | internal const int SPDRP_LOWERFILTERS = 0x12; 102 | internal const int SPDRP_MFG = 0xb; 103 | internal const int SPDRP_PHYSICAL_DEVICE_OBJECT_NAME = 0xe; 104 | internal const int SPDRP_REMOVAL_POLICY = 0x1f; 105 | internal const int SPDRP_REMOVAL_POLICY_HW_DEFAULT = 0x20; 106 | internal const int SPDRP_REMOVAL_POLICY_OVERRIDE = 0x21; 107 | internal const int SPDRP_SECURITY = 0x17; 108 | internal const int SPDRP_SECURITY_SDS = 0x18; 109 | internal const int SPDRP_SERVICE = 4; 110 | internal const int SPDRP_UI_NUMBER = 0x10; 111 | internal const int SPDRP_UI_NUMBER_DESC_FORMAT = 0x1d; 112 | 113 | internal const int DIF_PROPERTYCHANGE = 0x12; 114 | internal const int DICS_ENABLE = 1; 115 | internal const int DICS_DISABLE = 2; 116 | internal const int DICS_FLAG_GLOBAL = 1; 117 | 118 | 119 | internal const int SPDRP_UPPERFILTERS = 0x11; 120 | 121 | [StructLayout(LayoutKind.Sequential)] 122 | internal class DEV_BROADCAST_DEVICEINTERFACE 123 | { 124 | internal int dbcc_size; 125 | internal int dbcc_devicetype; 126 | internal int dbcc_reserved; 127 | internal Guid dbcc_classguid; 128 | internal short dbcc_name; 129 | } 130 | 131 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] 132 | internal class DEV_BROADCAST_DEVICEINTERFACE_1 133 | { 134 | internal int dbcc_size; 135 | internal int dbcc_devicetype; 136 | internal int dbcc_reserved; 137 | [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 16)] 138 | internal byte[] dbcc_classguid; 139 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 255)] 140 | internal char[] dbcc_name; 141 | } 142 | 143 | [StructLayout(LayoutKind.Sequential)] 144 | internal class DEV_BROADCAST_HANDLE 145 | { 146 | internal int dbch_size; 147 | internal int dbch_devicetype; 148 | internal int dbch_reserved; 149 | internal int dbch_handle; 150 | internal int dbch_hdevnotify; 151 | } 152 | 153 | [StructLayout(LayoutKind.Sequential)] 154 | internal class DEV_BROADCAST_HDR 155 | { 156 | internal int dbch_size; 157 | internal int dbch_devicetype; 158 | internal int dbch_reserved; 159 | } 160 | 161 | [StructLayout(LayoutKind.Sequential)] 162 | internal struct SP_DEVICE_INTERFACE_DATA 163 | { 164 | internal int cbSize; 165 | internal System.Guid InterfaceClassGuid; 166 | internal int Flags; 167 | internal IntPtr Reserved; 168 | } 169 | 170 | [StructLayout(LayoutKind.Sequential)] 171 | internal struct SP_DEVINFO_DATA 172 | { 173 | internal int cbSize; 174 | internal Guid ClassGuid; 175 | internal int DevInst; 176 | internal IntPtr Reserved; 177 | } 178 | 179 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] 180 | internal struct SP_DEVICE_INTERFACE_DETAIL_DATA 181 | { 182 | internal int Size; 183 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)] 184 | internal string DevicePath; 185 | } 186 | 187 | [StructLayout(LayoutKind.Sequential)] 188 | internal struct DEVPROPKEY 189 | { 190 | public Guid fmtid; 191 | public ulong pid; 192 | } 193 | 194 | internal static DEVPROPKEY DEVPKEY_Device_BusReportedDeviceDesc = 195 | new DEVPROPKEY { fmtid = new Guid(0x540b947e, 0x8b40, 0x45bc, 0xa8, 0xa2, 0x6a, 0x0b, 0x89, 0x4c, 0xbd, 0xa2), pid = 4 }; 196 | 197 | [DllImport("setupapi.dll", EntryPoint = "SetupDiGetDeviceRegistryProperty")] 198 | public static extern bool SetupDiGetDeviceRegistryProperty(IntPtr deviceInfoSet, ref SP_DEVINFO_DATA deviceInfoData, int propertyVal, ref int propertyRegDataType, byte[] propertyBuffer, int propertyBufferSize, ref int requiredSize); 199 | 200 | [DllImport("setupapi.dll", EntryPoint = "SetupDiGetDevicePropertyW", SetLastError = true)] 201 | public static extern bool SetupDiGetDeviceProperty(IntPtr deviceInfo, ref SP_DEVINFO_DATA deviceInfoData, ref DEVPROPKEY propkey, ref ulong propertyDataType, byte[] propertyBuffer, int propertyBufferSize, ref int requiredSize, uint flags); 202 | 203 | [DllImport("setupapi.dll")] 204 | static internal extern bool SetupDiEnumDeviceInfo(IntPtr deviceInfoSet, int memberIndex, ref SP_DEVINFO_DATA deviceInfoData); 205 | 206 | [DllImport("user32.dll", CharSet = CharSet.Auto)] 207 | static internal extern IntPtr RegisterDeviceNotification(IntPtr hRecipient, IntPtr notificationFilter, Int32 flags); 208 | 209 | [DllImport("setupapi.dll")] 210 | internal static extern int SetupDiCreateDeviceInfoList(ref Guid classGuid, int hwndParent); 211 | 212 | [DllImport("setupapi.dll")] 213 | static internal extern int SetupDiDestroyDeviceInfoList(IntPtr deviceInfoSet); 214 | 215 | [DllImport("setupapi.dll", CharSet = CharSet.Auto)] 216 | static internal extern bool SetupDiSetClassInstallParams(IntPtr deviceInfoSet, ref SP_DEVINFO_DATA deviceInfoData, ref SP_PROPCHANGE_PARAMS classInstallParams, int classInstallParamsSize); 217 | 218 | [DllImport("setupapi.dll", CharSet = CharSet.Auto)] 219 | static internal extern bool SetupDiCallClassInstaller(int installFunction, IntPtr deviceInfoSet, ref SP_DEVINFO_DATA deviceInfoData); 220 | 221 | [DllImport("setupapi.dll")] 222 | static internal extern bool SetupDiEnumDeviceInterfaces(IntPtr deviceInfoSet, ref SP_DEVINFO_DATA deviceInfoData, ref Guid interfaceClassGuid, int memberIndex, ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData); 223 | 224 | [DllImport("setupapi.dll", CharSet = CharSet.Auto)] 225 | static internal extern IntPtr SetupDiGetClassDevs(ref System.Guid classGuid, string enumerator, int hwndParent, int flags); 226 | 227 | [DllImport("setupapi.dll", CharSet = CharSet.Auto, EntryPoint = "SetupDiGetDeviceInterfaceDetail")] 228 | static internal extern bool SetupDiGetDeviceInterfaceDetailBuffer(IntPtr deviceInfoSet, ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData, IntPtr deviceInterfaceDetailData, int deviceInterfaceDetailDataSize, ref int requiredSize, IntPtr deviceInfoData); 229 | 230 | [DllImport("setupapi.dll", CharSet = CharSet.Auto)] 231 | static internal extern bool SetupDiGetDeviceInterfaceDetail(IntPtr deviceInfoSet, ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData, ref SP_DEVICE_INTERFACE_DETAIL_DATA deviceInterfaceDetailData, int deviceInterfaceDetailDataSize, ref int requiredSize, IntPtr deviceInfoData); 232 | 233 | [DllImport("user32.dll")] 234 | static internal extern bool UnregisterDeviceNotification(IntPtr handle); 235 | 236 | internal const short HIDP_INPUT = 0; 237 | internal const short HIDP_OUTPUT = 1; 238 | 239 | internal const short HIDP_FEATURE = 2; 240 | [StructLayout(LayoutKind.Sequential)] 241 | internal struct HIDD_ATTRIBUTES 242 | { 243 | internal int Size; 244 | internal ushort VendorID; 245 | internal ushort ProductID; 246 | internal short VersionNumber; 247 | } 248 | 249 | [StructLayout(LayoutKind.Sequential)] 250 | internal struct HIDP_CAPS 251 | { 252 | internal short Usage; 253 | internal short UsagePage; 254 | internal short InputReportByteLength; 255 | internal short OutputReportByteLength; 256 | internal short FeatureReportByteLength; 257 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 17)] 258 | internal short[] Reserved; 259 | internal short NumberLinkCollectionNodes; 260 | internal short NumberInputButtonCaps; 261 | internal short NumberInputValueCaps; 262 | internal short NumberInputDataIndices; 263 | internal short NumberOutputButtonCaps; 264 | internal short NumberOutputValueCaps; 265 | internal short NumberOutputDataIndices; 266 | internal short NumberFeatureButtonCaps; 267 | internal short NumberFeatureValueCaps; 268 | internal short NumberFeatureDataIndices; 269 | } 270 | 271 | [StructLayout(LayoutKind.Sequential)] 272 | internal struct SP_CLASSINSTALL_HEADER 273 | { 274 | internal int cbSize; 275 | internal int installFunction; 276 | } 277 | 278 | [StructLayout(LayoutKind.Sequential)] 279 | internal struct SP_PROPCHANGE_PARAMS 280 | { 281 | internal SP_CLASSINSTALL_HEADER classInstallHeader; 282 | internal int stateChange; 283 | internal int scope; 284 | internal int hwProfile; 285 | } 286 | 287 | [StructLayout(LayoutKind.Sequential)] 288 | internal struct HIDP_VALUE_CAPS 289 | { 290 | internal short UsagePage; 291 | internal byte ReportID; 292 | internal int IsAlias; 293 | internal short BitField; 294 | internal short LinkCollection; 295 | internal short LinkUsage; 296 | internal short LinkUsagePage; 297 | internal int IsRange; 298 | internal int IsStringRange; 299 | internal int IsDesignatorRange; 300 | internal int IsAbsolute; 301 | internal int HasNull; 302 | internal byte Reserved; 303 | internal short BitSize; 304 | internal short ReportCount; 305 | internal short Reserved2; 306 | internal short Reserved3; 307 | internal short Reserved4; 308 | internal short Reserved5; 309 | internal short Reserved6; 310 | internal int LogicalMin; 311 | internal int LogicalMax; 312 | internal int PhysicalMin; 313 | internal int PhysicalMax; 314 | internal short UsageMin; 315 | internal short UsageMax; 316 | internal short StringMin; 317 | internal short StringMax; 318 | internal short DesignatorMin; 319 | internal short DesignatorMax; 320 | internal short DataIndexMin; 321 | internal short DataIndexMax; 322 | } 323 | 324 | [DllImport("hid.dll")] 325 | static internal extern bool HidD_FlushQueue(IntPtr hidDeviceObject); 326 | 327 | [DllImport("hid.dll")] 328 | static internal extern bool HidD_GetAttributes(IntPtr hidDeviceObject, ref HIDD_ATTRIBUTES attributes); 329 | 330 | [DllImport("hid.dll")] 331 | static internal extern bool HidD_GetFeature(IntPtr hidDeviceObject, byte[] lpReportBuffer, int reportBufferLength); 332 | 333 | [DllImport("hid.dll")] 334 | static internal extern bool HidD_GetInputReport(IntPtr hidDeviceObject, ref byte lpReportBuffer, int reportBufferLength); 335 | 336 | [DllImport("hid.dll")] 337 | static internal extern void HidD_GetHidGuid(ref Guid hidGuid); 338 | 339 | [DllImport("hid.dll")] 340 | static internal extern bool HidD_GetNumInputBuffers(IntPtr hidDeviceObject, ref int numberBuffers); 341 | 342 | [DllImport("hid.dll")] 343 | static internal extern bool HidD_GetPreparsedData(IntPtr hidDeviceObject, ref IntPtr preparsedData); 344 | 345 | [DllImport("hid.dll")] 346 | static internal extern bool HidD_FreePreparsedData(IntPtr preparsedData); 347 | 348 | [DllImport("hid.dll")] 349 | static internal extern bool HidD_SetFeature(IntPtr hidDeviceObject, byte[] lpReportBuffer, int reportBufferLength); 350 | 351 | [DllImport("hid.dll")] 352 | static internal extern bool HidD_SetNumInputBuffers(IntPtr hidDeviceObject, int numberBuffers); 353 | 354 | [DllImport("hid.dll")] 355 | static internal extern bool HidD_SetOutputReport(IntPtr hidDeviceObject, byte[] lpReportBuffer, int reportBufferLength); 356 | 357 | [DllImport("hid.dll")] 358 | static internal extern int HidP_GetCaps(IntPtr preparsedData, ref HIDP_CAPS capabilities); 359 | 360 | [DllImport("hid.dll")] 361 | static internal extern int HidP_GetValueCaps(short reportType, ref byte valueCaps, ref short valueCapsLength, IntPtr preparsedData); 362 | 363 | [DllImport("hid.dll", CharSet = CharSet.Unicode)] 364 | internal static extern bool HidD_GetProductString(IntPtr hidDeviceObject, ref byte lpReportBuffer, int ReportBufferLength); 365 | 366 | [DllImport("hid.dll", CharSet = CharSet.Unicode)] 367 | internal static extern bool HidD_GetManufacturerString(IntPtr hidDeviceObject, ref byte lpReportBuffer, int ReportBufferLength); 368 | 369 | [DllImport("hid.dll", CharSet = CharSet.Unicode)] 370 | internal static extern bool HidD_GetSerialNumberString(IntPtr hidDeviceObject, ref byte lpReportBuffer, int reportBufferLength); 371 | } 372 | } 373 | -------------------------------------------------------------------------------- /mi/HidLibrary/HidDevice.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | namespace HidLibrary 7 | { 8 | public class HidDevice : IHidDevice 9 | { 10 | public event InsertedEventHandler Inserted; 11 | public event RemovedEventHandler Removed; 12 | 13 | private readonly string _description; 14 | private readonly string _devicePath; 15 | private readonly HidDeviceAttributes _deviceAttributes; 16 | 17 | private readonly HidDeviceCapabilities _deviceCapabilities; 18 | private DeviceMode _deviceReadMode = DeviceMode.NonOverlapped; 19 | private DeviceMode _deviceWriteMode = DeviceMode.NonOverlapped; 20 | private ShareMode _deviceShareMode = ShareMode.ShareRead | ShareMode.ShareWrite; 21 | 22 | private readonly HidDeviceEventMonitor _deviceEventMonitor; 23 | 24 | private bool _monitorDeviceEvents; 25 | protected delegate HidDeviceData ReadDelegate(int timeout); 26 | protected delegate HidReport ReadReportDelegate(int timeout); 27 | private delegate bool WriteDelegate(byte[] data, int timeout); 28 | private delegate bool WriteReportDelegate(HidReport report, int timeout); 29 | 30 | private NativeOverlapped read_overlapped; 31 | private byte[] read_buffer; 32 | private bool reading; 33 | 34 | internal HidDevice(string devicePath, string description = null) 35 | { 36 | _deviceEventMonitor = new HidDeviceEventMonitor(this); 37 | _deviceEventMonitor.Inserted += DeviceEventMonitorInserted; 38 | _deviceEventMonitor.Removed += DeviceEventMonitorRemoved; 39 | 40 | _devicePath = devicePath; 41 | _description = description; 42 | 43 | try 44 | { 45 | var hidHandle = OpenDeviceIO(_devicePath, NativeMethods.ACCESS_NONE); 46 | 47 | _deviceAttributes = GetDeviceAttributes(hidHandle); 48 | _deviceCapabilities = GetDeviceCapabilities(hidHandle); 49 | 50 | CloseDeviceIO(hidHandle); 51 | } 52 | catch (Exception exception) 53 | { 54 | throw new Exception(string.Format("Error querying HID device '{0}'.", devicePath), exception); 55 | } 56 | } 57 | 58 | public IntPtr Handle { get; private set; } 59 | public bool IsOpen { get; private set; } 60 | public bool IsConnected { get { return HidDevices.IsConnected(_devicePath); } } 61 | public string Description { get { return _description; } } 62 | public HidDeviceCapabilities Capabilities { get { return _deviceCapabilities; } } 63 | public HidDeviceAttributes Attributes { get { return _deviceAttributes; } } 64 | public string DevicePath { get { return _devicePath; } } 65 | 66 | public bool MonitorDeviceEvents 67 | { 68 | get { return _monitorDeviceEvents; } 69 | set 70 | { 71 | if (value & _monitorDeviceEvents == false) _deviceEventMonitor.Init(); 72 | _monitorDeviceEvents = value; 73 | } 74 | } 75 | 76 | public override string ToString() 77 | { 78 | return string.Format("VendorID={0}, ProductID={1}, Version={2}, DevicePath={3}", 79 | _deviceAttributes.VendorHexId, 80 | _deviceAttributes.ProductHexId, 81 | _deviceAttributes.Version, 82 | _devicePath); 83 | } 84 | 85 | public void OpenDevice() 86 | { 87 | OpenDevice(DeviceMode.NonOverlapped, DeviceMode.NonOverlapped, ShareMode.ShareRead | ShareMode.ShareWrite); 88 | } 89 | 90 | public void OpenDevice(DeviceMode readMode, DeviceMode writeMode, ShareMode shareMode) 91 | { 92 | if (IsOpen) return; 93 | 94 | _deviceReadMode = readMode; 95 | _deviceWriteMode = writeMode; 96 | _deviceShareMode = shareMode; 97 | 98 | try 99 | { 100 | Handle = OpenDeviceIO(_devicePath, readMode, NativeMethods.GENERIC_READ | NativeMethods.GENERIC_WRITE, shareMode); 101 | } 102 | catch (Exception exception) 103 | { 104 | IsOpen = false; 105 | throw new Exception("Error opening HID device.", exception); 106 | } 107 | 108 | IsOpen = Handle.ToInt32() != NativeMethods.INVALID_HANDLE_VALUE; 109 | if (!IsOpen) 110 | { 111 | throw new Exception("Error opening HID device."); 112 | } 113 | } 114 | 115 | 116 | public void CloseDevice() 117 | { 118 | if (!IsOpen) return; 119 | CloseDeviceIO(Handle); 120 | IsOpen = false; 121 | } 122 | 123 | public HidDeviceData Read() 124 | { 125 | return Read(0); 126 | } 127 | 128 | public HidDeviceData Read(int timeout) 129 | { 130 | if (IsOpen == false) return new HidDeviceData(HidDeviceData.ReadStatus.ReadError); 131 | try 132 | { 133 | return ReadData(timeout); 134 | } 135 | catch 136 | { 137 | return new HidDeviceData(HidDeviceData.ReadStatus.ReadError); 138 | } 139 | } 140 | 141 | public void Read(ReadCallback callback) 142 | { 143 | Read(callback, 0); 144 | } 145 | 146 | public void Read(ReadCallback callback, int timeout) 147 | { 148 | var readDelegate = new ReadDelegate(Read); 149 | var asyncState = new HidAsyncState(readDelegate, callback); 150 | readDelegate.BeginInvoke(timeout, EndRead, asyncState); 151 | } 152 | 153 | public async Task ReadAsync(int timeout = 0) 154 | { 155 | var readDelegate = new ReadDelegate(Read); 156 | return await Task.Factory.FromAsync(readDelegate.BeginInvoke, readDelegate.EndInvoke, timeout, null); 157 | } 158 | 159 | public HidReport ReadReport() 160 | { 161 | return ReadReport(0); 162 | } 163 | 164 | public HidReport ReadReport(int timeout) 165 | { 166 | return new HidReport(Capabilities.InputReportByteLength, Read(timeout)); 167 | } 168 | 169 | public void ReadReport(ReadReportCallback callback) 170 | { 171 | ReadReport(callback, 0); 172 | } 173 | 174 | public void ReadReport(ReadReportCallback callback, int timeout) 175 | { 176 | var readReportDelegate = new ReadReportDelegate(ReadReport); 177 | var asyncState = new HidAsyncState(readReportDelegate, callback); 178 | readReportDelegate.BeginInvoke(timeout, EndReadReport, asyncState); 179 | } 180 | 181 | public async Task ReadReportAsync(int timeout = 0) 182 | { 183 | var readReportDelegate = new ReadReportDelegate(ReadReport); 184 | return await Task.Factory.FromAsync(readReportDelegate.BeginInvoke, readReportDelegate.EndInvoke, timeout, null); 185 | } 186 | 187 | public bool ReadFeatureData(out byte[] data, byte reportId = 0) 188 | { 189 | if (_deviceCapabilities.FeatureReportByteLength <= 0) 190 | { 191 | data = new byte[0]; 192 | return false; 193 | } 194 | 195 | data = new byte[_deviceCapabilities.FeatureReportByteLength]; 196 | 197 | var buffer = CreateFeatureOutputBuffer(); 198 | buffer[0] = reportId; 199 | 200 | IntPtr hidHandle = IntPtr.Zero; 201 | bool success = false; 202 | try 203 | { 204 | if (IsOpen) 205 | hidHandle = Handle; 206 | else 207 | hidHandle = OpenDeviceIO(_devicePath, NativeMethods.ACCESS_NONE); 208 | 209 | success = NativeMethods.HidD_GetFeature(hidHandle, buffer, buffer.Length); 210 | 211 | if (success) 212 | { 213 | Array.Copy(buffer, 0, data, 0, Math.Min(data.Length, _deviceCapabilities.FeatureReportByteLength)); 214 | } 215 | } 216 | catch (Exception exception) 217 | { 218 | throw new Exception(string.Format("Error accessing HID device '{0}'.", _devicePath), exception); 219 | } 220 | finally 221 | { 222 | if (hidHandle != IntPtr.Zero && hidHandle != Handle) 223 | CloseDeviceIO(hidHandle); 224 | } 225 | 226 | return success; 227 | } 228 | 229 | public bool ReadProduct(out byte[] data) 230 | { 231 | data = new byte[254]; 232 | IntPtr hidHandle = IntPtr.Zero; 233 | bool success = false; 234 | try 235 | { 236 | if (IsOpen) 237 | hidHandle = Handle; 238 | else 239 | hidHandle = OpenDeviceIO(_devicePath, NativeMethods.ACCESS_NONE); 240 | 241 | success = NativeMethods.HidD_GetProductString(hidHandle, ref data[0], data.Length); 242 | } 243 | catch (Exception exception) 244 | { 245 | throw new Exception(string.Format("Error accessing HID device '{0}'.", _devicePath), exception); 246 | } 247 | finally 248 | { 249 | if (hidHandle != IntPtr.Zero && hidHandle != Handle) 250 | CloseDeviceIO(hidHandle); 251 | } 252 | 253 | return success; 254 | } 255 | 256 | public bool ReadManufacturer(out byte[] data) 257 | { 258 | data = new byte[254]; 259 | IntPtr hidHandle = IntPtr.Zero; 260 | bool success = false; 261 | try 262 | { 263 | if (IsOpen) 264 | hidHandle = Handle; 265 | else 266 | hidHandle = OpenDeviceIO(_devicePath, NativeMethods.ACCESS_NONE); 267 | 268 | success = NativeMethods.HidD_GetManufacturerString(hidHandle, ref data[0], data.Length); 269 | } 270 | catch (Exception exception) 271 | { 272 | throw new Exception(string.Format("Error accessing HID device '{0}'.", _devicePath), exception); 273 | } 274 | finally 275 | { 276 | if (hidHandle != IntPtr.Zero && hidHandle != Handle) 277 | CloseDeviceIO(hidHandle); 278 | } 279 | 280 | return success; 281 | } 282 | 283 | public bool ReadSerialNumber(out byte[] data) 284 | { 285 | data = new byte[254]; 286 | IntPtr hidHandle = IntPtr.Zero; 287 | bool success = false; 288 | try 289 | { 290 | if (IsOpen) 291 | hidHandle = Handle; 292 | else 293 | hidHandle = OpenDeviceIO(_devicePath, NativeMethods.ACCESS_NONE); 294 | 295 | success = NativeMethods.HidD_GetSerialNumberString(hidHandle, ref data[0], data.Length); 296 | } 297 | catch (Exception exception) 298 | { 299 | throw new Exception(string.Format("Error accessing HID device '{0}'.", _devicePath), exception); 300 | } 301 | finally 302 | { 303 | if (hidHandle != IntPtr.Zero && hidHandle != Handle) 304 | CloseDeviceIO(hidHandle); 305 | } 306 | 307 | return success; 308 | } 309 | 310 | public bool Write(byte[] data) 311 | { 312 | return Write(data, 0); 313 | } 314 | 315 | public bool Write(byte[] data, int timeout) 316 | { 317 | if (IsOpen == false) OpenDevice(_deviceReadMode, _deviceWriteMode, _deviceShareMode); 318 | try 319 | { 320 | return WriteData(data, timeout); 321 | } 322 | catch 323 | { 324 | return false; 325 | } 326 | } 327 | 328 | public void Write(byte[] data, WriteCallback callback) 329 | { 330 | Write(data, callback, 0); 331 | } 332 | 333 | public void Write(byte[] data, WriteCallback callback, int timeout) 334 | { 335 | var writeDelegate = new WriteDelegate(Write); 336 | var asyncState = new HidAsyncState(writeDelegate, callback); 337 | writeDelegate.BeginInvoke(data, timeout, EndWrite, asyncState); 338 | } 339 | 340 | public async Task WriteAsync(byte[] data, int timeout = 0) 341 | { 342 | var writeDelegate = new WriteDelegate(Write); 343 | return await Task.Factory.FromAsync(writeDelegate.BeginInvoke, writeDelegate.EndInvoke, data, timeout, null); 344 | } 345 | 346 | public bool WriteReport(HidReport report) 347 | { 348 | return WriteReport(report, 0); 349 | } 350 | 351 | public bool WriteReport(HidReport report, int timeout) 352 | { 353 | return Write(report.GetBytes(), timeout); 354 | } 355 | 356 | public void WriteReport(HidReport report, WriteCallback callback) 357 | { 358 | WriteReport(report, callback, 0); 359 | } 360 | 361 | public void WriteReport(HidReport report, WriteCallback callback, int timeout) 362 | { 363 | var writeReportDelegate = new WriteReportDelegate(WriteReport); 364 | var asyncState = new HidAsyncState(writeReportDelegate, callback); 365 | writeReportDelegate.BeginInvoke(report, timeout, EndWriteReport, asyncState); 366 | } 367 | 368 | public async Task WriteReportAsync(HidReport report, int timeout = 0) 369 | { 370 | var writeReportDelegate = new WriteReportDelegate(WriteReport); 371 | return await Task.Factory.FromAsync(writeReportDelegate.BeginInvoke, writeReportDelegate.EndInvoke, report, timeout, null); 372 | } 373 | 374 | public HidReport CreateReport() 375 | { 376 | return new HidReport(Capabilities.OutputReportByteLength); 377 | } 378 | 379 | public bool WriteFeatureData(byte[] data) 380 | { 381 | if (_deviceCapabilities.FeatureReportByteLength <= 0) return false; 382 | 383 | var buffer = CreateFeatureOutputBuffer(); 384 | 385 | Array.Copy(data, 0, buffer, 0, Math.Min(data.Length, _deviceCapabilities.FeatureReportByteLength)); 386 | 387 | 388 | IntPtr hidHandle = IntPtr.Zero; 389 | bool success = false; 390 | try 391 | { 392 | if (IsOpen) 393 | hidHandle = Handle; 394 | else 395 | return false; 396 | 397 | //var overlapped = new NativeOverlapped(); 398 | success = NativeMethods.HidD_SetFeature(hidHandle, buffer, buffer.Length); 399 | } 400 | catch (Exception exception) 401 | { 402 | throw new Exception(string.Format("Error accessing HID device '{0}'.", _devicePath), exception); 403 | } 404 | 405 | return success; 406 | } 407 | 408 | protected static void EndRead(IAsyncResult ar) 409 | { 410 | var hidAsyncState = (HidAsyncState)ar.AsyncState; 411 | var callerDelegate = (ReadDelegate)hidAsyncState.CallerDelegate; 412 | var callbackDelegate = (ReadCallback)hidAsyncState.CallbackDelegate; 413 | var data = callerDelegate.EndInvoke(ar); 414 | 415 | if ((callbackDelegate != null)) callbackDelegate.Invoke(data); 416 | } 417 | 418 | protected static void EndReadReport(IAsyncResult ar) 419 | { 420 | var hidAsyncState = (HidAsyncState)ar.AsyncState; 421 | var callerDelegate = (ReadReportDelegate)hidAsyncState.CallerDelegate; 422 | var callbackDelegate = (ReadReportCallback)hidAsyncState.CallbackDelegate; 423 | var report = callerDelegate.EndInvoke(ar); 424 | 425 | if ((callbackDelegate != null)) callbackDelegate.Invoke(report); 426 | } 427 | 428 | private static void EndWrite(IAsyncResult ar) 429 | { 430 | var hidAsyncState = (HidAsyncState)ar.AsyncState; 431 | var callerDelegate = (WriteDelegate)hidAsyncState.CallerDelegate; 432 | var callbackDelegate = (WriteCallback)hidAsyncState.CallbackDelegate; 433 | var result = callerDelegate.EndInvoke(ar); 434 | 435 | if ((callbackDelegate != null)) callbackDelegate.Invoke(result); 436 | } 437 | 438 | private static void EndWriteReport(IAsyncResult ar) 439 | { 440 | var hidAsyncState = (HidAsyncState)ar.AsyncState; 441 | var callerDelegate = (WriteReportDelegate)hidAsyncState.CallerDelegate; 442 | var callbackDelegate = (WriteCallback)hidAsyncState.CallbackDelegate; 443 | var result = callerDelegate.EndInvoke(ar); 444 | 445 | if ((callbackDelegate != null)) callbackDelegate.Invoke(result); 446 | } 447 | 448 | private byte[] CreateInputBuffer() 449 | { 450 | return CreateBuffer(Capabilities.InputReportByteLength - 1); 451 | } 452 | 453 | private byte[] CreateOutputBuffer() 454 | { 455 | return CreateBuffer(Capabilities.OutputReportByteLength - 1); 456 | } 457 | 458 | private byte[] CreateFeatureOutputBuffer() 459 | { 460 | return CreateBuffer(Capabilities.FeatureReportByteLength - 1); 461 | } 462 | 463 | private static byte[] CreateBuffer(int length) 464 | { 465 | byte[] buffer = null; 466 | Array.Resize(ref buffer, length + 1); 467 | return buffer; 468 | } 469 | 470 | private static HidDeviceAttributes GetDeviceAttributes(IntPtr hidHandle) 471 | { 472 | var deviceAttributes = default(NativeMethods.HIDD_ATTRIBUTES); 473 | deviceAttributes.Size = Marshal.SizeOf(deviceAttributes); 474 | NativeMethods.HidD_GetAttributes(hidHandle, ref deviceAttributes); 475 | return new HidDeviceAttributes(deviceAttributes); 476 | } 477 | 478 | private static HidDeviceCapabilities GetDeviceCapabilities(IntPtr hidHandle) 479 | { 480 | var capabilities = default(NativeMethods.HIDP_CAPS); 481 | var preparsedDataPointer = default(IntPtr); 482 | 483 | if (NativeMethods.HidD_GetPreparsedData(hidHandle, ref preparsedDataPointer)) 484 | { 485 | NativeMethods.HidP_GetCaps(preparsedDataPointer, ref capabilities); 486 | NativeMethods.HidD_FreePreparsedData(preparsedDataPointer); 487 | } 488 | return new HidDeviceCapabilities(capabilities); 489 | } 490 | 491 | private bool WriteData(byte[] data, int timeout) 492 | { 493 | if (_deviceCapabilities.OutputReportByteLength <= 0) return false; 494 | 495 | var buffer = CreateOutputBuffer(); 496 | uint bytesWritten = 0; 497 | 498 | Array.Copy(data, 0, buffer, 0, Math.Min(data.Length, _deviceCapabilities.OutputReportByteLength)); 499 | 500 | if (_deviceWriteMode == DeviceMode.Overlapped) 501 | { 502 | var security = new NativeMethods.SECURITY_ATTRIBUTES(); 503 | var overlapped = new NativeOverlapped(); 504 | 505 | var overlapTimeout = timeout <= 0 ? NativeMethods.WAIT_INFINITE : timeout; 506 | 507 | security.lpSecurityDescriptor = IntPtr.Zero; 508 | security.bInheritHandle = true; 509 | security.nLength = Marshal.SizeOf(security); 510 | 511 | overlapped.OffsetLow = 0; 512 | overlapped.OffsetHigh = 0; 513 | overlapped.EventHandle = NativeMethods.CreateEvent(ref security, Convert.ToInt32(false), Convert.ToInt32(true), ""); 514 | 515 | try 516 | { 517 | NativeMethods.WriteFile(Handle, buffer, (uint)buffer.Length, out bytesWritten, ref overlapped); 518 | } 519 | catch { return false; } 520 | 521 | var result = NativeMethods.WaitForSingleObject(overlapped.EventHandle, overlapTimeout); 522 | 523 | switch (result) 524 | { 525 | case NativeMethods.WAIT_OBJECT_0: 526 | return true; 527 | case NativeMethods.WAIT_TIMEOUT: 528 | return false; 529 | case NativeMethods.WAIT_FAILED: 530 | return false; 531 | default: 532 | return false; 533 | } 534 | } 535 | else 536 | { 537 | try 538 | { 539 | var overlapped = new NativeOverlapped(); 540 | return NativeMethods.WriteFile(Handle, buffer, (uint)buffer.Length, out bytesWritten, ref overlapped); 541 | } 542 | catch { return false; } 543 | } 544 | } 545 | 546 | protected HidDeviceData ReadData(int timeout) 547 | { 548 | var buffer = new byte[] { }; 549 | var status = HidDeviceData.ReadStatus.NoDataRead; 550 | 551 | if (_deviceCapabilities.InputReportByteLength > 0) 552 | { 553 | uint bytesRead = 0; 554 | 555 | 556 | 557 | if (_deviceReadMode == DeviceMode.Overlapped) 558 | { 559 | if (!reading) 560 | { 561 | read_buffer = CreateInputBuffer(); 562 | var security = new NativeMethods.SECURITY_ATTRIBUTES(); 563 | read_overlapped = new NativeOverlapped(); 564 | 565 | security.lpSecurityDescriptor = IntPtr.Zero; 566 | security.bInheritHandle = true; 567 | security.nLength = Marshal.SizeOf(security); 568 | 569 | read_overlapped.OffsetLow = 0; 570 | read_overlapped.OffsetHigh = 0; 571 | read_overlapped.EventHandle = NativeMethods.CreateEvent(ref security, Convert.ToInt32(false), Convert.ToInt32(true), string.Empty); 572 | NativeMethods.ReadFile(Handle, read_buffer, (uint)read_buffer.Length, out bytesRead, ref read_overlapped); 573 | } 574 | 575 | try 576 | { 577 | var result = NativeMethods.WaitForSingleObject(read_overlapped.EventHandle, timeout); 578 | 579 | switch (result) 580 | { 581 | case NativeMethods.WAIT_OBJECT_0: status = HidDeviceData.ReadStatus.Success; reading = false; CloseDeviceIO(read_overlapped.EventHandle); return new HidDeviceData(read_buffer, status); 582 | case NativeMethods.WAIT_TIMEOUT: 583 | status = HidDeviceData.ReadStatus.WaitTimedOut; 584 | buffer = new byte[] { }; 585 | reading = true; 586 | break; 587 | case NativeMethods.WAIT_FAILED: 588 | status = HidDeviceData.ReadStatus.WaitFail; 589 | buffer = new byte[] { }; 590 | reading = false; 591 | CloseDeviceIO(read_overlapped.EventHandle); 592 | break; 593 | default: 594 | status = HidDeviceData.ReadStatus.NoDataRead; 595 | buffer = new byte[] { }; 596 | reading = false; 597 | CloseDeviceIO(read_overlapped.EventHandle); 598 | break; 599 | } 600 | } 601 | catch { status = HidDeviceData.ReadStatus.ReadError; reading = false; CloseDeviceIO(read_overlapped.EventHandle); } 602 | } 603 | else 604 | { 605 | try 606 | { 607 | var overlapped = new NativeOverlapped(); 608 | 609 | NativeMethods.ReadFile(Handle, buffer, (uint)buffer.Length, out bytesRead, ref overlapped); 610 | status = HidDeviceData.ReadStatus.Success; 611 | } 612 | catch { status = HidDeviceData.ReadStatus.ReadError; } 613 | } 614 | } 615 | return new HidDeviceData(buffer, status); 616 | } 617 | 618 | private static IntPtr OpenDeviceIO(string devicePath, uint deviceAccess) 619 | { 620 | return OpenDeviceIO(devicePath, DeviceMode.NonOverlapped, deviceAccess, ShareMode.ShareRead | ShareMode.ShareWrite); 621 | } 622 | 623 | private static IntPtr OpenDeviceIO(string devicePath, DeviceMode deviceMode, uint deviceAccess, ShareMode shareMode) 624 | { 625 | var security = new NativeMethods.SECURITY_ATTRIBUTES(); 626 | var flags = 0; 627 | 628 | if (deviceMode == DeviceMode.Overlapped) flags = NativeMethods.FILE_FLAG_OVERLAPPED; 629 | 630 | security.lpSecurityDescriptor = IntPtr.Zero; 631 | security.bInheritHandle = true; 632 | security.nLength = Marshal.SizeOf(security); 633 | 634 | return NativeMethods.CreateFile(devicePath, deviceAccess, (int)shareMode, ref security, NativeMethods.OPEN_EXISTING, flags, 0); 635 | } 636 | 637 | private static void CloseDeviceIO(IntPtr handle) 638 | { 639 | if (Environment.OSVersion.Version.Major > 5) 640 | { 641 | NativeMethods.CancelIoEx(handle, IntPtr.Zero); 642 | } 643 | NativeMethods.CloseHandle(handle); 644 | } 645 | 646 | private void DeviceEventMonitorInserted() 647 | { 648 | if (!IsOpen) OpenDevice(_deviceReadMode, _deviceWriteMode, _deviceShareMode); 649 | if (Inserted != null) Inserted(); 650 | } 651 | 652 | private void DeviceEventMonitorRemoved() 653 | { 654 | if (IsOpen) CloseDevice(); 655 | if (Removed != null) Removed(); 656 | } 657 | 658 | public void Dispose() 659 | { 660 | if (MonitorDeviceEvents) MonitorDeviceEvents = false; 661 | if (IsOpen) CloseDevice(); 662 | } 663 | } 664 | } 665 | --------------------------------------------------------------------------------