Initial Commit

This commit is contained in:
Tomas 2020-09-25 22:23:42 +01:00
parent 60ebc07d8d
commit 459046901d
44 changed files with 3938 additions and 0 deletions

25
MiController.sln Normal file
View File

@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30503.244
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MiController", "MiController\MiController.csproj", "{AA86CCD2-39BC-4CEC-8E9C-29E3AB9E1C24}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
Release|x64 = Release|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{AA86CCD2-39BC-4CEC-8E9C-29E3AB9E1C24}.Debug|x64.ActiveCfg = Debug|Any CPU
{AA86CCD2-39BC-4CEC-8E9C-29E3AB9E1C24}.Debug|x64.Build.0 = Debug|Any CPU
{AA86CCD2-39BC-4CEC-8E9C-29E3AB9E1C24}.Release|x64.ActiveCfg = Release|Any CPU
{AA86CCD2-39BC-4CEC-8E9C-29E3AB9E1C24}.Release|x64.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {489CDE69-4008-4F22-8912-521BDBEF0DC6}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,181 @@
using System;
using System.ComponentModel;
using System.Linq;
using HidLibrary;
using ScpDriverInterface;
using System.Threading;
using System.Runtime.InteropServices;
namespace MiController
{
public class ControllerHelper
{
private static ScpBus _globalScpBus;
static bool ConsoleEventCallback(int eventType)
{
if (eventType == 2)
{
_globalScpBus.UnplugAll();
}
return false;
}
static ConsoleEventDelegate _handler; // Keeps it from getting garbage collected
// Pinvoke
private delegate bool ConsoleEventDelegate(int eventType);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool SetConsoleCtrlHandler(ConsoleEventDelegate callback, bool add);
public static void Run(BackgroundWorker bw)
{
ScpBus scpBus = new ScpBus();
scpBus.UnplugAll();
_globalScpBus = scpBus;
_handler = ConsoleEventCallback;
SetConsoleCtrlHandler(_handler, true);
Thread.Sleep(400);
XiaomiGamepad[] gamepads = new XiaomiGamepad[4];
int index = 1;
var compatibleDevices = HidDevices.Enumerate(0x2717, 0x3144).ToList();
foreach (var deviceInstance in compatibleDevices)
{
Console.WriteLine(deviceInstance);
HidDevice device = deviceInstance;
try
{
device.OpenDevice(DeviceMode.Overlapped, DeviceMode.Overlapped, ShareMode.ShareRead | ShareMode.ShareWrite);
}
catch
{
bw.ReportProgress(0, "Could not open gamepad in exclusive mode. Try re-enable device.");
var instanceId = DevicePathToInstanceId(deviceInstance.DevicePath);
if (TryReEnableDevice(instanceId))
{
try
{
device.OpenDevice(DeviceMode.Overlapped, DeviceMode.Overlapped, ShareMode.Exclusive);
bw.ReportProgress(0, "Opened in exclusive mode.");
}
catch
{
device.OpenDevice(DeviceMode.Overlapped, DeviceMode.Overlapped, ShareMode.ShareRead | ShareMode.ShareWrite);
bw.ReportProgress(0, "Opened in shared mode.");
}
}
else
{
device.OpenDevice(DeviceMode.Overlapped, DeviceMode.Overlapped, ShareMode.ShareRead | ShareMode.ShareWrite);
bw.ReportProgress(0, "Opened in shared mode.");
}
}
byte[] vibration = { 0x20, 0x00, 0x00 };
if (device.WriteFeatureData(vibration) == false)
{
bw.ReportProgress(0, "Could not write to gamepad (is it closed?), skipping");
device.CloseDevice();
continue;
}
device.ReadSerialNumber(out _);
device.ReadProduct(out _);
gamepads[index - 1] = new XiaomiGamepad(device, scpBus, index);
++index;
if (index >= 5)
{
break;
}
}
bw.ReportProgress(0, $"{index - 1} controllers connected");
while (!bw.CancellationPending)
{
Thread.Sleep(1000);
}
}
private static bool TryReEnableDevice(string deviceInstanceId)
{
try
{
bool success;
Guid hidGuid = new Guid();
HidLibrary.NativeMethods.HidD_GetHidGuid(ref hidGuid);
IntPtr deviceInfoSet = HidLibrary.NativeMethods.SetupDiGetClassDevs(ref hidGuid, deviceInstanceId, 0, HidLibrary.NativeMethods.DIGCF_PRESENT | HidLibrary.NativeMethods.DIGCF_DEVICEINTERFACE);
HidLibrary.NativeMethods.SP_DEVINFO_DATA deviceInfoData = new HidLibrary.NativeMethods.SP_DEVINFO_DATA();
deviceInfoData.cbSize = Marshal.SizeOf(deviceInfoData);
success = HidLibrary.NativeMethods.SetupDiEnumDeviceInfo(deviceInfoSet, 0, ref deviceInfoData);
if (!success)
{
Console.WriteLine("Error getting device info data, error code = " + Marshal.GetLastWin32Error());
}
success = HidLibrary.NativeMethods.SetupDiEnumDeviceInfo(deviceInfoSet, 1, ref deviceInfoData); // Checks that we have a unique device
if (success)
{
Console.WriteLine("Can't find unique device");
}
HidLibrary.NativeMethods.SP_PROPCHANGE_PARAMS propChangeParams = new HidLibrary.NativeMethods.SP_PROPCHANGE_PARAMS();
propChangeParams.classInstallHeader.cbSize = Marshal.SizeOf(propChangeParams.classInstallHeader);
propChangeParams.classInstallHeader.installFunction = HidLibrary.NativeMethods.DIF_PROPERTYCHANGE;
propChangeParams.stateChange = HidLibrary.NativeMethods.DICS_DISABLE;
propChangeParams.scope = HidLibrary.NativeMethods.DICS_FLAG_GLOBAL;
propChangeParams.hwProfile = 0;
success = HidLibrary.NativeMethods.SetupDiSetClassInstallParams(deviceInfoSet, ref deviceInfoData, ref propChangeParams, Marshal.SizeOf(propChangeParams));
if (!success)
{
Console.WriteLine("Error setting class install params, error code = " + Marshal.GetLastWin32Error());
return false;
}
success = HidLibrary.NativeMethods.SetupDiCallClassInstaller(HidLibrary.NativeMethods.DIF_PROPERTYCHANGE, deviceInfoSet, ref deviceInfoData);
if (!success)
{
Console.WriteLine("Error disabling device, error code = " + Marshal.GetLastWin32Error());
return false;
}
propChangeParams.stateChange = HidLibrary.NativeMethods.DICS_ENABLE;
success = HidLibrary.NativeMethods.SetupDiSetClassInstallParams(deviceInfoSet, ref deviceInfoData, ref propChangeParams, Marshal.SizeOf(propChangeParams));
if (!success)
{
Console.WriteLine("Error setting class install params, error code = " + Marshal.GetLastWin32Error());
return false;
}
success = HidLibrary.NativeMethods.SetupDiCallClassInstaller(HidLibrary.NativeMethods.DIF_PROPERTYCHANGE, deviceInfoSet, ref deviceInfoData);
if (!success)
{
Console.WriteLine("Error enabling device, error code = " + Marshal.GetLastWin32Error());
return false;
}
HidLibrary.NativeMethods.SetupDiDestroyDeviceInfoList(deviceInfoSet);
return true;
}
catch
{
Console.WriteLine("Can't reenable device");
return false;
}
}
private static string DevicePathToInstanceId(string devicePath)
{
string deviceInstanceId = devicePath;
deviceInstanceId = deviceInstanceId.Remove(0, deviceInstanceId.LastIndexOf('\\') + 1);
deviceInstanceId = deviceInstanceId.Remove(deviceInstanceId.LastIndexOf('{'));
deviceInstanceId = deviceInstanceId.Replace('#', '\\');
if (deviceInstanceId.EndsWith("\\"))
{
deviceInstanceId = deviceInstanceId.Remove(deviceInstanceId.Length - 1);
}
return deviceInstanceId;
}
}
}

View File

@ -0,0 +1,58 @@
using System;
using System.IO;
using System.Reflection;
using MiController.DriverUtils;
namespace MiController
{
public class DriverSetup
{
private static readonly Guid Ds3BusClassGuid = new Guid("f679f562-3164-42ce-a4db-e7ddbe723909");
public static void Install()
{
string driverDir = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? string.Empty, "ScpDriver");
string devPath = string.Empty;
string instanceId = string.Empty;
var flags = DifxFlags.DRIVER_PACKAGE_ONLY_IF_DEVICE_PRESENT;
//if (force)
// flags |= DifxFlags.DRIVER_PACKAGE_FORCE;
if (!Devcon.Find(Ds3BusClassGuid, ref devPath, ref instanceId))
{
bool virtualBusCreated = Devcon.Create("System", new Guid("{4D36E97D-E325-11CE-BFC1-08002BE10318}"),
"root\\ScpVBus\0\0");
}
var installer = Difx.Factory(driverDir);
uint result = installer.Install(Path.Combine(driverDir, @"ScpVBus.inf"), flags, out var rebootRequired);
if (result != 0)
{
Uninstall();
throw new Exception("Driver installation failed. Error code: " + result);
}
}
public static void Uninstall()
{
string driverDir = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? string.Empty, "ScpDriver");
string devPath = string.Empty;
string instanceId = string.Empty;
if (!Devcon.Find(Ds3BusClassGuid, ref devPath, ref instanceId))
{
Devcon.Remove(Ds3BusClassGuid, devPath, instanceId);
}
var installer = Difx.Factory(driverDir);
installer.Uninstall(Path.Combine(driverDir, @"ScpVBus.inf"),
DifxFlags.DRIVER_PACKAGE_DELETE_FILES,
out var rebootRequired);
}
}
}

View File

@ -0,0 +1,242 @@
using System;
using System.Runtime.InteropServices;
namespace MiController.DriverUtils
{
public class Devcon
{
public static bool Find(Guid target, ref string path, ref string instanceId, int instance = 0)
{
var deviceInfoSet = IntPtr.Zero;
try
{
SP_DEVINFO_DATA deviceInterfaceData = new SP_DEVINFO_DATA(), da = new SP_DEVINFO_DATA();
int bufferSize = 0, memberIndex = 0;
deviceInfoSet = SetupDiGetClassDevs(ref target, IntPtr.Zero, IntPtr.Zero,
DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
deviceInterfaceData.cbSize = da.cbSize = Marshal.SizeOf(deviceInterfaceData);
while (SetupDiEnumDeviceInterfaces(deviceInfoSet, IntPtr.Zero, ref target, memberIndex,
ref deviceInterfaceData))
{
SetupDiGetDeviceInterfaceDetail(deviceInfoSet, ref deviceInterfaceData, IntPtr.Zero, 0,
ref bufferSize, ref da);
{
var detailDataBuffer = Marshal.AllocHGlobal(bufferSize);
Marshal.WriteInt32(detailDataBuffer,
(IntPtr.Size == 4) ? (4 + Marshal.SystemDefaultCharSize) : 8);
if (SetupDiGetDeviceInterfaceDetail(deviceInfoSet, ref deviceInterfaceData, detailDataBuffer,
bufferSize, ref bufferSize, ref da))
{
var pDevicePathName = detailDataBuffer + 4;
path = Marshal.PtrToStringAuto(pDevicePathName).ToUpper();
if (memberIndex == instance)
{
var nBytes = 256;
var ptrInstanceBuf = Marshal.AllocHGlobal(nBytes);
CM_Get_Device_ID(da.Flags, ptrInstanceBuf, nBytes, 0);
instanceId = Marshal.PtrToStringAuto(ptrInstanceBuf).ToUpper();
Marshal.FreeHGlobal(ptrInstanceBuf);
return true;
}
}
else Marshal.FreeHGlobal(detailDataBuffer);
}
memberIndex++;
}
}
finally
{
if (deviceInfoSet != IntPtr.Zero)
{
SetupDiDestroyDeviceInfoList(deviceInfoSet);
}
}
return false;
}
public static bool Create(string className, Guid classGuid, string node)
{
var deviceInfoSet = (IntPtr)(-1);
var deviceInfoData = new SP_DEVINFO_DATA();
try
{
deviceInfoSet = SetupDiCreateDeviceInfoList(ref classGuid, IntPtr.Zero);
if (deviceInfoSet == (IntPtr)(-1))
{
return false;
}
deviceInfoData.cbSize = Marshal.SizeOf(deviceInfoData);
if (
!SetupDiCreateDeviceInfo(deviceInfoSet, className, ref classGuid, null, IntPtr.Zero,
DICD_GENERATE_ID, ref deviceInfoData))
{
return false;
}
if (
!SetupDiSetDeviceRegistryProperty(deviceInfoSet, ref deviceInfoData, SPDRP_HARDWAREID, node,
node.Length * 2))
{
return false;
}
if (!SetupDiCallClassInstaller(DIF_REGISTERDEVICE, deviceInfoSet, ref deviceInfoData))
{
return false;
}
}
finally
{
if (deviceInfoSet != (IntPtr)(-1))
{
SetupDiDestroyDeviceInfoList(deviceInfoSet);
}
}
return true;
}
public static bool Remove(Guid classGuid, string path, string instanceId)
{
var deviceInfoSet = IntPtr.Zero;
try
{
var deviceInterfaceData = new SP_DEVINFO_DATA();
deviceInterfaceData.cbSize = Marshal.SizeOf(deviceInterfaceData);
deviceInfoSet = SetupDiGetClassDevs(ref classGuid, IntPtr.Zero, IntPtr.Zero,
DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
if (SetupDiOpenDeviceInfo(deviceInfoSet, instanceId, IntPtr.Zero, 0, ref deviceInterfaceData))
{
var props = new SP_REMOVEDEVICE_PARAMS {ClassInstallHeader = new SP_CLASSINSTALL_HEADER()};
props.ClassInstallHeader.cbSize = Marshal.SizeOf(props.ClassInstallHeader);
props.ClassInstallHeader.InstallFunction = DIF_REMOVE;
props.Scope = DI_REMOVEDEVICE_GLOBAL;
props.HwProfile = 0x00;
if (SetupDiSetClassInstallParams(deviceInfoSet, ref deviceInterfaceData, ref props,
Marshal.SizeOf(props)))
{
return SetupDiCallClassInstaller(DIF_REMOVE, deviceInfoSet, ref deviceInterfaceData);
}
}
}
finally
{
if (deviceInfoSet != IntPtr.Zero)
{
SetupDiDestroyDeviceInfoList(deviceInfoSet);
}
}
return false;
}
#region Constant and Structure Definitions
protected const int DIGCF_PRESENT = 0x0002;
protected const int DIGCF_DEVICEINTERFACE = 0x0010;
protected const int DICD_GENERATE_ID = 0x0001;
protected const int SPDRP_HARDWAREID = 0x0001;
protected const int DIF_REMOVE = 0x0005;
protected const int DIF_REGISTERDEVICE = 0x0019;
protected const int DI_REMOVEDEVICE_GLOBAL = 0x0001;
[StructLayout(LayoutKind.Sequential)]
protected struct SP_DEVINFO_DATA
{
internal int cbSize;
internal Guid ClassGuid;
internal int Flags;
internal IntPtr Reserved;
}
[StructLayout(LayoutKind.Sequential)]
protected struct SP_CLASSINSTALL_HEADER
{
internal int cbSize;
internal int InstallFunction;
}
[StructLayout(LayoutKind.Sequential)]
protected struct SP_REMOVEDEVICE_PARAMS
{
internal SP_CLASSINSTALL_HEADER ClassInstallHeader;
internal int Scope;
internal int HwProfile;
}
#endregion
#region Interop Definitions
[DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Auto)]
protected static extern IntPtr SetupDiCreateDeviceInfoList(ref Guid ClassGuid, IntPtr hwndParent);
[DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Auto)]
protected static extern bool SetupDiDestroyDeviceInfoList(IntPtr DeviceInfoSet);
[DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Auto)]
protected static extern bool SetupDiCreateDeviceInfo(IntPtr DeviceInfoSet, string DeviceName, ref Guid ClassGuid,
string DeviceDescription, IntPtr hwndParent, int CreationFlags, ref SP_DEVINFO_DATA DeviceInfoData);
[DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Auto)]
protected static extern bool SetupDiSetDeviceRegistryProperty(IntPtr DeviceInfoSet,
ref SP_DEVINFO_DATA DeviceInfoData, int Property, [MarshalAs(UnmanagedType.LPWStr)] string PropertyBuffer,
int PropertyBufferSize);
[DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Auto)]
protected static extern bool SetupDiCallClassInstaller(int InstallFunction, IntPtr DeviceInfoSet,
ref SP_DEVINFO_DATA DeviceInfoData);
[DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Auto)]
protected static extern IntPtr SetupDiGetClassDevs(ref Guid ClassGuid, IntPtr Enumerator, IntPtr hwndParent,
int Flags);
[DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Auto)]
protected static extern bool SetupDiEnumDeviceInterfaces(IntPtr DeviceInfoSet, IntPtr DeviceInfoData,
ref Guid InterfaceClassGuid, int MemberIndex, ref SP_DEVINFO_DATA DeviceInterfaceData);
[DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Auto)]
protected static extern bool SetupDiGetDeviceInterfaceDetail(IntPtr DeviceInfoSet,
ref SP_DEVINFO_DATA DeviceInterfaceData, IntPtr DeviceInterfaceDetailData, int DeviceInterfaceDetailDataSize,
ref int RequiredSize, ref SP_DEVINFO_DATA DeviceInfoData);
[DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Auto)]
protected static extern int CM_Get_Device_ID(int DevInst, IntPtr Buffer, int BufferLen, int Flags);
[DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Auto)]
protected static extern bool SetupDiOpenDeviceInfo(IntPtr DeviceInfoSet, string DeviceInstanceId,
IntPtr hwndParent, int Flags, ref SP_DEVINFO_DATA DeviceInfoData);
[DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Auto)]
protected static extern bool SetupDiSetClassInstallParams(IntPtr DeviceInfoSet,
ref SP_DEVINFO_DATA DeviceInterfaceData, ref SP_REMOVEDEVICE_PARAMS ClassInstallParams,
int ClassInstallParamsSize);
#endregion
}
}

View File

@ -0,0 +1,192 @@
using System;
using System.Runtime.InteropServices;
namespace MiController.DriverUtils
{
[Flags]
public enum DifxFlags
{
DRIVER_PACKAGE_REPAIR = 0x00000001,
DRIVER_PACKAGE_SILENT = 0x00000002,
DRIVER_PACKAGE_FORCE = 0x00000004,
DRIVER_PACKAGE_ONLY_IF_DEVICE_PRESENT = 0x00000008,
DRIVER_PACKAGE_LEGACY_MODE = 0x00000010,
DRIVER_PACKAGE_DELETE_FILES = 0x00000020,
}
public enum DifxLog
{
DIFXAPI_SUCCESS = 0,
DIFXAPI_INFO = 1,
DIFXAPI_WARNING = 2,
DIFXAPI_ERROR = 3,
}
/// <summary>
/// Driver Install Frameworks API (DIFxAPI)
/// </summary>
public class Difx
{
public delegate void DIFLOGCALLBACK(
DifxLog EventType,
Int32 ErrorCode,
[MarshalAs(UnmanagedType.LPTStr)] String EventDescription,
IntPtr CallbackContext
);
public void Logger(
DifxLog EventType,
Int32 ErrorCode,
[MarshalAs(UnmanagedType.LPTStr)] String EventDescription,
IntPtr CallbackContext)
{
if (onLogEvent != null) onLogEvent(EventType, ErrorCode, EventDescription);
}
protected DIFLOGCALLBACK m_LogCallback;
public delegate void LogEventHandler(DifxLog Event, Int32 Error, String Description);
public LogEventHandler onLogEvent;
protected Difx()
{
m_LogCallback = new DIFLOGCALLBACK(Logger);
}
public virtual UInt32 Preinstall(String InfPath, DifxFlags Flags)
{
return 0xFFFFFFFF;
}
public virtual UInt32 Install(String InfPath, DifxFlags Flags, out Boolean RebootRequired)
{
RebootRequired = false;
return 0xFFFFFFFF;
}
public virtual UInt32 Uninstall(String InfPath, DifxFlags Flags, out Boolean RebootRequired)
{
RebootRequired = false;
return 0xFFFFFFFF;
}
public static Difx Factory(string driverDir)
{
Difx retVal;
Environment.CurrentDirectory = driverDir;
if (Environment.Is64BitProcess)
{
retVal = new Difx64();
}
else
{
retVal = new Difx32();
}
return retVal;
}
}
/// <summary>
/// Driver Install Frameworks API (DIFxAPI) for x86 platform.
/// </summary>
public class Difx32 : Difx
{
[DllImport(@".\x86\DIFxAPI.dll", SetLastError = true, CharSet = CharSet.Auto, CallingConvention = CallingConvention.Winapi)]
private static extern UInt32 DriverPackagePreinstall(
[MarshalAs(UnmanagedType.LPTStr)] String DriverPackageInfPath,
[MarshalAs(UnmanagedType.U4)] UInt32 Flags
);
[DllImport(@".\x86\DIFxAPI.dll", SetLastError = true, CharSet = CharSet.Auto, CallingConvention = CallingConvention.Winapi)]
private static extern UInt32 DriverPackageInstall(
[MarshalAs(UnmanagedType.LPTStr)] String DriverPackageInfPath,
[MarshalAs(UnmanagedType.U4)] UInt32 Flags,
IntPtr pInstallerInfo,
[MarshalAs(UnmanagedType.Bool)] out Boolean pNeedReboot
);
[DllImport(@".\x86\DIFxAPI.dll", SetLastError = true, CharSet = CharSet.Auto, CallingConvention = CallingConvention.Winapi)]
private static extern UInt32 DriverPackageUninstall(
[MarshalAs(UnmanagedType.LPTStr)] String DriverPackageInfPath,
[MarshalAs(UnmanagedType.U4)] UInt32 Flags,
IntPtr pInstallerInfo,
[MarshalAs(UnmanagedType.Bool)] out Boolean pNeedReboot
);
[DllImport(@".\x86\DIFxAPI.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern void SetDifxLogCallback(DIFLOGCALLBACK LogCallback, IntPtr CallbackContext);
public Difx32()
{
SetDifxLogCallback(m_LogCallback, IntPtr.Zero);
}
public override UInt32 Preinstall(String InfPath, DifxFlags Flags)
{
return DriverPackagePreinstall(InfPath, (UInt32)Flags);
}
public override UInt32 Install(String InfPath, DifxFlags Flags, out Boolean RebootRequired)
{
return DriverPackageInstall(InfPath, (UInt32)Flags, IntPtr.Zero, out RebootRequired);
}
public override UInt32 Uninstall(String InfPath, DifxFlags Flags, out Boolean RebootRequired)
{
return DriverPackageUninstall(InfPath, (UInt32)Flags, IntPtr.Zero, out RebootRequired);
}
}
/// <summary>
/// Driver Install Frameworks API (DIFxAPI) for x64 platform.
/// </summary>
public class Difx64 : Difx
{
[DllImport(@".\amd64\DIFxAPI.dll", SetLastError = true, CharSet = CharSet.Auto, CallingConvention = CallingConvention.Winapi)]
private static extern UInt32 DriverPackagePreinstall(
[MarshalAs(UnmanagedType.LPTStr)] String DriverPackageInfPath,
[MarshalAs(UnmanagedType.U4)] UInt32 Flags
);
[DllImport(@".\amd64\DIFxAPI.dll", SetLastError = true, CharSet = CharSet.Auto, CallingConvention = CallingConvention.Winapi)]
private static extern UInt32 DriverPackageInstall(
[MarshalAs(UnmanagedType.LPTStr)] String DriverPackageInfPath,
[MarshalAs(UnmanagedType.U4)] UInt32 Flags,
IntPtr pInstallerInfo,
[MarshalAs(UnmanagedType.Bool)] out Boolean pNeedReboot
);
[DllImport(@".\ScpDriver\amd64\DIFxAPI.dll", SetLastError = true, CharSet = CharSet.Auto, CallingConvention = CallingConvention.Winapi)]
private static extern UInt32 DriverPackageUninstall(
[MarshalAs(UnmanagedType.LPTStr)] String DriverPackageInfPath,
[MarshalAs(UnmanagedType.U4)] UInt32 Flags,
IntPtr pInstallerInfo,
[MarshalAs(UnmanagedType.Bool)] out Boolean pNeedReboot
);
[DllImport(@".\amd64\DIFxAPI.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern void SetDifxLogCallback(DIFLOGCALLBACK LogCallback, IntPtr CallbackContext);
public Difx64()
{
SetDifxLogCallback(m_LogCallback, IntPtr.Zero);
}
public override UInt32 Preinstall(String InfPath, DifxFlags Flags)
{
return DriverPackagePreinstall(InfPath, (UInt32)Flags);
}
public override UInt32 Install(String InfPath, DifxFlags Flags, out Boolean RebootRequired)
{
return DriverPackageInstall(InfPath, (UInt32)Flags, (IntPtr)0, out RebootRequired);
}
public override UInt32 Uninstall(String InfPath, DifxFlags Flags, out Boolean RebootRequired)
{
return DriverPackageUninstall(InfPath, (UInt32)Flags, (IntPtr)0, out RebootRequired);
}
}
}

View File

@ -0,0 +1,19 @@
using System.Text;
namespace HidLibrary
{
public static class Extensions
{
public static string ToUTF8String(this byte[] buffer)
{
var value = Encoding.UTF8.GetString(buffer);
return value.Remove(value.IndexOf((char)0));
}
public static string ToUTF16String(this byte[] buffer)
{
var value = Encoding.Unicode.GetString(buffer);
return value.Remove(value.IndexOf((char)0));
}
}
}

View File

@ -0,0 +1,17 @@
namespace HidLibrary
{
public class HidAsyncState
{
private readonly object _callerDelegate;
private readonly object _callbackDelegate;
public HidAsyncState(object callerDelegate, object callbackDelegate)
{
_callerDelegate = callerDelegate;
_callbackDelegate = callbackDelegate;
}
public object CallerDelegate { get { return _callerDelegate; } }
public object CallbackDelegate { get { return _callbackDelegate; } }
}
}

View File

@ -0,0 +1,664 @@
using System;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
namespace HidLibrary
{
public class HidDevice : IHidDevice
{
public event InsertedEventHandler Inserted;
public event RemovedEventHandler Removed;
private readonly string _description;
private readonly string _devicePath;
private readonly HidDeviceAttributes _deviceAttributes;
private readonly HidDeviceCapabilities _deviceCapabilities;
private DeviceMode _deviceReadMode = DeviceMode.NonOverlapped;
private DeviceMode _deviceWriteMode = DeviceMode.NonOverlapped;
private ShareMode _deviceShareMode = ShareMode.ShareRead | ShareMode.ShareWrite;
private readonly HidDeviceEventMonitor _deviceEventMonitor;
private bool _monitorDeviceEvents;
protected delegate HidDeviceData ReadDelegate(int timeout);
protected delegate HidReport ReadReportDelegate(int timeout);
private delegate bool WriteDelegate(byte[] data, int timeout);
private delegate bool WriteReportDelegate(HidReport report, int timeout);
private NativeOverlapped read_overlapped;
private byte[] read_buffer;
private bool reading;
internal HidDevice(string devicePath, string description = null)
{
_deviceEventMonitor = new HidDeviceEventMonitor(this);
_deviceEventMonitor.Inserted += DeviceEventMonitorInserted;
_deviceEventMonitor.Removed += DeviceEventMonitorRemoved;
_devicePath = devicePath;
_description = description;
try
{
var hidHandle = OpenDeviceIO(_devicePath, NativeMethods.ACCESS_NONE);
_deviceAttributes = GetDeviceAttributes(hidHandle);
_deviceCapabilities = GetDeviceCapabilities(hidHandle);
CloseDeviceIO(hidHandle);
}
catch (Exception exception)
{
throw new Exception(string.Format("Error querying HID device '{0}'.", devicePath), exception);
}
}
public IntPtr Handle { get; private set; }
public bool IsOpen { get; private set; }
public bool IsConnected { get { return HidDevices.IsConnected(_devicePath); } }
public string Description { get { return _description; } }
public HidDeviceCapabilities Capabilities { get { return _deviceCapabilities; } }
public HidDeviceAttributes Attributes { get { return _deviceAttributes; } }
public string DevicePath { get { return _devicePath; } }
public bool MonitorDeviceEvents
{
get { return _monitorDeviceEvents; }
set
{
if (value & _monitorDeviceEvents == false) _deviceEventMonitor.Init();
_monitorDeviceEvents = value;
}
}
public override string ToString()
{
return string.Format("VendorID={0}, ProductID={1}, Version={2}, DevicePath={3}",
_deviceAttributes.VendorHexId,
_deviceAttributes.ProductHexId,
_deviceAttributes.Version,
_devicePath);
}
public void OpenDevice()
{
OpenDevice(DeviceMode.NonOverlapped, DeviceMode.NonOverlapped, ShareMode.ShareRead | ShareMode.ShareWrite);
}
public void OpenDevice(DeviceMode readMode, DeviceMode writeMode, ShareMode shareMode)
{
if (IsOpen) return;
_deviceReadMode = readMode;
_deviceWriteMode = writeMode;
_deviceShareMode = shareMode;
try
{
Handle = OpenDeviceIO(_devicePath, readMode, NativeMethods.GENERIC_READ | NativeMethods.GENERIC_WRITE, shareMode);
}
catch (Exception exception)
{
IsOpen = false;
throw new Exception("Error opening HID device.", exception);
}
IsOpen = Handle.ToInt32() != NativeMethods.INVALID_HANDLE_VALUE;
if (!IsOpen)
{
throw new Exception("Error opening HID device.");
}
}
public void CloseDevice()
{
if (!IsOpen) return;
CloseDeviceIO(Handle);
IsOpen = false;
}
public HidDeviceData Read()
{
return Read(0);
}
public HidDeviceData Read(int timeout)
{
if (IsOpen == false) return new HidDeviceData(HidDeviceData.ReadStatus.ReadError);
try
{
return ReadData(timeout);
}
catch
{
return new HidDeviceData(HidDeviceData.ReadStatus.ReadError);
}
}
public void Read(ReadCallback callback)
{
Read(callback, 0);
}
public void Read(ReadCallback callback, int timeout)
{
var readDelegate = new ReadDelegate(Read);
var asyncState = new HidAsyncState(readDelegate, callback);
readDelegate.BeginInvoke(timeout, EndRead, asyncState);
}
public async Task<HidDeviceData> ReadAsync(int timeout = 0)
{
var readDelegate = new ReadDelegate(Read);
return await Task<HidDeviceData>.Factory.FromAsync(readDelegate.BeginInvoke, readDelegate.EndInvoke, timeout, null);
}
public HidReport ReadReport()
{
return ReadReport(0);
}
public HidReport ReadReport(int timeout)
{
return new HidReport(Capabilities.InputReportByteLength, Read(timeout));
}
public void ReadReport(ReadReportCallback callback)
{
ReadReport(callback, 0);
}
public void ReadReport(ReadReportCallback callback, int timeout)
{
var readReportDelegate = new ReadReportDelegate(ReadReport);
var asyncState = new HidAsyncState(readReportDelegate, callback);
readReportDelegate.BeginInvoke(timeout, EndReadReport, asyncState);
}
public async Task<HidReport> ReadReportAsync(int timeout = 0)
{
var readReportDelegate = new ReadReportDelegate(ReadReport);
return await Task<HidReport>.Factory.FromAsync(readReportDelegate.BeginInvoke, readReportDelegate.EndInvoke, timeout, null);
}
public bool ReadFeatureData(out byte[] data, byte reportId = 0)
{
if (_deviceCapabilities.FeatureReportByteLength <= 0)
{
data = new byte[0];
return false;
}
data = new byte[_deviceCapabilities.FeatureReportByteLength];
var buffer = CreateFeatureOutputBuffer();
buffer[0] = reportId;
IntPtr hidHandle = IntPtr.Zero;
bool success = false;
try
{
if (IsOpen)
hidHandle = Handle;
else
hidHandle = OpenDeviceIO(_devicePath, NativeMethods.ACCESS_NONE);
success = NativeMethods.HidD_GetFeature(hidHandle, buffer, buffer.Length);
if (success)
{
Array.Copy(buffer, 0, data, 0, Math.Min(data.Length, _deviceCapabilities.FeatureReportByteLength));
}
}
catch (Exception exception)
{
throw new Exception(string.Format("Error accessing HID device '{0}'.", _devicePath), exception);
}
finally
{
if (hidHandle != IntPtr.Zero && hidHandle != Handle)
CloseDeviceIO(hidHandle);
}
return success;
}
public bool ReadProduct(out byte[] data)
{
data = new byte[254];
IntPtr hidHandle = IntPtr.Zero;
bool success = false;
try
{
if (IsOpen)
hidHandle = Handle;
else
hidHandle = OpenDeviceIO(_devicePath, NativeMethods.ACCESS_NONE);
success = NativeMethods.HidD_GetProductString(hidHandle, ref data[0], data.Length);
}
catch (Exception exception)
{
throw new Exception(string.Format("Error accessing HID device '{0}'.", _devicePath), exception);
}
finally
{
if (hidHandle != IntPtr.Zero && hidHandle != Handle)
CloseDeviceIO(hidHandle);
}
return success;
}
public bool ReadManufacturer(out byte[] data)
{
data = new byte[254];
IntPtr hidHandle = IntPtr.Zero;
bool success = false;
try
{
if (IsOpen)
hidHandle = Handle;
else
hidHandle = OpenDeviceIO(_devicePath, NativeMethods.ACCESS_NONE);
success = NativeMethods.HidD_GetManufacturerString(hidHandle, ref data[0], data.Length);
}
catch (Exception exception)
{
throw new Exception(string.Format("Error accessing HID device '{0}'.", _devicePath), exception);
}
finally
{
if (hidHandle != IntPtr.Zero && hidHandle != Handle)
CloseDeviceIO(hidHandle);
}
return success;
}
public bool ReadSerialNumber(out byte[] data)
{
data = new byte[254];
IntPtr hidHandle = IntPtr.Zero;
bool success = false;
try
{
if (IsOpen)
hidHandle = Handle;
else
hidHandle = OpenDeviceIO(_devicePath, NativeMethods.ACCESS_NONE);
success = NativeMethods.HidD_GetSerialNumberString(hidHandle, ref data[0], data.Length);
}
catch (Exception exception)
{
throw new Exception(string.Format("Error accessing HID device '{0}'.", _devicePath), exception);
}
finally
{
if (hidHandle != IntPtr.Zero && hidHandle != Handle)
CloseDeviceIO(hidHandle);
}
return success;
}
public bool Write(byte[] data)
{
return Write(data, 0);
}
public bool Write(byte[] data, int timeout)
{
if (IsOpen == false) OpenDevice(_deviceReadMode, _deviceWriteMode, _deviceShareMode);
try
{
return WriteData(data, timeout);
}
catch
{
return false;
}
}
public void Write(byte[] data, WriteCallback callback)
{
Write(data, callback, 0);
}
public void Write(byte[] data, WriteCallback callback, int timeout)
{
var writeDelegate = new WriteDelegate(Write);
var asyncState = new HidAsyncState(writeDelegate, callback);
writeDelegate.BeginInvoke(data, timeout, EndWrite, asyncState);
}
public async Task<bool> WriteAsync(byte[] data, int timeout = 0)
{
var writeDelegate = new WriteDelegate(Write);
return await Task<bool>.Factory.FromAsync(writeDelegate.BeginInvoke, writeDelegate.EndInvoke, data, timeout, null);
}
public bool WriteReport(HidReport report)
{
return WriteReport(report, 0);
}
public bool WriteReport(HidReport report, int timeout)
{
return Write(report.GetBytes(), timeout);
}
public void WriteReport(HidReport report, WriteCallback callback)
{
WriteReport(report, callback, 0);
}
public void WriteReport(HidReport report, WriteCallback callback, int timeout)
{
var writeReportDelegate = new WriteReportDelegate(WriteReport);
var asyncState = new HidAsyncState(writeReportDelegate, callback);
writeReportDelegate.BeginInvoke(report, timeout, EndWriteReport, asyncState);
}
public async Task<bool> WriteReportAsync(HidReport report, int timeout = 0)
{
var writeReportDelegate = new WriteReportDelegate(WriteReport);
return await Task<bool>.Factory.FromAsync(writeReportDelegate.BeginInvoke, writeReportDelegate.EndInvoke, report, timeout, null);
}
public HidReport CreateReport()
{
return new HidReport(Capabilities.OutputReportByteLength);
}
public bool WriteFeatureData(byte[] data)
{
if (_deviceCapabilities.FeatureReportByteLength <= 0) return false;
var buffer = CreateFeatureOutputBuffer();
Array.Copy(data, 0, buffer, 0, Math.Min(data.Length, _deviceCapabilities.FeatureReportByteLength));
IntPtr hidHandle = IntPtr.Zero;
bool success = false;
try
{
if (IsOpen)
hidHandle = Handle;
else
return false;
//var overlapped = new NativeOverlapped();
success = NativeMethods.HidD_SetFeature(hidHandle, buffer, buffer.Length);
}
catch (Exception exception)
{
throw new Exception(string.Format("Error accessing HID device '{0}'.", _devicePath), exception);
}
return success;
}
protected static void EndRead(IAsyncResult ar)
{
var hidAsyncState = (HidAsyncState)ar.AsyncState;
var callerDelegate = (ReadDelegate)hidAsyncState.CallerDelegate;
var callbackDelegate = (ReadCallback)hidAsyncState.CallbackDelegate;
var data = callerDelegate.EndInvoke(ar);
if ((callbackDelegate != null)) callbackDelegate.Invoke(data);
}
protected static void EndReadReport(IAsyncResult ar)
{
var hidAsyncState = (HidAsyncState)ar.AsyncState;
var callerDelegate = (ReadReportDelegate)hidAsyncState.CallerDelegate;
var callbackDelegate = (ReadReportCallback)hidAsyncState.CallbackDelegate;
var report = callerDelegate.EndInvoke(ar);
if ((callbackDelegate != null)) callbackDelegate.Invoke(report);
}
private static void EndWrite(IAsyncResult ar)
{
var hidAsyncState = (HidAsyncState)ar.AsyncState;
var callerDelegate = (WriteDelegate)hidAsyncState.CallerDelegate;
var callbackDelegate = (WriteCallback)hidAsyncState.CallbackDelegate;
var result = callerDelegate.EndInvoke(ar);
if ((callbackDelegate != null)) callbackDelegate.Invoke(result);
}
private static void EndWriteReport(IAsyncResult ar)
{
var hidAsyncState = (HidAsyncState)ar.AsyncState;
var callerDelegate = (WriteReportDelegate)hidAsyncState.CallerDelegate;
var callbackDelegate = (WriteCallback)hidAsyncState.CallbackDelegate;
var result = callerDelegate.EndInvoke(ar);
if ((callbackDelegate != null)) callbackDelegate.Invoke(result);
}
private byte[] CreateInputBuffer()
{
return CreateBuffer(Capabilities.InputReportByteLength - 1);
}
private byte[] CreateOutputBuffer()
{
return CreateBuffer(Capabilities.OutputReportByteLength - 1);
}
private byte[] CreateFeatureOutputBuffer()
{
return CreateBuffer(Capabilities.FeatureReportByteLength - 1);
}
private static byte[] CreateBuffer(int length)
{
byte[] buffer = null;
Array.Resize(ref buffer, length + 1);
return buffer;
}
private static HidDeviceAttributes GetDeviceAttributes(IntPtr hidHandle)
{
var deviceAttributes = default(NativeMethods.HIDD_ATTRIBUTES);
deviceAttributes.Size = Marshal.SizeOf(deviceAttributes);
NativeMethods.HidD_GetAttributes(hidHandle, ref deviceAttributes);
return new HidDeviceAttributes(deviceAttributes);
}
private static HidDeviceCapabilities GetDeviceCapabilities(IntPtr hidHandle)
{
var capabilities = default(NativeMethods.HIDP_CAPS);
var preparsedDataPointer = default(IntPtr);
if (NativeMethods.HidD_GetPreparsedData(hidHandle, ref preparsedDataPointer))
{
NativeMethods.HidP_GetCaps(preparsedDataPointer, ref capabilities);
NativeMethods.HidD_FreePreparsedData(preparsedDataPointer);
}
return new HidDeviceCapabilities(capabilities);
}
private bool WriteData(byte[] data, int timeout)
{
if (_deviceCapabilities.OutputReportByteLength <= 0) return false;
var buffer = CreateOutputBuffer();
uint bytesWritten = 0;
Array.Copy(data, 0, buffer, 0, Math.Min(data.Length, _deviceCapabilities.OutputReportByteLength));
if (_deviceWriteMode == DeviceMode.Overlapped)
{
var security = new NativeMethods.SECURITY_ATTRIBUTES();
var overlapped = new NativeOverlapped();
var overlapTimeout = timeout <= 0 ? NativeMethods.WAIT_INFINITE : timeout;
security.lpSecurityDescriptor = IntPtr.Zero;
security.bInheritHandle = true;
security.nLength = Marshal.SizeOf(security);
overlapped.OffsetLow = 0;
overlapped.OffsetHigh = 0;
overlapped.EventHandle = NativeMethods.CreateEvent(ref security, Convert.ToInt32(false), Convert.ToInt32(true), "");
try
{
NativeMethods.WriteFile(Handle, buffer, (uint)buffer.Length, out bytesWritten, ref overlapped);
}
catch { return false; }
var result = NativeMethods.WaitForSingleObject(overlapped.EventHandle, overlapTimeout);
switch (result)
{
case NativeMethods.WAIT_OBJECT_0:
return true;
case NativeMethods.WAIT_TIMEOUT:
return false;
case NativeMethods.WAIT_FAILED:
return false;
default:
return false;
}
}
else
{
try
{
var overlapped = new NativeOverlapped();
return NativeMethods.WriteFile(Handle, buffer, (uint)buffer.Length, out bytesWritten, ref overlapped);
}
catch { return false; }
}
}
protected HidDeviceData ReadData(int timeout)
{
var buffer = new byte[] { };
var status = HidDeviceData.ReadStatus.NoDataRead;
if (_deviceCapabilities.InputReportByteLength > 0)
{
uint bytesRead = 0;
if (_deviceReadMode == DeviceMode.Overlapped)
{
if (!reading)
{
read_buffer = CreateInputBuffer();
var security = new NativeMethods.SECURITY_ATTRIBUTES();
read_overlapped = new NativeOverlapped();
security.lpSecurityDescriptor = IntPtr.Zero;
security.bInheritHandle = true;
security.nLength = Marshal.SizeOf(security);
read_overlapped.OffsetLow = 0;
read_overlapped.OffsetHigh = 0;
read_overlapped.EventHandle = NativeMethods.CreateEvent(ref security, Convert.ToInt32(false), Convert.ToInt32(true), string.Empty);
NativeMethods.ReadFile(Handle, read_buffer, (uint)read_buffer.Length, out bytesRead, ref read_overlapped);
}
try
{
var result = NativeMethods.WaitForSingleObject(read_overlapped.EventHandle, timeout);
switch (result)
{
case NativeMethods.WAIT_OBJECT_0: status = HidDeviceData.ReadStatus.Success; reading = false; CloseDeviceIO(read_overlapped.EventHandle); return new HidDeviceData(read_buffer, status);
case NativeMethods.WAIT_TIMEOUT:
status = HidDeviceData.ReadStatus.WaitTimedOut;
buffer = new byte[] { };
reading = true;
break;
case NativeMethods.WAIT_FAILED:
status = HidDeviceData.ReadStatus.WaitFail;
buffer = new byte[] { };
reading = false;
CloseDeviceIO(read_overlapped.EventHandle);
break;
default:
status = HidDeviceData.ReadStatus.NoDataRead;
buffer = new byte[] { };
reading = false;
CloseDeviceIO(read_overlapped.EventHandle);
break;
}
}
catch { status = HidDeviceData.ReadStatus.ReadError; reading = false; CloseDeviceIO(read_overlapped.EventHandle); }
}
else
{
try
{
var overlapped = new NativeOverlapped();
NativeMethods.ReadFile(Handle, buffer, (uint)buffer.Length, out bytesRead, ref overlapped);
status = HidDeviceData.ReadStatus.Success;
}
catch { status = HidDeviceData.ReadStatus.ReadError; }
}
}
return new HidDeviceData(buffer, status);
}
private static IntPtr OpenDeviceIO(string devicePath, uint deviceAccess)
{
return OpenDeviceIO(devicePath, DeviceMode.NonOverlapped, deviceAccess, ShareMode.ShareRead | ShareMode.ShareWrite);
}
private static IntPtr OpenDeviceIO(string devicePath, DeviceMode deviceMode, uint deviceAccess, ShareMode shareMode)
{
var security = new NativeMethods.SECURITY_ATTRIBUTES();
var flags = 0;
if (deviceMode == DeviceMode.Overlapped) flags = NativeMethods.FILE_FLAG_OVERLAPPED;
security.lpSecurityDescriptor = IntPtr.Zero;
security.bInheritHandle = true;
security.nLength = Marshal.SizeOf(security);
return NativeMethods.CreateFile(devicePath, deviceAccess, (int)shareMode, ref security, NativeMethods.OPEN_EXISTING, flags, 0);
}
private static void CloseDeviceIO(IntPtr handle)
{
if (Environment.OSVersion.Version.Major > 5)
{
NativeMethods.CancelIoEx(handle, IntPtr.Zero);
}
NativeMethods.CloseHandle(handle);
}
private void DeviceEventMonitorInserted()
{
if (!IsOpen) OpenDevice(_deviceReadMode, _deviceWriteMode, _deviceShareMode);
if (Inserted != null) Inserted();
}
private void DeviceEventMonitorRemoved()
{
if (IsOpen) CloseDevice();
if (Removed != null) Removed();
}
public void Dispose()
{
if (MonitorDeviceEvents) MonitorDeviceEvents = false;
if (IsOpen) CloseDevice();
}
}
}

View File

@ -0,0 +1,21 @@
namespace HidLibrary
{
public class HidDeviceAttributes
{
internal HidDeviceAttributes(NativeMethods.HIDD_ATTRIBUTES attributes)
{
VendorId = attributes.VendorID;
ProductId = attributes.ProductID;
Version = attributes.VersionNumber;
VendorHexId = "0x" + attributes.VendorID.ToString("X4");
ProductHexId = "0x" + attributes.ProductID.ToString("X4");
}
public int VendorId { get; private set; }
public int ProductId { get; private set; }
public int Version { get; private set; }
public string VendorHexId { get; set; }
public string ProductHexId { get; set; }
}
}

View File

@ -0,0 +1,43 @@
namespace HidLibrary
{
public class HidDeviceCapabilities
{
internal HidDeviceCapabilities(NativeMethods.HIDP_CAPS capabilities)
{
Usage = capabilities.Usage;
UsagePage = capabilities.UsagePage;
InputReportByteLength = capabilities.InputReportByteLength;
OutputReportByteLength = capabilities.OutputReportByteLength;
FeatureReportByteLength = capabilities.FeatureReportByteLength;
Reserved = capabilities.Reserved;
NumberLinkCollectionNodes = capabilities.NumberLinkCollectionNodes;
NumberInputButtonCaps = capabilities.NumberInputButtonCaps;
NumberInputValueCaps = capabilities.NumberInputValueCaps;
NumberInputDataIndices = capabilities.NumberInputDataIndices;
NumberOutputButtonCaps = capabilities.NumberOutputButtonCaps;
NumberOutputValueCaps = capabilities.NumberOutputValueCaps;
NumberOutputDataIndices = capabilities.NumberOutputDataIndices;
NumberFeatureButtonCaps = capabilities.NumberFeatureButtonCaps;
NumberFeatureValueCaps = capabilities.NumberFeatureValueCaps;
NumberFeatureDataIndices = capabilities.NumberFeatureDataIndices;
}
public short Usage { get; private set; }
public short UsagePage { get; private set; }
public short InputReportByteLength { get; private set; }
public short OutputReportByteLength { get; private set; }
public short FeatureReportByteLength { get; private set; }
public short[] Reserved { get; private set; }
public short NumberLinkCollectionNodes { get; private set; }
public short NumberInputButtonCaps { get; private set; }
public short NumberInputValueCaps { get; private set; }
public short NumberInputDataIndices { get; private set; }
public short NumberOutputButtonCaps { get; private set; }
public short NumberOutputValueCaps { get; private set; }
public short NumberOutputDataIndices { get; private set; }
public short NumberFeatureButtonCaps { get; private set; }
public short NumberFeatureValueCaps { get; private set; }
public short NumberFeatureDataIndices { get; private set; }
}
}

View File

@ -0,0 +1,30 @@
namespace HidLibrary
{
public class HidDeviceData
{
public enum ReadStatus
{
Success = 0,
WaitTimedOut = 1,
WaitFail = 2,
NoDataRead = 3,
ReadError = 4,
NotConnected = 5
}
public HidDeviceData(ReadStatus status)
{
Data = new byte[] {};
Status = status;
}
public HidDeviceData(byte[] data, ReadStatus status)
{
Data = data;
Status = status;
}
public byte[] Data { get; private set; }
public ReadStatus Status { get; private set; }
}
}

View File

@ -0,0 +1,49 @@
using System;
using System.Threading;
namespace HidLibrary
{
internal class HidDeviceEventMonitor
{
public event InsertedEventHandler Inserted;
public event RemovedEventHandler Removed;
public delegate void InsertedEventHandler();
public delegate void RemovedEventHandler();
private readonly HidDevice _device;
private bool _wasConnected;
public HidDeviceEventMonitor(HidDevice device)
{
_device = device;
}
public void Init()
{
var eventMonitor = new Action(DeviceEventMonitor);
eventMonitor.BeginInvoke(DisposeDeviceEventMonitor, eventMonitor);
}
private void DeviceEventMonitor()
{
var isConnected = _device.IsConnected;
if (isConnected != _wasConnected)
{
if (isConnected && Inserted != null) Inserted();
else if (!isConnected && Removed != null) Removed();
_wasConnected = isConnected;
}
Thread.Sleep(500);
if (_device.MonitorDeviceEvents) Init();
}
private static void DisposeDeviceEventMonitor(IAsyncResult ar)
{
((Action)ar.AsyncState).EndInvoke(ar);
}
}
}

View File

@ -0,0 +1,152 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
namespace HidLibrary
{
public class HidDevices
{
private static Guid _hidClassGuid = Guid.Empty;
public static bool IsConnected(string devicePath)
{
return EnumerateDevices().Any(x => x.Path == devicePath);
}
public static HidDevice GetDevice(string devicePath)
{
return Enumerate(devicePath).FirstOrDefault();
}
public static IEnumerable<HidDevice> Enumerate()
{
return EnumerateDevices().Select(x => new HidDevice(x.Path, x.Description));
}
public static IEnumerable<HidDevice> Enumerate(string devicePath)
{
return EnumerateDevices().Where(x => x.Path == devicePath).Select(x => new HidDevice(x.Path, x.Description));
}
public static IEnumerable<HidDevice> Enumerate(int vendorId, params int[] productIds)
{
return EnumerateDevices().Select(x => new HidDevice(x.Path, x.Description)).Where(x => x.Attributes.VendorId == vendorId &&
productIds.Contains(x.Attributes.ProductId));
}
public static IEnumerable<HidDevice> Enumerate(int vendorId)
{
return EnumerateDevices().Select(x => new HidDevice(x.Path, x.Description)).Where(x => x.Attributes.VendorId == vendorId);
}
private class DeviceInfo { public string Path { get; set; } public string Description { get; set; } }
private static IEnumerable<DeviceInfo> EnumerateDevices()
{
var devices = new List<DeviceInfo>();
var hidClass = HidClassGuid;
var deviceInfoSet = NativeMethods.SetupDiGetClassDevs(ref hidClass, null, 0, NativeMethods.DIGCF_PRESENT | NativeMethods.DIGCF_DEVICEINTERFACE);
if (deviceInfoSet.ToInt64() != NativeMethods.INVALID_HANDLE_VALUE)
{
var deviceInfoData = CreateDeviceInfoData();
var deviceIndex = 0;
while (NativeMethods.SetupDiEnumDeviceInfo(deviceInfoSet, deviceIndex, ref deviceInfoData))
{
deviceIndex += 1;
var deviceInterfaceData = new NativeMethods.SP_DEVICE_INTERFACE_DATA();
deviceInterfaceData.cbSize = Marshal.SizeOf(deviceInterfaceData);
var deviceInterfaceIndex = 0;
while (NativeMethods.SetupDiEnumDeviceInterfaces(deviceInfoSet, ref deviceInfoData, ref hidClass, deviceInterfaceIndex, ref deviceInterfaceData))
{
deviceInterfaceIndex++;
var devicePath = GetDevicePath(deviceInfoSet, deviceInterfaceData);
var description = GetBusReportedDeviceDescription(deviceInfoSet, ref deviceInfoData) ??
GetDeviceDescription(deviceInfoSet, ref deviceInfoData);
devices.Add(new DeviceInfo { Path = devicePath, Description = description });
}
}
NativeMethods.SetupDiDestroyDeviceInfoList(deviceInfoSet);
}
return devices;
}
private static NativeMethods.SP_DEVINFO_DATA CreateDeviceInfoData()
{
var deviceInfoData = new NativeMethods.SP_DEVINFO_DATA();
deviceInfoData.cbSize = Marshal.SizeOf(deviceInfoData);
deviceInfoData.DevInst = 0;
deviceInfoData.ClassGuid = Guid.Empty;
deviceInfoData.Reserved = IntPtr.Zero;
return deviceInfoData;
}
private static string GetDevicePath(IntPtr deviceInfoSet, NativeMethods.SP_DEVICE_INTERFACE_DATA deviceInterfaceData)
{
var bufferSize = 0;
var interfaceDetail = new NativeMethods.SP_DEVICE_INTERFACE_DETAIL_DATA { Size = IntPtr.Size == 4 ? 4 + Marshal.SystemDefaultCharSize : 8 };
NativeMethods.SetupDiGetDeviceInterfaceDetailBuffer(deviceInfoSet, ref deviceInterfaceData, IntPtr.Zero, 0, ref bufferSize, IntPtr.Zero);
return NativeMethods.SetupDiGetDeviceInterfaceDetail(deviceInfoSet, ref deviceInterfaceData, ref interfaceDetail, bufferSize, ref bufferSize, IntPtr.Zero) ?
interfaceDetail.DevicePath : null;
}
private static Guid HidClassGuid
{
get
{
if (_hidClassGuid.Equals(Guid.Empty)) NativeMethods.HidD_GetHidGuid(ref _hidClassGuid);
return _hidClassGuid;
}
}
private static string GetDeviceDescription(IntPtr deviceInfoSet, ref NativeMethods.SP_DEVINFO_DATA devinfoData)
{
var descriptionBuffer = new byte[1024];
var requiredSize = 0;
var type = 0;
NativeMethods.SetupDiGetDeviceRegistryProperty(deviceInfoSet,
ref devinfoData,
NativeMethods.SPDRP_DEVICEDESC,
ref type,
descriptionBuffer,
descriptionBuffer.Length,
ref requiredSize);
return descriptionBuffer.ToUTF8String();
}
private static string GetBusReportedDeviceDescription(IntPtr deviceInfoSet, ref NativeMethods.SP_DEVINFO_DATA devinfoData)
{
var descriptionBuffer = new byte[1024];
if (Environment.OSVersion.Version.Major > 5)
{
ulong propertyType = 0;
var requiredSize = 0;
var _continue = NativeMethods.SetupDiGetDeviceProperty(deviceInfoSet,
ref devinfoData,
ref NativeMethods.DEVPKEY_Device_BusReportedDeviceDesc,
ref propertyType,
descriptionBuffer,
descriptionBuffer.Length,
ref requiredSize,
0);
if (_continue) return descriptionBuffer.ToUTF16String();
}
return null;
}
}
}

View File

@ -0,0 +1,76 @@
using System.Threading.Tasks;
namespace HidLibrary
{
public class HidFastReadDevice : HidDevice
{
internal HidFastReadDevice(string devicePath, string description = null)
: base(devicePath, description) { }
// FastRead assumes that the device is connected,
// which could cause stability issues if hardware is
// disconnected during a read
public HidDeviceData FastRead()
{
return FastRead(0);
}
public HidDeviceData FastRead(int timeout)
{
try
{
return ReadData(timeout);
}
catch
{
return new HidDeviceData(HidDeviceData.ReadStatus.ReadError);
}
}
public void FastRead(ReadCallback callback)
{
FastRead(callback, 0);
}
public void FastRead(ReadCallback callback, int timeout)
{
var readDelegate = new ReadDelegate(FastRead);
var asyncState = new HidAsyncState(readDelegate, callback);
readDelegate.BeginInvoke(timeout, EndRead, asyncState);
}
public async Task<HidDeviceData> FastReadAsync(int timeout = 0)
{
var readDelegate = new ReadDelegate(FastRead);
return await Task<HidDeviceData>.Factory.FromAsync(readDelegate.BeginInvoke, readDelegate.EndInvoke, timeout, null);
}
public HidReport FastReadReport()
{
return FastReadReport(0);
}
public HidReport FastReadReport(int timeout)
{
return new HidReport(Capabilities.InputReportByteLength, FastRead(timeout));
}
public void FastReadReport(ReadReportCallback callback)
{
FastReadReport(callback, 0);
}
public void FastReadReport(ReadReportCallback callback, int timeout)
{
var readReportDelegate = new ReadReportDelegate(FastReadReport);
var asyncState = new HidAsyncState(readReportDelegate, callback);
readReportDelegate.BeginInvoke(timeout, EndReadReport, asyncState);
}
public async Task<HidReport> FastReadReportAsync(int timeout = 0)
{
var readReportDelegate = new ReadReportDelegate(FastReadReport);
return await Task<HidReport>.Factory.FromAsync(readReportDelegate.BeginInvoke, readReportDelegate.EndInvoke, timeout, null);
}
}
}

View File

@ -0,0 +1,75 @@
using System;
namespace HidLibrary
{
public class HidReport
{
private byte _reportId;
private byte[] _data = new byte[] {};
private readonly HidDeviceData.ReadStatus _status;
public HidReport(int reportSize)
{
Array.Resize(ref _data, reportSize - 1);
}
public HidReport(int reportSize, HidDeviceData deviceData)
{
_status = deviceData.Status;
Array.Resize(ref _data, reportSize - 1);
if ((deviceData.Data != null))
{
if (deviceData.Data.Length > 0)
{
_reportId = deviceData.Data[0];
Exists = true;
if (deviceData.Data.Length > 1)
{
var dataLength = reportSize - 1;
if (deviceData.Data.Length < reportSize - 1) dataLength = deviceData.Data.Length;
Array.Copy(deviceData.Data, 1, _data, 0, dataLength);
}
}
else Exists = false;
}
else Exists = false;
}
public bool Exists { get; private set; }
public HidDeviceData.ReadStatus ReadStatus { get { return _status; } }
public byte ReportId
{
get { return _reportId; }
set
{
_reportId = value;
Exists = true;
}
}
public byte[] Data
{
get { return _data; }
set
{
_data = value;
Exists = true;
}
}
public byte[] GetBytes()
{
byte[] data = null;
Array.Resize(ref data, _data.Length + 1);
data[0] = _reportId;
Array.Copy(_data, 0, data, 1, _data.Length);
return data;
}
}
}

View File

@ -0,0 +1,99 @@
using System;
using System.Threading.Tasks;
namespace HidLibrary
{
public delegate void InsertedEventHandler();
public delegate void RemovedEventHandler();
public enum DeviceMode
{
NonOverlapped = 0,
Overlapped = 1
}
[Flags]
public enum ShareMode
{
Exclusive = 0,
ShareRead = NativeMethods.FILE_SHARE_READ,
ShareWrite = NativeMethods.FILE_SHARE_WRITE
}
public delegate void ReadCallback(HidDeviceData data);
public delegate void ReadReportCallback(HidReport report);
public delegate void WriteCallback(bool success);
public interface IHidDevice : IDisposable
{
event InsertedEventHandler Inserted;
event RemovedEventHandler Removed;
IntPtr Handle { get; }
bool IsOpen { get; }
bool IsConnected { get; }
string Description { get; }
HidDeviceCapabilities Capabilities { get; }
HidDeviceAttributes Attributes { get; }
string DevicePath { get; }
bool MonitorDeviceEvents { get; set; }
void OpenDevice();
void OpenDevice(DeviceMode readMode, DeviceMode writeMode, ShareMode shareMode);
void CloseDevice();
HidDeviceData Read();
void Read(ReadCallback callback);
void Read(ReadCallback callback, int timeout);
Task<HidDeviceData> ReadAsync(int timeout = 0);
HidDeviceData Read(int timeout);
void ReadReport(ReadReportCallback callback);
void ReadReport(ReadReportCallback callback, int timeout);
Task<HidReport> ReadReportAsync(int timeout = 0);
HidReport ReadReport(int timeout);
HidReport ReadReport();
bool ReadFeatureData(out byte[] data, byte reportId = 0);
bool ReadProduct(out byte[] data);
bool ReadManufacturer(out byte[] data);
bool ReadSerialNumber(out byte[] data);
void Write(byte[] data, WriteCallback callback);
bool Write(byte[] data);
bool Write(byte[] data, int timeout);
void Write(byte[] data, WriteCallback callback, int timeout);
Task<bool> WriteAsync(byte[] data, int timeout = 0);
void WriteReport(HidReport report, WriteCallback callback);
bool WriteReport(HidReport report);
bool WriteReport(HidReport report, int timeout);
void WriteReport(HidReport report, WriteCallback callback, int timeout);
Task<bool> WriteReportAsync(HidReport report, int timeout = 0);
HidReport CreateReport();
bool WriteFeatureData(byte[] data);
}
}

View File

@ -0,0 +1,57 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace HidLibrary
{
public interface IHidEnumerator
{
bool IsConnected(string devicePath);
IHidDevice GetDevice(string devicePath);
IEnumerable<IHidDevice> Enumerate();
IEnumerable<IHidDevice> Enumerate(string devicePath);
IEnumerable<IHidDevice> Enumerate(int vendorId, params int[] productIds);
IEnumerable<IHidDevice> Enumerate(int vendorId);
}
// Instance class that wraps HidDevices
// The purpose of this is to allow consumer classes to create
// their own enumeration abstractions, either for testing or
// for comparing different implementations
public class HidEnumerator : IHidEnumerator
{
public bool IsConnected(string devicePath)
{
return HidDevices.IsConnected(devicePath);
}
public IHidDevice GetDevice(string devicePath)
{
return HidDevices.GetDevice(devicePath) as IHidDevice;
}
public IEnumerable<IHidDevice> Enumerate()
{
return HidDevices.Enumerate().
Select(d => d as IHidDevice);
}
public IEnumerable<IHidDevice> Enumerate(string devicePath)
{
return HidDevices.Enumerate(devicePath).
Select(d => d as IHidDevice);
}
public IEnumerable<IHidDevice> Enumerate(int vendorId, params int[] productIds)
{
return HidDevices.Enumerate(vendorId, productIds).
Select(d => d as IHidDevice);
}
public IEnumerable<IHidDevice> Enumerate(int vendorId)
{
return HidDevices.Enumerate(vendorId).
Select(d => d as IHidDevice);
}
}
}

View File

@ -0,0 +1,372 @@
using System;
using System.Runtime.InteropServices;
namespace HidLibrary
{
internal static class NativeMethods
{
internal const int FILE_FLAG_OVERLAPPED = 0x40000000;
internal const short FILE_SHARE_READ = 0x1;
internal const short FILE_SHARE_WRITE = 0x2;
internal const uint GENERIC_READ = 0x80000000;
internal const uint GENERIC_WRITE = 0x40000000;
internal const int ACCESS_NONE = 0;
internal const int INVALID_HANDLE_VALUE = -1;
internal const short OPEN_EXISTING = 3;
internal const int WAIT_TIMEOUT = 0x102;
internal const uint WAIT_OBJECT_0 = 0;
internal const uint WAIT_FAILED = 0xffffffff;
internal const int WAIT_INFINITE = 0xffff;
[StructLayout(LayoutKind.Sequential)]
internal struct OVERLAPPED
{
public int Internal;
public int InternalHigh;
public int Offset;
public int OffsetHigh;
public int hEvent;
}
[StructLayout(LayoutKind.Sequential)]
internal struct SECURITY_ATTRIBUTES
{
public int nLength;
public IntPtr lpSecurityDescriptor;
public bool bInheritHandle;
}
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true, CharSet = CharSet.Auto)]
static internal extern bool CancelIo(IntPtr hFile);
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true, CharSet = CharSet.Auto)]
static internal extern bool CancelIoEx(IntPtr hFile, IntPtr lpOverlapped);
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true, CharSet = CharSet.Auto)]
static internal extern bool CloseHandle(IntPtr hObject);
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true, CharSet = CharSet.Auto)]
static internal extern bool CancelSynchronousIo(IntPtr hObject);
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
static internal extern IntPtr CreateEvent(ref SECURITY_ATTRIBUTES securityAttributes, int bManualReset, int bInitialState, string lpName);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static internal extern IntPtr CreateFile(string lpFileName, uint dwDesiredAccess, int dwShareMode, ref SECURITY_ATTRIBUTES lpSecurityAttributes, int dwCreationDisposition, int dwFlagsAndAttributes, int hTemplateFile);
[DllImport("kernel32.dll", SetLastError = true)]
static internal extern bool ReadFile(IntPtr hFile, [Out] byte[] lpBuffer, uint nNumberOfBytesToRead, out uint lpNumberOfBytesRead, [In] ref System.Threading.NativeOverlapped lpOverlapped);
[DllImport("kernel32.dll")]
static internal extern uint WaitForSingleObject(IntPtr hHandle, int dwMilliseconds);
[DllImport("kernel32.dll")]
static internal extern bool GetOverlappedResult(IntPtr hFile, [In] ref System.Threading.NativeOverlapped lpOverlapped, out uint lpNumberOfBytesRead, bool bwait);
[DllImport("kernel32.dll")]
static internal extern bool WriteFile(IntPtr hFile, byte[] lpBuffer, uint nNumberOfBytesToWrite, out uint lpNumberOfBytesWritten, [In] ref System.Threading.NativeOverlapped lpOverlapped);
internal const int DBT_DEVICEARRIVAL = 0x8000;
internal const int DBT_DEVICEREMOVECOMPLETE = 0x8004;
internal const int DBT_DEVTYP_DEVICEINTERFACE = 5;
internal const int DBT_DEVTYP_HANDLE = 6;
internal const int DEVICE_NOTIFY_ALL_INTERFACE_CLASSES = 4;
internal const int DEVICE_NOTIFY_SERVICE_HANDLE = 1;
internal const int DEVICE_NOTIFY_WINDOW_HANDLE = 0;
internal const int WM_DEVICECHANGE = 0x219;
internal const short DIGCF_PRESENT = 0x2;
internal const short DIGCF_DEVICEINTERFACE = 0x10;
internal const int DIGCF_ALLCLASSES = 0x4;
internal const int MAX_DEV_LEN = 1000;
internal const int SPDRP_ADDRESS = 0x1c;
internal const int SPDRP_BUSNUMBER = 0x15;
internal const int SPDRP_BUSTYPEGUID = 0x13;
internal const int SPDRP_CAPABILITIES = 0xf;
internal const int SPDRP_CHARACTERISTICS = 0x1b;
internal const int SPDRP_CLASS = 7;
internal const int SPDRP_CLASSGUID = 8;
internal const int SPDRP_COMPATIBLEIDS = 2;
internal const int SPDRP_CONFIGFLAGS = 0xa;
internal const int SPDRP_DEVICE_POWER_DATA = 0x1e;
internal const int SPDRP_DEVICEDESC = 0;
internal const int SPDRP_DEVTYPE = 0x19;
internal const int SPDRP_DRIVER = 9;
internal const int SPDRP_ENUMERATOR_NAME = 0x16;
internal const int SPDRP_EXCLUSIVE = 0x1a;
internal const int SPDRP_FRIENDLYNAME = 0xc;
internal const int SPDRP_HARDWAREID = 1;
internal const int SPDRP_LEGACYBUSTYPE = 0x14;
internal const int SPDRP_LOCATION_INFORMATION = 0xd;
internal const int SPDRP_LOWERFILTERS = 0x12;
internal const int SPDRP_MFG = 0xb;
internal const int SPDRP_PHYSICAL_DEVICE_OBJECT_NAME = 0xe;
internal const int SPDRP_REMOVAL_POLICY = 0x1f;
internal const int SPDRP_REMOVAL_POLICY_HW_DEFAULT = 0x20;
internal const int SPDRP_REMOVAL_POLICY_OVERRIDE = 0x21;
internal const int SPDRP_SECURITY = 0x17;
internal const int SPDRP_SECURITY_SDS = 0x18;
internal const int SPDRP_SERVICE = 4;
internal const int SPDRP_UI_NUMBER = 0x10;
internal const int SPDRP_UI_NUMBER_DESC_FORMAT = 0x1d;
internal const int DIF_PROPERTYCHANGE = 0x12;
internal const int DICS_ENABLE = 1;
internal const int DICS_DISABLE = 2;
internal const int DICS_FLAG_GLOBAL = 1;
internal const int SPDRP_UPPERFILTERS = 0x11;
[StructLayout(LayoutKind.Sequential)]
internal class DEV_BROADCAST_DEVICEINTERFACE
{
internal int dbcc_size;
internal int dbcc_devicetype;
internal int dbcc_reserved;
internal Guid dbcc_classguid;
internal short dbcc_name;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal class DEV_BROADCAST_DEVICEINTERFACE_1
{
internal int dbcc_size;
internal int dbcc_devicetype;
internal int dbcc_reserved;
[MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 16)]
internal byte[] dbcc_classguid;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 255)]
internal char[] dbcc_name;
}
[StructLayout(LayoutKind.Sequential)]
internal class DEV_BROADCAST_HANDLE
{
internal int dbch_size;
internal int dbch_devicetype;
internal int dbch_reserved;
internal int dbch_handle;
internal int dbch_hdevnotify;
}
[StructLayout(LayoutKind.Sequential)]
internal class DEV_BROADCAST_HDR
{
internal int dbch_size;
internal int dbch_devicetype;
internal int dbch_reserved;
}
[StructLayout(LayoutKind.Sequential)]
internal struct SP_DEVICE_INTERFACE_DATA
{
internal int cbSize;
internal System.Guid InterfaceClassGuid;
internal int Flags;
internal IntPtr Reserved;
}
[StructLayout(LayoutKind.Sequential)]
internal struct SP_DEVINFO_DATA
{
internal int cbSize;
internal Guid ClassGuid;
internal int DevInst;
internal IntPtr Reserved;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
internal struct SP_DEVICE_INTERFACE_DETAIL_DATA
{
internal int Size;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
internal string DevicePath;
}
[StructLayout(LayoutKind.Sequential)]
internal struct DEVPROPKEY
{
public Guid fmtid;
public ulong pid;
}
internal static DEVPROPKEY DEVPKEY_Device_BusReportedDeviceDesc =
new DEVPROPKEY { fmtid = new Guid(0x540b947e, 0x8b40, 0x45bc, 0xa8, 0xa2, 0x6a, 0x0b, 0x89, 0x4c, 0xbd, 0xa2), pid = 4 };
[DllImport("setupapi.dll", EntryPoint = "SetupDiGetDeviceRegistryProperty")]
public static extern bool SetupDiGetDeviceRegistryProperty(IntPtr deviceInfoSet, ref SP_DEVINFO_DATA deviceInfoData, int propertyVal, ref int propertyRegDataType, byte[] propertyBuffer, int propertyBufferSize, ref int requiredSize);
[DllImport("setupapi.dll", EntryPoint = "SetupDiGetDevicePropertyW", SetLastError = true)]
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);
[DllImport("setupapi.dll")]
static internal extern bool SetupDiEnumDeviceInfo(IntPtr deviceInfoSet, int memberIndex, ref SP_DEVINFO_DATA deviceInfoData);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static internal extern IntPtr RegisterDeviceNotification(IntPtr hRecipient, IntPtr notificationFilter, Int32 flags);
[DllImport("setupapi.dll")]
internal static extern int SetupDiCreateDeviceInfoList(ref Guid classGuid, int hwndParent);
[DllImport("setupapi.dll")]
static internal extern int SetupDiDestroyDeviceInfoList(IntPtr deviceInfoSet);
[DllImport("setupapi.dll", CharSet = CharSet.Auto)]
static internal extern bool SetupDiSetClassInstallParams(IntPtr deviceInfoSet, ref SP_DEVINFO_DATA deviceInfoData, ref SP_PROPCHANGE_PARAMS classInstallParams, int classInstallParamsSize);
[DllImport("setupapi.dll", CharSet = CharSet.Auto)]
static internal extern bool SetupDiCallClassInstaller(int installFunction, IntPtr deviceInfoSet, ref SP_DEVINFO_DATA deviceInfoData);
[DllImport("setupapi.dll")]
static internal extern bool SetupDiEnumDeviceInterfaces(IntPtr deviceInfoSet, ref SP_DEVINFO_DATA deviceInfoData, ref Guid interfaceClassGuid, int memberIndex, ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData);
[DllImport("setupapi.dll", CharSet = CharSet.Auto)]
static internal extern IntPtr SetupDiGetClassDevs(ref System.Guid classGuid, string enumerator, int hwndParent, int flags);
[DllImport("setupapi.dll", CharSet = CharSet.Auto, EntryPoint = "SetupDiGetDeviceInterfaceDetail")]
static internal extern bool SetupDiGetDeviceInterfaceDetailBuffer(IntPtr deviceInfoSet, ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData, IntPtr deviceInterfaceDetailData, int deviceInterfaceDetailDataSize, ref int requiredSize, IntPtr deviceInfoData);
[DllImport("setupapi.dll", CharSet = CharSet.Auto)]
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);
[DllImport("user32.dll")]
static internal extern bool UnregisterDeviceNotification(IntPtr handle);
internal const short HIDP_INPUT = 0;
internal const short HIDP_OUTPUT = 1;
internal const short HIDP_FEATURE = 2;
[StructLayout(LayoutKind.Sequential)]
internal struct HIDD_ATTRIBUTES
{
internal int Size;
internal ushort VendorID;
internal ushort ProductID;
internal short VersionNumber;
}
[StructLayout(LayoutKind.Sequential)]
internal struct HIDP_CAPS
{
internal short Usage;
internal short UsagePage;
internal short InputReportByteLength;
internal short OutputReportByteLength;
internal short FeatureReportByteLength;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 17)]
internal short[] Reserved;
internal short NumberLinkCollectionNodes;
internal short NumberInputButtonCaps;
internal short NumberInputValueCaps;
internal short NumberInputDataIndices;
internal short NumberOutputButtonCaps;
internal short NumberOutputValueCaps;
internal short NumberOutputDataIndices;
internal short NumberFeatureButtonCaps;
internal short NumberFeatureValueCaps;
internal short NumberFeatureDataIndices;
}
[StructLayout(LayoutKind.Sequential)]
internal struct SP_CLASSINSTALL_HEADER
{
internal int cbSize;
internal int installFunction;
}
[StructLayout(LayoutKind.Sequential)]
internal struct SP_PROPCHANGE_PARAMS
{
internal SP_CLASSINSTALL_HEADER classInstallHeader;
internal int stateChange;
internal int scope;
internal int hwProfile;
}
[StructLayout(LayoutKind.Sequential)]
internal struct HIDP_VALUE_CAPS
{
internal short UsagePage;
internal byte ReportID;
internal int IsAlias;
internal short BitField;
internal short LinkCollection;
internal short LinkUsage;
internal short LinkUsagePage;
internal int IsRange;
internal int IsStringRange;
internal int IsDesignatorRange;
internal int IsAbsolute;
internal int HasNull;
internal byte Reserved;
internal short BitSize;
internal short ReportCount;
internal short Reserved2;
internal short Reserved3;
internal short Reserved4;
internal short Reserved5;
internal short Reserved6;
internal int LogicalMin;
internal int LogicalMax;
internal int PhysicalMin;
internal int PhysicalMax;
internal short UsageMin;
internal short UsageMax;
internal short StringMin;
internal short StringMax;
internal short DesignatorMin;
internal short DesignatorMax;
internal short DataIndexMin;
internal short DataIndexMax;
}
[DllImport("hid.dll")]
static internal extern bool HidD_FlushQueue(IntPtr hidDeviceObject);
[DllImport("hid.dll")]
static internal extern bool HidD_GetAttributes(IntPtr hidDeviceObject, ref HIDD_ATTRIBUTES attributes);
[DllImport("hid.dll")]
static internal extern bool HidD_GetFeature(IntPtr hidDeviceObject, byte[] lpReportBuffer, int reportBufferLength);
[DllImport("hid.dll")]
static internal extern bool HidD_GetInputReport(IntPtr hidDeviceObject, ref byte lpReportBuffer, int reportBufferLength);
[DllImport("hid.dll")]
static internal extern void HidD_GetHidGuid(ref Guid hidGuid);
[DllImport("hid.dll")]
static internal extern bool HidD_GetNumInputBuffers(IntPtr hidDeviceObject, ref int numberBuffers);
[DllImport("hid.dll")]
static internal extern bool HidD_GetPreparsedData(IntPtr hidDeviceObject, ref IntPtr preparsedData);
[DllImport("hid.dll")]
static internal extern bool HidD_FreePreparsedData(IntPtr preparsedData);
[DllImport("hid.dll")]
static internal extern bool HidD_SetFeature(IntPtr hidDeviceObject, byte[] lpReportBuffer, int reportBufferLength);
[DllImport("hid.dll")]
static internal extern bool HidD_SetNumInputBuffers(IntPtr hidDeviceObject, int numberBuffers);
[DllImport("hid.dll")]
static internal extern bool HidD_SetOutputReport(IntPtr hidDeviceObject, byte[] lpReportBuffer, int reportBufferLength);
[DllImport("hid.dll")]
static internal extern int HidP_GetCaps(IntPtr preparsedData, ref HIDP_CAPS capabilities);
[DllImport("hid.dll")]
static internal extern int HidP_GetValueCaps(short reportType, ref byte valueCaps, ref short valueCapsLength, IntPtr preparsedData);
[DllImport("hid.dll", CharSet = CharSet.Unicode)]
internal static extern bool HidD_GetProductString(IntPtr hidDeviceObject, ref byte lpReportBuffer, int ReportBufferLength);
[DllImport("hid.dll", CharSet = CharSet.Unicode)]
internal static extern bool HidD_GetManufacturerString(IntPtr hidDeviceObject, ref byte lpReportBuffer, int ReportBufferLength);
[DllImport("hid.dll", CharSet = CharSet.Unicode)]
internal static extern bool HidD_GetSerialNumberString(IntPtr hidDeviceObject, ref byte lpReportBuffer, int reportBufferLength);
}
}

View File

@ -0,0 +1,164 @@
using MiController.Properties;
using System;
using System.ComponentModel;
using System.Drawing;
using System.Globalization;
using System.Windows.Forms;
namespace MiController
{
public class MiApplicationContext : ApplicationContext
{
private readonly NotifyIcon _trayIcon;
private BackgroundWorker _backgroundWorker;
private ContextMenuStrip _contextMenuStrip;
private ToolStripMenuItem _connectToolStripMenuItem;
private ToolStripMenuItem _disconnectToolStripMenuItem;
private ToolStripMenuItem _statusToolStripMenuItem;
public MiApplicationContext()
{
InitBackgroundWorker();
InitContextMenu();
// Initialize Tray Icon
_trayIcon = new NotifyIcon
{
Icon = Resources.MiLogoGrey,
ContextMenuStrip = _contextMenuStrip,
Visible = true
};
}
private void exitToolStripMenuItem_Click(object sender, EventArgs e)
{
_backgroundWorker.CancelAsync();
_trayIcon.Visible = false;
Application.Exit();
}
private void connectToolStripMenuItem_Click(object sender, EventArgs e)
{
StartStopProcess(true);
_backgroundWorker.RunWorkerAsync();
}
private void disconnectToolStripMenuItem_Click(object sender, EventArgs e)
{
_backgroundWorker.CancelAsync();
}
private void InitBackgroundWorker()
{
_backgroundWorker = new BackgroundWorker {WorkerReportsProgress = true, WorkerSupportsCancellation = true};
_backgroundWorker.DoWork += backgroundWorker_DoWork;
_backgroundWorker.ProgressChanged += backgroundWorker_ProgressChanged;
_backgroundWorker.RunWorkerCompleted += backgroundWorker_RunWorkerCompleted;
}
private void StartStopProcess(bool start)
{
_connectToolStripMenuItem.Enabled = !start;
_disconnectToolStripMenuItem.Enabled = start;
_trayIcon.Icon = start ? Resources.MiLogo : Resources.MiLogoGrey;
}
private void InitContextMenu()
{
_connectToolStripMenuItem = new ToolStripMenuItem
{
Name = "connectToolStripMenuItem",
Size = new Size(180, 22),
Text = "Connect",
Image = Resources.ConnectPlugged,
ImageTransparentColor = Color.Magenta
};
_connectToolStripMenuItem.Click += connectToolStripMenuItem_Click;
_disconnectToolStripMenuItem = new ToolStripMenuItem
{
Name = "disconnectToolStripMenuItem",
Size = new Size(180, 22),
Text = "Disconnect",
Image = Resources.ConnectUnplugged,
ImageTransparentColor = Color.Magenta,
Enabled = false
};
_disconnectToolStripMenuItem.Click += disconnectToolStripMenuItem_Click;
var toolStripSeparator1 = new ToolStripSeparator { Name = "toolStripSeparator1", Size = new Size(322, 6) };
_statusToolStripMenuItem = new ToolStripMenuItem
{
Name = "statusToolStripMenuItem",
Size = new Size(180, 22),
Text = "ready",
Enabled = false
};
var toolStripSeparator2 = new ToolStripSeparator { Name = "toolStripSeparator2", Size = new Size(322, 6) };
var exitToolStripMenuItem = new ToolStripMenuItem
{
Name = "exitToolStripMenuItem",
Size = new Size(180, 22),
Text = "E&xit",
Image = Resources.Close,
ImageTransparentColor = Color.Magenta
};
exitToolStripMenuItem.Click += exitToolStripMenuItem_Click;
_contextMenuStrip = new ContextMenuStrip { Renderer = new NoHighlightRenderer() };
_contextMenuStrip.Items.Add(_connectToolStripMenuItem);
_contextMenuStrip.Items.Add(_disconnectToolStripMenuItem);
_contextMenuStrip.Items.Add(toolStripSeparator1);
_contextMenuStrip.Items.Add(_statusToolStripMenuItem);
_contextMenuStrip.Items.Add(toolStripSeparator2);
_contextMenuStrip.Items.Add(exitToolStripMenuItem);
}
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
var bw = sender as BackgroundWorker;
ControllerHelper.Run(bw);
if (bw != null)
{
if (bw.CancellationPending)
{
e.Cancel = true;
}
}
}
private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled)
{
_statusToolStripMenuItem.Text = "disconnected";
}
else if (e.Error != null)
{
string msg = String.Format(CultureInfo.InvariantCulture, "An error occurred: {0}", e.Error.Message);
_statusToolStripMenuItem.Text = msg;
}
StartStopProcess(false);
}
private void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
_statusToolStripMenuItem.Text = (string)e.UserState;
}
}
internal class NoHighlightRenderer : ToolStripProfessionalRenderer
{
protected override void OnRenderMenuItemBackground(ToolStripItemRenderEventArgs e)
{
if (e.Item.Enabled)
{
base.OnRenderMenuItemBackground(e);
}
}
}
}

View File

@ -0,0 +1,176 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{AA86CCD2-39BC-4CEC-8E9C-29E3AB9E1C24}</ProjectGuid>
<OutputType>WinExe</OutputType>
<RootNamespace>MiController</RootNamespace>
<AssemblyName>MiController</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Deterministic>true</Deterministic>
<PublishUrl>publish\</PublishUrl>
<Install>true</Install>
<InstallFrom>Disk</InstallFrom>
<UpdateEnabled>false</UpdateEnabled>
<UpdateMode>Foreground</UpdateMode>
<UpdateInterval>7</UpdateInterval>
<UpdateIntervalUnits>Days</UpdateIntervalUnits>
<UpdatePeriodically>false</UpdatePeriodically>
<UpdateRequired>false</UpdateRequired>
<MapFileExtensions>true</MapFileExtensions>
<ApplicationRevision>0</ApplicationRevision>
<ApplicationVersion>1.0.0.%2a</ApplicationVersion>
<IsWebBootstrapper>false</IsWebBootstrapper>
<UseApplicationTrust>false</UseApplicationTrust>
<BootstrapperEnabled>true</BootstrapperEnabled>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>x64</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>x64</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup>
<ApplicationIcon>MiLogo.ico</ApplicationIcon>
</PropertyGroup>
<PropertyGroup>
<SignAssembly>true</SignAssembly>
</PropertyGroup>
<PropertyGroup>
<AssemblyOriginatorKeyFile>MiControllerKey.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>
<PropertyGroup>
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Configuration.Install" />
<Reference Include="System.Core" />
<Reference Include="System.Management" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Deployment" />
<Reference Include="System.Drawing" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="DriverUtils\Devcon.cs" />
<Compile Include="DriverUtils\Difx.cs" />
<Compile Include="HidLibrary\Extensions.cs" />
<Compile Include="HidLibrary\HidAsyncState.cs" />
<Compile Include="HidLibrary\HidDevice.cs" />
<Compile Include="HidLibrary\HidDeviceAttributes.cs" />
<Compile Include="HidLibrary\HidDeviceCapabilities.cs" />
<Compile Include="HidLibrary\HidDeviceData.cs" />
<Compile Include="HidLibrary\HidDeviceEventMonitor.cs" />
<Compile Include="HidLibrary\HidDevices.cs" />
<Compile Include="HidLibrary\HidFastReadDevice.cs" />
<Compile Include="HidLibrary\HidReport.cs" />
<Compile Include="HidLibrary\IHidDevice.cs" />
<Compile Include="HidLibrary\IHidEnumerator.cs" />
<Compile Include="HidLibrary\NativeMethods.cs" />
<Compile Include="MiApplicationContext .cs" />
<Compile Include="ControllerHelper.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ScpDriverInterface\ScpBus.cs" />
<Compile Include="ScpDriverInterface\X360Controller.cs" />
<Compile Include="DriverSetup.cs" />
<Compile Include="XiaomiGamepad.cs" />
<Content Include="Setup.iss" />
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
<SubType>Designer</SubType>
</EmbeddedResource>
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
<DesignTime>True</DesignTime>
</Compile>
<None Include="app.manifest" />
<None Include="MiControllerKey.snk" />
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
<Compile Include="Properties\Settings.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
<DesignTimeSharedInput>True</DesignTimeSharedInput>
</Compile>
<Content Include="MiLogoGrey.ico" />
<Content Include="ScpDriver\amd64\DIFxAPI.dll">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="ScpDriver\amd64\ScpVBus.sys">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="ScpDriver\ScpVBus.cat">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="ScpDriver\x86\DIFxAPI.dll">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="ScpDriver\x86\ScpVBus.sys">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<None Include="Resources\milogo.png" />
</ItemGroup>
<ItemGroup>
<None Include="MiLogo.ico" />
</ItemGroup>
<ItemGroup>
<None Include="Resources\close.png" />
</ItemGroup>
<ItemGroup>
<None Include="Resources\connectPlugged.png" />
</ItemGroup>
<ItemGroup>
<None Include="Resources\connectUnplugged.png" />
</ItemGroup>
<ItemGroup>
<BootstrapperPackage Include=".NETFramework,Version=v4.7.2">
<Visible>False</Visible>
<ProductName>Microsoft .NET Framework 4.7.2 %28x86 and x64%29</ProductName>
<Install>true</Install>
</BootstrapperPackage>
<BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
<Visible>False</Visible>
<ProductName>.NET Framework 3.5 SP1</ProductName>
<Install>false</Install>
</BootstrapperPackage>
</ItemGroup>
<ItemGroup />
<ItemGroup>
<Content Include="ScpDriver\ScpVBus.inf">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PostBuildEvent>"C:\Program Files (x86)\Inno Setup 6\ISCC.exe" "$(ProjectDir)Setup.iss"</PostBuildEvent>
</PropertyGroup>
</Project>

Binary file not shown.

BIN
MiController/MiLogo.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

BIN
MiController/MiLogoGrey.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

35
MiController/Program.cs Normal file
View File

@ -0,0 +1,35 @@
using System;
using System.Windows.Forms;
namespace MiController
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string[] args)
{
if (args.Length > 0)
{
string cmd = args[0].ToLowerInvariant();
if (cmd == "/install")
{
DriverSetup.Uninstall();
DriverSetup.Install();
}
else if (cmd == "/uninstall")
{
DriverSetup.Uninstall();
return;
}
}
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MiApplicationContext());
}
}
}

View File

@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("MiController")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("TomaSoft")]
[assembly: AssemblyProduct("MiController")]
[assembly: AssemblyCopyright("Copyright © 2020")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("aa86ccd2-39bc-4cec-8e9c-29e3ab9e1c24")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@ -0,0 +1,113 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace MiController.Properties {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("MiController.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap Close {
get {
object obj = ResourceManager.GetObject("Close", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap ConnectPlugged {
get {
object obj = ResourceManager.GetObject("ConnectPlugged", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap ConnectUnplugged {
get {
object obj = ResourceManager.GetObject("ConnectUnplugged", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Icon similar to (Icon).
/// </summary>
internal static System.Drawing.Icon MiLogo {
get {
object obj = ResourceManager.GetObject("MiLogo", resourceCulture);
return ((System.Drawing.Icon)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Icon similar to (Icon).
/// </summary>
internal static System.Drawing.Icon MiLogoGrey {
get {
object obj = ResourceManager.GetObject("MiLogoGrey", resourceCulture);
return ((System.Drawing.Icon)(obj));
}
}
}
}

View File

@ -0,0 +1,136 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="Close" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\close.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="ConnectPlugged" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\connectPlugged.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="ConnectUnplugged" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\connectUnplugged.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="MiLogo" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\MiLogo.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="MiLogoGrey" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\MiLogoGrey.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
</root>

View File

@ -0,0 +1,30 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace MiController.Properties
{
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
{
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
public static Settings Default
{
get
{
return defaultInstance;
}
}
}
}

View File

@ -0,0 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
<Profiles>
<Profile Name="(Default)" />
</Profiles>
<Settings />
</SettingsFile>

Binary file not shown.

After

Width:  |  Height:  |  Size: 297 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 542 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 598 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

View File

@ -0,0 +1,71 @@
; =============================================================================
; Copyright (c) Scarlet.Crush Productions. All rights reserved.
;
; ScpVBus.inf
; =============================================================================
[Version]
Provider = %SCProd%
Signature = "$Windows NT$"
Class = System
ClassGuid = {4D36E97D-E325-11CE-BFC1-08002BE10318}
DriverVer = 05/05/2013, 1.0.0.103
CatalogFile = ScpVBus.cat
; =============================================================================
[DestinationDirs]
DefaultDestDir = 12
[Manufacturer]
%SCProd% = SCProductions, NTx86, NTamd64
[SourceDisksNames]
1 = %MediaDescription%
[SCProductions.NTx86]
%ScpVBus.DeviceDesc% = ScpVBus_Install, root\ScpVBus
[SourceDisksFiles.x86]
ScpVBus.sys = 1, .\x86,
[SCProductions.NTamd64]
%ScpVBus.DeviceDesc% = ScpVBus_Install, root\ScpVBus
[SourceDisksFiles.amd64]
ScpVBus.sys = 1, .\amd64,
; =============================================================================
[ScpVBus_Install.NT]
CopyFiles = ScpVBus_Install.NT.Copy
[ScpVBus_Install.NT.hw]
AddReg = ScpVBus_Device_AddReg
[ScpVBus_Install.NT.Copy]
ScpVBus.sys
[ScpVBus_Device_AddReg]
[ScpVBus_Install.NT.Services]
AddService = ScpVBus, %SPSVCINST_ASSOCSERVICE%, ScpVBus_Service_Inst
[ScpVBus_Service_Inst]
DisplayName = %ScpVBus.SVCDESC%
ServiceType = 1 ; SERVICE_KERNEL_DRIVER
StartType = 3 ; SERVICE_DEMAND_START
ErrorControl = 1 ; SERVICE_ERROR_NORMAL
ServiceBinary = %12%\ScpVBus.sys
LoadOrderGroup = Extended Base
; =============================================================================
[Strings]
SCProd = "Scarlet.Crush Productions"
MediaDescription = "Scp Virtual Bus Installation Media"
ScpVBus.DeviceDesc = "Scp Virtual Bus Driver"
ScpVBus.SVCDESC = "Scp Virtual Bus Driver"
SPSVCINST_ASSOCSERVICE = 0x00000002
; =============================================================================

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,312 @@
/*
* ScpDriverInterface - by Mogzol (and of course Scarlet.Crush) - Jan, 2016
*
* This is a simple little DLL which allows you to use Scarlet.Crush's SCP Virtual
* Bus Driver to emulate XBox 360 Controllers.
*
* Most of the code here has been ripped out of his ScpControl source code, mostly
* from the ScpDevice and BusDevice classes, so obviously credit and major props to
* Scarlet.Crush, without him this wouldn't be possible. You can download his
* original source code from here:
* http://forums.pcsx2.net/Thread-XInput-Wrapper-for-DS3-and-Play-com-USB-Dual-DS2-Controller
*
* Note that for this to work the SCP Virtual Bus Driver must be installed. (Duh)
*/
using System;
using System.Globalization;
using System.IO;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
[assembly: CLSCompliant(true)]
namespace ScpDriverInterface
{
/// <summary>
/// Emulates XBox 360 controllers via Scarlet.Crush's SCP Virtual Bus Driver.
/// </summary>
public class ScpBus : IDisposable
{
private const string SCP_BUS_CLASS_GUID = "{F679F562-3164-42CE-A4DB-E7DDBE723909}";
private const int ReportSize = 28;
private readonly SafeFileHandle _deviceHandle;
/// <summary>
/// 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.
/// </summary>
public ScpBus() : this(0) { }
/// <summary>
/// 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.
/// </summary>
/// <param name="instance">Specifies which SCP Virtual Bus device to use. This is 0-based.</param>
public ScpBus(int instance)
{
string devicePath = "";
if (Find(new Guid(SCP_BUS_CLASS_GUID), ref devicePath, instance))
{
_deviceHandle = GetHandle(devicePath);
}
else
{
throw new IOException("SCP Virtual Bus Device not found");
}
}
/// <summary>
/// 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.
/// </summary>
/// <param name="devicePath">The path to the SCP Virtual Bus device that you want to use.</param>
public ScpBus(string devicePath)
{
_deviceHandle = GetHandle(devicePath);
}
/// <summary>
/// Closes the handle to the SCP Virtual Bus device. Call this when you are done with your instance of ScpBus.
///
/// (This method does the same thing as the Dispose() method. Use one or the other.)
/// </summary>
public void Close()
{
Dispose();
}
/// <summary>
/// Closes the handle to the SCP Virtual Bus device. Call this when you are done with your instance of ScpBus.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (_deviceHandle != null && !_deviceHandle.IsInvalid)
{
_deviceHandle.Dispose();
}
}
/// <summary>
/// Plugs in an emulated XBox 360 controller.
/// </summary>
/// <param name="controllerNumber">Used to identify the controller. Give each controller you plug in a different number. Number must be non-zero.</param>
/// <returns>True if the operation was successful, false otherwise.</returns>
public bool PlugIn(int controllerNumber)
{
if (_deviceHandle.IsInvalid)
throw new ObjectDisposedException("SCP Virtual Bus device handle is closed");
int transfered = 0;
byte[] buffer = new byte[16];
buffer[0] = 0x10;
buffer[1] = 0x00;
buffer[2] = 0x00;
buffer[3] = 0x00;
buffer[4] = (byte)((controllerNumber) & 0xFF);
buffer[5] = (byte)((controllerNumber >> 8) & 0xFF);
buffer[6] = (byte)((controllerNumber >> 16) & 0xFF);
buffer[7] = (byte)((controllerNumber >> 24) & 0xFF);
return NativeMethods.DeviceIoControl(_deviceHandle, 0x2A4000, buffer, buffer.Length, null, 0, ref transfered, IntPtr.Zero);
}
/// <summary>
/// Unplugs an emulated XBox 360 controller.
/// </summary>
/// <param name="controllerNumber">The controller you want to unplug.</param>
/// <returns>True if the operation was successful, false otherwise.</returns>
public bool Unplug(int controllerNumber)
{
if (_deviceHandle.IsInvalid)
throw new ObjectDisposedException("SCP Virtual Bus device handle is closed");
int transfered = 0;
byte[] buffer = new Byte[16];
buffer[0] = 0x10;
buffer[1] = 0x00;
buffer[2] = 0x00;
buffer[3] = 0x00;
buffer[4] = (byte)((controllerNumber) & 0xFF);
buffer[5] = (byte)((controllerNumber >> 8) & 0xFF);
buffer[6] = (byte)((controllerNumber >> 16) & 0xFF);
buffer[7] = (byte)((controllerNumber >> 24) & 0xFF);
return NativeMethods.DeviceIoControl(_deviceHandle, 0x2A4004, buffer, buffer.Length, null, 0, ref transfered, IntPtr.Zero);
}
/// <summary>
/// Unplugs all emulated XBox 360 controllers.
/// </summary>
/// <returns>True if the operation was successful, false otherwise.</returns>
public bool UnplugAll()
{
if (_deviceHandle.IsInvalid)
throw new ObjectDisposedException("SCP Virtual Bus device handle is closed");
int transfered = 0;
byte[] buffer = new byte[16];
buffer[0] = 0x10;
buffer[1] = 0x00;
buffer[2] = 0x00;
buffer[3] = 0x00;
return NativeMethods.DeviceIoControl(_deviceHandle, 0x2A4004, buffer, buffer.Length, null, 0, ref transfered, IntPtr.Zero);
}
/// <summary>
/// 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().
/// </summary>
/// <param name="controllerNumber">The controller to report.</param>
/// <param name="controllerReport">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.</param>
/// <returns>True if the operation was successful, false otherwise.</returns>
public bool Report(int controllerNumber, byte[] controllerReport)
{
return Report(controllerNumber, controllerReport, null);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="controllerNumber">The controller to report.</param>
/// <param name="controllerReport">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.</param>
/// <param name="outputBuffer">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.</param>
/// <returns>True if the operation was successful, false otherwise.</returns>
public bool Report(int controllerNumber, byte[] controllerReport, byte[] outputBuffer)
{
if (_deviceHandle.IsInvalid)
throw new ObjectDisposedException("SCP Virtual Bus device handle is closed");
byte[] head = new byte[8];
head[0] = 0x1C;
head[4] = (byte)((controllerNumber) & 0xFF);
head[5] = (byte)((controllerNumber >> 8) & 0xFF);
head[6] = (byte)((controllerNumber >> 16) & 0xFF);
head[7] = (byte)((controllerNumber >> 24) & 0xFF);
byte[] fullReport = new byte[28];
Buffer.BlockCopy(head, 0, fullReport, 0, head.Length);
Buffer.BlockCopy(controllerReport, 0, fullReport, head.Length, controllerReport.Length);
int transferred = 0;
return NativeMethods.DeviceIoControl(_deviceHandle, 0x2A400C, fullReport, fullReport.Length, outputBuffer, outputBuffer?.Length ?? 0, ref transferred, IntPtr.Zero) && transferred > 0;
}
private static bool Find(Guid target, ref string path, int instance = 0)
{
IntPtr detailDataBuffer = IntPtr.Zero;
IntPtr deviceInfoSet = IntPtr.Zero;
try
{
NativeMethods.SP_DEVICE_INTERFACE_DATA DeviceInterfaceData = new NativeMethods.SP_DEVICE_INTERFACE_DATA(), da = new NativeMethods.SP_DEVICE_INTERFACE_DATA();
int bufferSize = 0, memberIndex = 0;
deviceInfoSet = NativeMethods.SetupDiGetClassDevs(ref target, IntPtr.Zero, IntPtr.Zero, NativeMethods.DIGCF_PRESENT | NativeMethods.DIGCF_DEVICEINTERFACE);
DeviceInterfaceData.cbSize = da.cbSize = Marshal.SizeOf(DeviceInterfaceData);
while (NativeMethods.SetupDiEnumDeviceInterfaces(deviceInfoSet, IntPtr.Zero, ref target, memberIndex, ref DeviceInterfaceData))
{
NativeMethods.SetupDiGetDeviceInterfaceDetail(deviceInfoSet, ref DeviceInterfaceData, IntPtr.Zero, 0, ref bufferSize, ref da);
detailDataBuffer = Marshal.AllocHGlobal(bufferSize);
Marshal.WriteInt32(detailDataBuffer, (IntPtr.Size == 4) ? (4 + Marshal.SystemDefaultCharSize) : 8);
if (NativeMethods.SetupDiGetDeviceInterfaceDetail(deviceInfoSet, ref DeviceInterfaceData, detailDataBuffer, bufferSize, ref bufferSize, ref da))
{
IntPtr pDevicePathName = detailDataBuffer + 4;
path = Marshal.PtrToStringAuto(pDevicePathName).ToUpper(CultureInfo.InvariantCulture);
Marshal.FreeHGlobal(detailDataBuffer);
if (memberIndex == instance) return true;
}
else Marshal.FreeHGlobal(detailDataBuffer);
memberIndex++;
}
}
finally
{
if (deviceInfoSet != IntPtr.Zero)
{
NativeMethods.SetupDiDestroyDeviceInfoList(deviceInfoSet);
}
}
return false;
}
private static SafeFileHandle GetHandle(string devicePath)
{
devicePath = devicePath.ToUpper(CultureInfo.InvariantCulture);
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);
if (handle == null || handle.IsInvalid)
{
throw new IOException("Unable to get SCP Virtual Bus Device handle");
}
return handle;
}
}
internal static class NativeMethods
{
[StructLayout(LayoutKind.Sequential)]
internal struct SP_DEVICE_INTERFACE_DATA
{
internal int cbSize;
internal Guid InterfaceClassGuid;
internal int Flags;
internal IntPtr Reserved;
}
internal const uint FILE_ATTRIBUTE_NORMAL = 0x80;
internal const uint FILE_FLAG_OVERLAPPED = 0x40000000;
internal const uint FILE_SHARE_READ = 1;
internal const uint FILE_SHARE_WRITE = 2;
internal const uint GENERIC_READ = 0x80000000;
internal const uint GENERIC_WRITE = 0x40000000;
internal const uint OPEN_EXISTING = 3;
internal const int DIGCF_PRESENT = 0x0002;
internal const int DIGCF_DEVICEINTERFACE = 0x0010;
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern SafeFileHandle CreateFile(string lpFileName, uint dwDesiredAccess, uint dwShareMode, IntPtr lpSecurityAttributes, uint dwCreationDisposition, uint dwFlagsAndAttributes, UIntPtr hTemplateFile);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool DeviceIoControl(SafeFileHandle hDevice, int dwIoControlCode, byte[] lpInBuffer, int nInBufferSize, byte[] lpOutBuffer, int nOutBufferSize, ref int lpBytesReturned, IntPtr lpOverlapped);
[DllImport("setupapi.dll", SetLastError = true)]
internal static extern int SetupDiDestroyDeviceInfoList(IntPtr deviceInfoSet);
[DllImport("setupapi.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool SetupDiEnumDeviceInterfaces(IntPtr hDevInfo, IntPtr devInfo, ref Guid interfaceClassGuid, int memberIndex, ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData);
[DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Auto)]
internal static extern IntPtr SetupDiGetClassDevs(ref Guid classGuid, IntPtr enumerator, IntPtr hwndParent, int flags);
[DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Auto)]
[return: MarshalAs(UnmanagedType.Bool)]
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);
}
}

View File

@ -0,0 +1,157 @@
using System;
namespace ScpDriverInterface
{
/// <summary>
/// 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.
/// </summary>
public class X360Controller
{
/// <summary>
/// Generates a new X360Controller object with the default initial state (no buttons pressed, all analog inputs 0).
/// </summary>
public X360Controller()
{
Buttons = X360Buttons.None;
LeftTrigger = 0;
RightTrigger = 0;
LeftStickX = 0;
LeftStickY = 0;
RightStickX = 0;
RightStickY = 0;
}
/// <summary>
/// Generates a new X360Controller object. Optionally, you can specify the initial state of the controller.
/// </summary>
/// <param name="buttons">The pressed buttons. Use like flags (i.e. (X360Buttons.A | X360Buttons.X) would be mean both A and X are pressed).</param>
/// <param name="leftTrigger">Left trigger analog input. 0 to 255.</param>
/// <param name="rightTrigger">Right trigger analog input. 0 to 255.</param>
/// <param name="leftStickX">Left stick X-axis. -32,768 to 32,767.</param>
/// <param name="leftStickY">Left stick Y-axis. -32,768 to 32,767.</param>
/// <param name="rightStickX">Right stick X-axis. -32,768 to 32,767.</param>
/// <param name="rightStickY">Right stick Y-axis. -32,768 to 32,767.</param>
public X360Controller(X360Buttons buttons, byte leftTrigger, byte rightTrigger, short leftStickX, short leftStickY, short rightStickX, short rightStickY)
{
Buttons = buttons;
LeftTrigger = leftTrigger;
RightTrigger = rightTrigger;
LeftStickX = leftStickX;
LeftStickY = leftStickY;
RightStickX = rightStickX;
RightStickY = rightStickY;
}
/// <summary>
/// Generates a new X360Controller object with the same values as the specified X360Controller object.
/// </summary>
/// <param name="controller">An X360Controller object to copy values from.</param>
public X360Controller(X360Controller controller)
{
Buttons = controller.Buttons;
LeftTrigger = controller.LeftTrigger;
RightTrigger = controller.RightTrigger;
LeftStickX = controller.LeftStickX;
LeftStickY = controller.LeftStickY;
RightStickX = controller.RightStickX;
RightStickY = controller.RightStickY;
}
/// <summary>
/// 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).
/// </summary>
public X360Buttons Buttons { get; set; }
/// <summary>
/// The controller's left trigger analog input. Value can range from 0 to 255.
/// </summary>
public byte LeftTrigger { get; set; }
/// <summary>
/// The controller's right trigger analog input. Value can range from 0 to 255.
/// </summary>
public byte RightTrigger { get; set; }
/// <summary>
/// The controller's left stick X-axis. Value can range from -32,768 to 32,767.
/// </summary>
public short LeftStickX { get; set; }
/// <summary>
/// The controller's left stick Y-axis. Value can range from -32,768 to 32,767.
/// </summary>
public short LeftStickY { get; set; }
/// <summary>
/// The controller's right stick X-axis. Value can range from -32,768 to 32,767.
/// </summary>
public short RightStickX { get; set; }
/// <summary>
/// The controller's right stick Y-axis. Value can range from -32,768 to 32,767.
/// </summary>
public short RightStickY { get; set; }
/// <summary>
/// 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.
/// </summary>
/// <returns>A 20-byte XBox 360 controller report.</returns>
public byte[] GetReport()
{
byte[] bytes = new byte[20];
bytes[0] = 0x00; // Message type (input report)
bytes[1] = 0x14; // Message size (20 bytes)
bytes[2] = (byte)((ushort)Buttons & 0xFF); // Buttons low
bytes[3] = (byte)((ushort)Buttons >> 8 & 0xFF); // Buttons high
bytes[4] = LeftTrigger; // Left trigger
bytes[5] = RightTrigger; // Right trigger
bytes[6] = (byte)(LeftStickX & 0xFF); // Left stick X-axis low
bytes[7] = (byte)(LeftStickX >> 8 & 0xFF); // Left stick X-axis high
bytes[8] = (byte)(LeftStickY & 0xFF); // Left stick Y-axis low
bytes[9] = (byte)(LeftStickY >> 8 & 0xFF); // Left stick Y-axis high
bytes[10] = (byte)(RightStickX & 0xFF); // Right stick X-axis low
bytes[11] = (byte)(RightStickX >> 8 & 0xFF); // Right stick X-axis high
bytes[12] = (byte)(RightStickY & 0xFF); // Right stick Y-axis low
bytes[13] = (byte)(RightStickY >> 8 & 0xFF); // Right stick Y-axis high
// Remaining bytes are unused
return bytes;
}
}
/// <summary>
/// The buttons to be used with an X360Controller object.
/// </summary>
[Flags]
public enum X360Buttons
{
None = 0,
Up = 1 << 0,
Down = 1 << 1,
Left = 1 << 2,
Right = 1 << 3,
Start = 1 << 4,
Back = 1 << 5,
LeftStick = 1 << 6,
RightStick = 1 << 7,
LeftBumper = 1 << 8,
RightBumper = 1 << 9,
Logo = 1 << 10,
A = 1 << 12,
B = 1 << 13,
X = 1 << 14,
Y = 1 << 15,
}
}

53
MiController/Setup.iss Normal file
View File

@ -0,0 +1,53 @@
; Script generated by the Inno Setup Script Wizard.
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
#define MyAppName "Mi Controller"
#define MyAppVersion "1.0"
#define MyAppPublisher "TomaSoft"
#define MyAppExeName "MiController.exe"
[Setup]
; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications.
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
AppId={{D507AE01-7EA5-46C1-B776-F9F27D159E02}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
;AppVerName={#MyAppName} {#MyAppVersion}
AppPublisher={#MyAppPublisher}
DefaultDirName={autopf}\MiController
DisableProgramGroupPage=yes
; Uncomment the following line to run in non administrative install mode (install for current user only.)
;PrivilegesRequired=lowest
OutputDir=.\bin\Release
OutputBaseFilename=MiControllerSetup
SetupIconFile=.\MiLogo.ico
UninstallDisplayIcon="{app}\{#MyAppExeName}"
Compression=lzma
SolidCompression=yes
WizardStyle=modern
ArchitecturesInstallIn64BitMode=x64
[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"
[Tasks]
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
[Files]
Source: ".\bin\Release\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
Source: ".\bin\Release\ScpDriver\*"; DestDir: "{app}\ScpDriver"; Flags: ignoreversion recursesubdirs createallsubdirs
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
[Registry]
Root: HKLM; Subkey: "Software\Microsoft\Windows\CurrentVersion\Run"; ValueType: string; ValueName: "{#MyAppName}"; ValueData: "{app}\{#MyAppExeName}"
Root: HKLM; Subkey: "Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Run"; ValueType: string; ValueName: "{#MyAppName}"; ValueData: "{app}\{#MyAppExeName}"
[Icons]
Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
[Run]
Filename: "{app}\{#MyAppExeName}"; Parameters: "/install"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait skipifsilent
[UninstallRun]
Filename: "{app}\{#MyAppExeName}"; Parameters: "/uninstall";

View File

@ -0,0 +1,200 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using HidLibrary;
using ScpDriverInterface;
using System.Threading;
using System.Runtime.InteropServices;
namespace MiController
{
public class XiaomiGamepad
{
private byte[] Vibration = { 0x20, 0x00, 0x00 };
private Mutex rumble_mutex = new Mutex();
public XiaomiGamepad(HidDevice Device, ScpBus scpBus, int index)
{
Device.WriteFeatureData(Vibration);
Thread rThread = new Thread(() => rumble_thread(Device));
// rThread.Priority = ThreadPriority.BelowNormal;
rThread.Start();
Thread iThread = new Thread(() => input_thread(Device, scpBus, index));
iThread.Priority = ThreadPriority.Highest;
iThread.Start();
}
private void rumble_thread(HidDevice Device)
{
byte[] local_vibration = { 0x20, 0x00, 0x00 };
while (true)
{
rumble_mutex.WaitOne();
if (local_vibration[2] != Vibration[2] || Vibration[1] != local_vibration[1])
{
local_vibration[2] = Vibration[2];
local_vibration[1] = Vibration[1];
rumble_mutex.ReleaseMutex();
Device.WriteFeatureData(local_vibration);
//Console.WriteLine("Big Motor: {0}, Small Motor: {1}", Vibration[2], Vibration[1]);
}
else
{
rumble_mutex.ReleaseMutex();
}
Thread.Sleep(20);
}
}
private void input_thread(HidDevice Device, ScpBus scpBus, int index)
{
scpBus.PlugIn(index);
X360Controller controller = new X360Controller();
int timeout = 30;
long last_changed = 0;
long last_mi_button = 0;
while (true)
{
HidDeviceData data = Device.Read(timeout);
var currentState = data.Data;
bool changed = false;
if (data.Status == HidDeviceData.ReadStatus.Success && currentState.Length >= 21 && currentState[0] == 4)
{
//Console.WriteLine(Program.ByteArrayToHexString(currentState));
X360Buttons Buttons = X360Buttons.None;
if ((currentState[1] & 1) != 0) Buttons |= X360Buttons.A;
if ((currentState[1] & 2) != 0) Buttons |= X360Buttons.B;
if ((currentState[1] & 8) != 0) Buttons |= X360Buttons.X;
if ((currentState[1] & 16) != 0) Buttons |= X360Buttons.Y;
if ((currentState[1] & 64) != 0) Buttons |= X360Buttons.LeftBumper;
if ((currentState[1] & 128) != 0) Buttons |= X360Buttons.RightBumper;
if ((currentState[2] & 32) != 0) Buttons |= X360Buttons.LeftStick;
if ((currentState[2] & 64) != 0) Buttons |= X360Buttons.RightStick;
if (currentState[4] != 15)
{
if (currentState[4] == 0 || currentState[4] == 1 || currentState[4] == 7) Buttons |= X360Buttons.Up;
if (currentState[4] == 4 || currentState[4] == 3 || currentState[4] == 5) Buttons |= X360Buttons.Down;
if (currentState[4] == 6 || currentState[4] == 5 || currentState[4] == 7) Buttons |= X360Buttons.Left;
if (currentState[4] == 2 || currentState[4] == 1 || currentState[4] == 3) Buttons |= X360Buttons.Right;
}
if ((currentState[2] & 8) != 0) Buttons |= X360Buttons.Start;
if ((currentState[2] & 4) != 0) Buttons |= X360Buttons.Back;
if ((currentState[20] & 1) != 0)
{
last_mi_button = (DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond);
Buttons |= X360Buttons.Logo;
}
if (last_mi_button != 0) Buttons |= X360Buttons.Logo;
if (controller.Buttons != Buttons)
{
changed = true;
controller.Buttons = Buttons;
}
short LeftStickX = (short)((Math.Max(-127.0, currentState[5] - 128) / 127) * 32767);
if (LeftStickX == -32767)
LeftStickX = -32768;
if (LeftStickX != controller.LeftStickX)
{
changed = true;
controller.LeftStickX = LeftStickX;
}
short LeftStickY = (short)((Math.Max(-127.0, currentState[6] - 128) / 127) * -32767);
if (LeftStickY == -32767)
LeftStickY = -32768;
if (LeftStickY != controller.LeftStickY)
{
changed = true;
controller.LeftStickY = LeftStickY;
}
short RightStickX = (short)((Math.Max(-127.0, currentState[7] - 128) / 127) * 32767);
if (RightStickX == -32767)
RightStickX = -32768;
if (RightStickX != controller.RightStickX)
{
changed = true;
controller.RightStickX = RightStickX;
}
short RightStickY = (short)((Math.Max(-127.0, currentState[8] - 128) / 127) * -32767);
if (RightStickY == -32767)
RightStickY = -32768;
if (RightStickY != controller.RightStickY)
{
changed = true;
controller.RightStickY = RightStickY;
}
if (controller.LeftTrigger != currentState[11])
{
changed = true;
controller.LeftTrigger = currentState[11];
}
if (controller.RightTrigger != currentState[12])
{
changed = true;
controller.RightTrigger = currentState[12];
}
}
if (data.Status == HidDeviceData.ReadStatus.WaitTimedOut || (!changed && ((last_changed + timeout) < (DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond))))
{
changed = true;
}
if (changed)
{
//Console.WriteLine("changed");
//Console.WriteLine((DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond));
byte[] outputReport = new byte[8];
scpBus.Report(index, controller.GetReport(), outputReport);
if (outputReport[1] == 0x08)
{
byte bigMotor = outputReport[3];
byte smallMotor = outputReport[4];
rumble_mutex.WaitOne();
if (bigMotor != Vibration[2] || Vibration[1] != smallMotor)
{
Vibration[1] = smallMotor;
Vibration[2] = bigMotor;
}
rumble_mutex.ReleaseMutex();
}
if (last_mi_button != 0)
{
if ((last_mi_button + 100) < (DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond))
{
last_mi_button = 0;
controller.Buttons ^= X360Buttons.Logo;
}
}
last_changed = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;
}
}
}
}
}

76
MiController/app.manifest Normal file
View File

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<!-- UAC Manifest Options
If you want to change the Windows User Account Control level replace the
requestedExecutionLevel node with one of the following.
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
<requestedExecutionLevel level="highestAvailable" uiAccess="false" />
Specifying requestedExecutionLevel element will disable file and registry virtualization.
Remove this element if your application requires this virtualization for backwards
compatibility.
-->
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
</requestedPrivileges>
</security>
</trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- A list of the Windows versions that this application has been tested on
and is designed to work with. Uncomment the appropriate elements
and Windows will automatically select the most compatible environment. -->
<!-- Windows Vista -->
<!--<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />-->
<!-- Windows 7 -->
<!--<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />-->
<!-- Windows 8 -->
<!--<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />-->
<!-- Windows 8.1 -->
<!--<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />-->
<!-- Windows 10 -->
<!--<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />-->
</application>
</compatibility>
<!-- Indicates that the application is DPI-aware and will not be automatically scaled by Windows at higher
DPIs. Windows Presentation Foundation (WPF) applications are automatically DPI-aware and do not need
to opt in. Windows Forms applications targeting .NET Framework 4.6 that opt into this setting, should
also set the 'EnableWindowsFormsHighDpiAutoResizing' setting to 'true' in their app.config. -->
<!--
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
</windowsSettings>
</application>
-->
<!-- Enable themes for Windows common controls and dialogs (Windows XP and later) -->
<!--
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
processorArchitecture="*"
publicKeyToken="6595b64144ccf1df"
language="*"
/>
</dependentAssembly>
</dependency>
-->
</assembly>