Removed SCP dependency

This commit is contained in:
Tomas 2022-02-26 15:16:10 +00:00
parent 948115d9b3
commit 6bed360a74
39 changed files with 1570 additions and 1910 deletions

View File

@ -1,18 +1,24 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30503.244
# Visual Studio Version 17
VisualStudioVersion = 17.0.32112.339
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|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{AA86CCD2-39BC-4CEC-8E9C-29E3AB9E1C24}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AA86CCD2-39BC-4CEC-8E9C-29E3AB9E1C24}.Debug|Any CPU.Build.0 = Debug|Any CPU
{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|Any CPU.ActiveCfg = Release|Any CPU
{AA86CCD2-39BC-4CEC-8E9C-29E3AB9E1C24}.Release|Any CPU.Build.0 = Release|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

View File

@ -1,181 +0,0 @@
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

@ -1,58 +0,0 @@
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

@ -1,242 +0,0 @@
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

@ -1,192 +0,0 @@
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

@ -27,10 +27,6 @@ namespace HidLibrary
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);
@ -106,10 +102,6 @@ namespace HidLibrary
}
IsOpen = Handle.ToInt32() != NativeMethods.INVALID_HANDLE_VALUE;
if (!IsOpen)
{
throw new Exception("Error opening HID device.");
}
}
@ -127,7 +119,9 @@ namespace HidLibrary
public HidDeviceData Read(int timeout)
{
if (IsOpen == false) return new HidDeviceData(HidDeviceData.ReadStatus.ReadError);
if (IsConnected)
{
if (IsOpen == false) OpenDevice(_deviceReadMode, _deviceWriteMode, _deviceShareMode);
try
{
return ReadData(timeout);
@ -136,6 +130,9 @@ namespace HidLibrary
{
return new HidDeviceData(HidDeviceData.ReadStatus.ReadError);
}
}
return new HidDeviceData(HidDeviceData.ReadStatus.NotConnected);
}
public void Read(ReadCallback callback)
@ -184,6 +181,22 @@ namespace HidLibrary
return await Task<HidReport>.Factory.FromAsync(readReportDelegate.BeginInvoke, readReportDelegate.EndInvoke, timeout, null);
}
/// <summary>
/// Reads an input report from the Control channel. This method provides access to report data for devices that
/// do not use the interrupt channel to communicate for specific usages.
/// </summary>
/// <param name="reportId">The report ID to read from the device</param>
/// <returns>The HID report that is read. The report will contain the success status of the read request</returns>
///
public HidReport ReadReportSync(byte reportId)
{
byte[] cmdBuffer = new byte[Capabilities.InputReportByteLength];
cmdBuffer[0] = reportId;
bool bSuccess = NativeMethods.HidD_GetInputReport(Handle, cmdBuffer, cmdBuffer.Length);
HidDeviceData deviceData = new HidDeviceData(cmdBuffer, bSuccess ? HidDeviceData.ReadStatus.Success : HidDeviceData.ReadStatus.NoDataRead);
return new HidReport(Capabilities.InputReportByteLength, deviceData);
}
public bool ReadFeatureData(out byte[] data, byte reportId = 0)
{
if (_deviceCapabilities.FeatureReportByteLength <= 0)
@ -313,6 +326,8 @@ namespace HidLibrary
}
public bool Write(byte[] data, int timeout)
{
if (IsConnected)
{
if (IsOpen == false) OpenDevice(_deviceReadMode, _deviceWriteMode, _deviceShareMode);
try
@ -324,6 +339,8 @@ namespace HidLibrary
return false;
}
}
return false;
}
public void Write(byte[] data, WriteCallback callback)
{
@ -365,6 +382,25 @@ namespace HidLibrary
writeReportDelegate.BeginInvoke(report, timeout, EndWriteReport, asyncState);
}
/// <summary>
/// Handle data transfers on the control channel. This method places data on the control channel for devices
/// that do not support the interupt transfers
/// </summary>
/// <param name="report">The outbound HID report</param>
/// <returns>The result of the tranfer request: true if successful otherwise false</returns>
///
public bool WriteReportSync(HidReport report)
{
if (null != report)
{
byte[] buffer = report.GetBytes();
return (NativeMethods.HidD_SetOutputReport(Handle, buffer, buffer.Length));
}
else
throw new ArgumentException("The output report is null, it must be allocated before you call this method", "report");
}
public async Task<bool> WriteReportAsync(HidReport report, int timeout = 0)
{
var writeReportDelegate = new WriteReportDelegate(WriteReport);
@ -392,7 +428,7 @@ namespace HidLibrary
if (IsOpen)
hidHandle = Handle;
else
return false;
hidHandle = OpenDeviceIO(_devicePath, NativeMethods.ACCESS_NONE);
//var overlapped = new NativeOverlapped();
success = NativeMethods.HidD_SetFeature(hidHandle, buffer, buffer.Length);
@ -401,7 +437,11 @@ namespace HidLibrary
{
throw new Exception(string.Format("Error accessing HID device '{0}'.", _devicePath), exception);
}
finally
{
if (hidHandle != IntPtr.Zero && hidHandle != Handle)
CloseDeviceIO(hidHandle);
}
return success;
}
@ -547,58 +587,70 @@ namespace HidLibrary
{
var buffer = new byte[] { };
var status = HidDeviceData.ReadStatus.NoDataRead;
IntPtr nonManagedBuffer;
if (_deviceCapabilities.InputReportByteLength > 0)
{
uint bytesRead = 0;
buffer = CreateInputBuffer();
nonManagedBuffer = Marshal.AllocHGlobal(buffer.Length);
if (_deviceReadMode == DeviceMode.Overlapped)
{
if (!reading)
{
read_buffer = CreateInputBuffer();
var security = new NativeMethods.SECURITY_ATTRIBUTES();
read_overlapped = new NativeOverlapped();
var overlapped = new NativeOverlapped();
var overlapTimeout = timeout <= 0 ? NativeMethods.WAIT_INFINITE : timeout;
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);
}
overlapped.OffsetLow = 0;
overlapped.OffsetHigh = 0;
overlapped.EventHandle = NativeMethods.CreateEvent(ref security, Convert.ToInt32(false), Convert.ToInt32(true), string.Empty);
try
{
var result = NativeMethods.WaitForSingleObject(read_overlapped.EventHandle, timeout);
var success = NativeMethods.ReadFile(Handle, nonManagedBuffer, (uint)buffer.Length, out bytesRead, ref overlapped);
if (success)
{
status = HidDeviceData.ReadStatus.Success; // No check here to see if bytesRead > 0 . Perhaps not necessary?
}
else
{
var result = NativeMethods.WaitForSingleObject(overlapped.EventHandle, overlapTimeout);
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_OBJECT_0:
status = HidDeviceData.ReadStatus.Success;
NativeMethods.GetOverlappedResult(Handle, ref overlapped, out bytesRead, false);
break;
case NativeMethods.WAIT_TIMEOUT:
status = HidDeviceData.ReadStatus.WaitTimedOut;
NativeMethods.CancelIo(Handle);
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); }
Marshal.Copy(nonManagedBuffer, buffer, 0, (int)bytesRead);
}
catch { status = HidDeviceData.ReadStatus.ReadError; }
finally
{
CloseDeviceIO(overlapped.EventHandle);
Marshal.FreeHGlobal(nonManagedBuffer);
}
}
else
{
@ -606,10 +658,12 @@ namespace HidLibrary
{
var overlapped = new NativeOverlapped();
NativeMethods.ReadFile(Handle, buffer, (uint)buffer.Length, out bytesRead, ref overlapped);
NativeMethods.ReadFile(Handle, nonManagedBuffer, (uint)buffer.Length, out bytesRead, ref overlapped);
status = HidDeviceData.ReadStatus.Success;
Marshal.Copy(nonManagedBuffer, buffer, 0, (int)bytesRead);
}
catch { status = HidDeviceData.ReadStatus.ReadError; }
finally { Marshal.FreeHGlobal(nonManagedBuffer); }
}
}
return new HidDeviceData(buffer, status);

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
namespace HidLibrary
{
@ -36,19 +37,27 @@ namespace HidLibrary
productIds.Contains(x.Attributes.ProductId));
}
public static IEnumerable<HidDevice> Enumerate(int vendorId, int productId, ushort UsagePage)
{
return EnumerateDevices().Select(x => new HidDevice(x.Path, x.Description)).Where(x => x.Attributes.VendorId == vendorId &&
productId == (ushort)x.Attributes.ProductId && (ushort)x.Capabilities.UsagePage == UsagePage);
}
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; } }
public class DeviceInfo { public string Path { get; set; } public string Description { get; set; } public string InstanceID { get; set; } }
private static IEnumerable<DeviceInfo> EnumerateDevices()
public 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);
var buf = new char[1024];
if (deviceInfoSet.ToInt64() != NativeMethods.INVALID_HANDLE_VALUE)
{
var deviceInfoData = CreateDeviceInfoData();
@ -58,6 +67,9 @@ namespace HidLibrary
{
deviceIndex += 1;
NativeMethods.SetupDiGetDeviceInstanceId(deviceInfoSet, ref deviceInfoData, buf, buf.Length, out var requiredSize);
var instid = new string(buf, 0, requiredSize - 1);
var deviceInterfaceData = new NativeMethods.SP_DEVICE_INTERFACE_DATA();
deviceInterfaceData.cbSize = Marshal.SizeOf(deviceInterfaceData);
var deviceInterfaceIndex = 0;
@ -68,7 +80,7 @@ namespace HidLibrary
var devicePath = GetDevicePath(deviceInfoSet, deviceInterfaceData);
var description = GetBusReportedDeviceDescription(deviceInfoSet, ref deviceInfoData) ??
GetDeviceDescription(deviceInfoSet, ref deviceInfoData);
devices.Add(new DeviceInfo { Path = devicePath, Description = description });
devices.Add(new DeviceInfo { Path = devicePath, Description = description, InstanceID = instid });
}
}
NativeMethods.SetupDiDestroyDeviceInfoList(deviceInfoSet);

View File

@ -0,0 +1,44 @@
using System.Collections.Generic;
using System.Linq;
namespace HidLibrary
{
public class HidFastReadEnumerator : IHidEnumerator
{
public bool IsConnected(string devicePath)
{
return HidDevices.IsConnected(devicePath);
}
public IHidDevice GetDevice(string devicePath)
{
return Enumerate(devicePath).FirstOrDefault() as IHidDevice;
}
public IEnumerable<IHidDevice> Enumerate()
{
return HidDevices.EnumerateDevices().
Select(d => new HidFastReadDevice(d.Path, d.Description) as IHidDevice);
}
public IEnumerable<IHidDevice> Enumerate(string devicePath)
{
return HidDevices.EnumerateDevices().Where(x => x.Path == devicePath).
Select(d => new HidFastReadDevice(d.Path, d.Description) as IHidDevice);
}
public IEnumerable<IHidDevice> Enumerate(int vendorId, params int[] productIds)
{
return HidDevices.EnumerateDevices().Select(d => new HidFastReadDevice(d.Path, d.Description)).
Where(f => f.Attributes.VendorId == vendorId && productIds.Contains(f.Attributes.ProductId)).
Select(d => d as IHidDevice);
}
public IEnumerable<IHidDevice> Enumerate(int vendorId)
{
return HidDevices.EnumerateDevices().Select(d => new HidFastReadDevice(d.Path, d.Description)).
Where(f => f.Attributes.VendorId == vendorId).
Select(d => d as IHidDevice);
}
}
}

View File

@ -17,7 +17,7 @@ namespace HidLibrary
internal const uint WAIT_OBJECT_0 = 0;
internal const uint WAIT_FAILED = 0xffffffff;
internal const int WAIT_INFINITE = 0xffff;
internal const int WAIT_INFINITE = -1;
[StructLayout(LayoutKind.Sequential)]
internal struct OVERLAPPED
{
@ -55,13 +55,13 @@ namespace HidLibrary
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);
static internal extern bool ReadFile(IntPtr hFile, IntPtr 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", SetLastError = true)]
static internal extern bool GetOverlappedResult(IntPtr hFile, [In] ref System.Threading.NativeOverlapped lpOverlapped, out uint lpNumberOfBytesTransferred, 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);
@ -110,12 +110,6 @@ namespace HidLibrary
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)]
@ -212,12 +206,6 @@ namespace HidLibrary
[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);
@ -230,6 +218,9 @@ namespace HidLibrary
[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("setupapi.dll", SetLastError = true, CharSet = CharSet.Auto)]
static internal extern bool SetupDiGetDeviceInstanceId(IntPtr DeviceInfoSet, ref SP_DEVINFO_DATA DeviceInfoData, [Out] char[] DeviceInstanceId, int DeviceInstanceIdSize, out int RequiredSize);
[DllImport("user32.dll")]
static internal extern bool UnregisterDeviceNotification(IntPtr handle);
@ -268,22 +259,6 @@ namespace HidLibrary
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
{
@ -331,7 +306,7 @@ namespace HidLibrary
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);
static internal extern bool HidD_GetInputReport(IntPtr hidDeviceObject, byte[] lpReportBuffer, int reportBufferLength);
[DllImport("hid.dll")]
static internal extern void HidD_GetHidGuid(ref Guid hidGuid);

View File

@ -1,25 +1,26 @@
using MiController.Properties;
using System;
using System.ComponentModel;
using System.Drawing;
using System.Globalization;
using System.Windows.Forms;
using MiController.Win32;
using Nefarius.ViGEm.Client.Exceptions;
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;
private const string XIAOMI_GAMEPAD_HARDWARE_FILTER = @"VID&00022717_PID&3144";
private XInputManager _manager;
private HidMonitor _monitor;
public MiApplicationContext()
{
InitBackgroundWorker();
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
InitContextMenu();
InitGamepad();
StartGamePad();
// Initialize Tray Icon
_trayIcon = new NotifyIcon
{
@ -29,69 +30,33 @@ namespace MiController
};
}
private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs args)
{
var message = args.ExceptionObject is Exception exception ? exception.Message : args.ExceptionObject.ToString();
_statusToolStripMenuItem.Text = $"Error: {message}";
}
private void exitToolStripMenuItem_Click(object sender, EventArgs e)
{
_backgroundWorker.CancelAsync();
_trayIcon.Visible = false;
_monitor.Stop();
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;
_statusToolStripMenuItem.Image = start ? Resources.ConnectPlugged : Resources.ConnectUnplugged;
_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
Enabled = true
};
var toolStripSeparator2 = new ToolStripSeparator { Name = "toolStripSeparator2", Size = new Size(322, 6) };
@ -107,58 +72,57 @@ namespace MiController
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)
private void Manager_GamepadRunning(object sender, EventArgs eventArgs)
{
if (bw.CancellationPending)
{
e.Cancel = true;
}
}
_statusToolStripMenuItem.Text = "Gamepad connected";
StartStopProcess(true);
}
private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
private void Manager_GamepadRemoved(object sender, EventArgs eventArgs)
{
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;
}
_statusToolStripMenuItem.Text = "Gamepad disconnected";
StartStopProcess(false);
}
private void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
private void Monitor_DeviceAttached(object sender, DeviceEventArgs e)
{
_statusToolStripMenuItem.Text = (string)e.UserState;
//_statusToolStripMenuItem.Text = $"New HID device connected: {e.Description} {e.Path}";
_manager.AddAndStart(e.Path, e.InstanceId);
}
private void Monitor_DeviceRemoved(object sender, DeviceEventArgs e)
{
//_statusToolStripMenuItem.Text = $"HID device disconnected: {e.Description} {e.Path}";
_manager.StopAndRemove(e.Path);
}
internal class NoHighlightRenderer : ToolStripProfessionalRenderer
private void InitGamepad()
{
protected override void OnRenderMenuItemBackground(ToolStripItemRenderEventArgs e)
try
{
if (e.Item.Enabled)
_manager = new XInputManager();
_manager.GamepadRunning += Manager_GamepadRunning;
_manager.GamepadRemoved += Manager_GamepadRemoved;
}
catch (VigemBusNotFoundException ex)
{
base.OnRenderMenuItemBackground(e);
}
throw new ApplicationException("ViGEm Bus not found. Please make sure that is installed correctly", ex);
}
_monitor = new HidMonitor(XIAOMI_GAMEPAD_HARDWARE_FILTER);
_monitor.DeviceAttached += Monitor_DeviceAttached;
_monitor.DeviceRemoved += Monitor_DeviceRemoved;
}
private void StartGamePad()
{
_monitor.Start();
}
}
}

View File

@ -8,7 +8,7 @@
<OutputType>WinExe</OutputType>
<RootNamespace>MiController</RootNamespace>
<AssemblyName>MiController</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Deterministic>true</Deterministic>
@ -27,9 +27,10 @@
<IsWebBootstrapper>false</IsWebBootstrapper>
<UseApplicationTrust>false</UseApplicationTrust>
<BootstrapperEnabled>true</BootstrapperEnabled>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>x64</PlatformTarget>
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
@ -37,6 +38,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>x64</PlatformTarget>
@ -51,7 +53,7 @@
<ApplicationIcon>MiLogo.ico</ApplicationIcon>
</PropertyGroup>
<PropertyGroup>
<SignAssembly>true</SignAssembly>
<SignAssembly>false</SignAssembly>
</PropertyGroup>
<PropertyGroup>
<AssemblyOriginatorKeyFile>MiControllerKey.snk</AssemblyOriginatorKeyFile>
@ -60,6 +62,9 @@
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
<ItemGroup>
<Reference Include="Nefarius.ViGEm.Client, Version=1.17.178.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Nefarius.ViGEm.Client.1.17.178\lib\net452\Nefarius.ViGEm.Client.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Configuration.Install" />
<Reference Include="System.Core" />
@ -74,8 +79,6 @@
<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" />
@ -85,18 +88,24 @@
<Compile Include="HidLibrary\HidDeviceEventMonitor.cs" />
<Compile Include="HidLibrary\HidDevices.cs" />
<Compile Include="HidLibrary\HidFastReadDevice.cs" />
<Compile Include="HidLibrary\HidFastReadEnumerator.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="NoHighlightRenderer.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="Win32\DeviceEventArgs.cs" />
<Compile Include="Win32\DeviceInfoEqualityComparer.cs" />
<Compile Include="Win32\HidHide.cs" />
<Compile Include="Win32\HidMonitor.cs" />
<Compile Include="Win32\Native\Kernel32.cs" />
<Compile Include="Win32\VolumeHelper.cs" />
<Compile Include="Win32\WideStringHelper.cs" />
<Compile Include="XiaomiGamepad.cs" />
<Compile Include="XInputManager.cs" />
<Content Include="Setup.iss" />
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
@ -108,8 +117,10 @@
<DependentUpon>Resources.resx</DependentUpon>
<DesignTime>True</DesignTime>
</Compile>
<None Include="app.config" />
<None Include="app.manifest" />
<None Include="MiControllerKey.snk" />
<None Include="packages.config" />
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
@ -120,21 +131,6 @@
<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" />
@ -164,11 +160,6 @@
</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>

View File

@ -0,0 +1,15 @@
using System.Windows.Forms;
namespace MiController
{
internal class NoHighlightRenderer : ToolStripProfessionalRenderer
{
protected override void OnRenderMenuItemBackground(ToolStripItemRenderEventArgs e)
{
if (e.Item.Enabled)
{
base.OnRenderMenuItemBackground(e);
}
}
}
}

View File

@ -9,24 +9,8 @@ namespace MiController
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string[] args)
static void Main()
{
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

@ -1,5 +1,4 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
@ -10,7 +9,7 @@ using System.Runtime.InteropServices;
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("TomaSoft")]
[assembly: AssemblyProduct("MiController")]
[assembly: AssemblyCopyright("Copyright © 2020")]
[assembly: AssemblyCopyright("Copyright © 2022")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
@ -32,5 +31,5 @@ using System.Runtime.InteropServices;
// 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")]
[assembly: AssemblyVersion("1.1.0.0")]
[assembly: AssemblyFileVersion("1.1.0.0")]

View File

@ -19,7 +19,7 @@ namespace MiController.Properties {
// 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.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {

View File

@ -8,21 +8,17 @@
// </auto-generated>
//------------------------------------------------------------------------------
namespace MiController.Properties
{
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
{
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.0.3.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
{
public static Settings Default {
get {
return defaultInstance;
}
}

Binary file not shown.

View File

@ -1,71 +0,0 @@
; =============================================================================
; 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
; =============================================================================

View File

@ -1,312 +0,0 @@
/*
* 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

@ -1,157 +0,0 @@
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,
}
}

View File

@ -2,7 +2,7 @@
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
#define MyAppName "Mi Controller"
#define MyAppVersion "1.0"
#define MyAppVersion "1.1"
#define MyAppPublisher "TomaSoft"
#define MyAppExeName "MiController.exe"
@ -35,7 +35,6 @@ Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{
[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
@ -45,8 +44,6 @@ Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: de
[Run]
Filename: schtasks; Parameters: "/Create /SC ONLOGON /TN ""{#MyAppName}"" /TR ""{app}\{#MyAppExeName}"" /RL HIGHEST" ; Flags: nowait
Filename: "{app}\{#MyAppExeName}"; Parameters: "/install"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait skipifsilent
[UninstallRun]
Filename: "{app}\{#MyAppExeName}"; Parameters: "/uninstall";
Filename: schtasks; Parameters: "/Delete /TN ""{#MyAppName}"" /F "

View File

@ -0,0 +1,19 @@
using System;
using HidLibrary;
namespace MiController.Win32
{
public class DeviceEventArgs : EventArgs
{
internal DeviceEventArgs(HidDevices.DeviceInfo info)
{
Path = info.Path;
Description = info.Description;
InstanceId = info.InstanceID;
}
public string Path { get; set; }
public string Description { get; set; }
public string InstanceId { get; set; }
}
}

View File

@ -0,0 +1,11 @@
using System.Collections.Generic;
using HidLibrary;
namespace MiController.Win32
{
public class DeviceInfoEqualityComparer : IEqualityComparer<HidDevices.DeviceInfo>
{
public bool Equals(HidDevices.DeviceInfo x, HidDevices.DeviceInfo y) => y != null && x != null && x.Path == y.Path;
public int GetHashCode(HidDevices.DeviceInfo di) => di.Path.GetHashCode();
}
}

View File

@ -0,0 +1,219 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using MiController.Win32.Native;
using Microsoft.Win32.SafeHandles;
namespace MiController.Win32
{
public class HidHide : IDisposable
{
private const uint IOCTL_GET_WHITELIST = 0x80016000;
private const uint IOCTL_SET_WHITELIST = 0x80016004;
private const uint IOCTL_GET_BLACKLIST = 0x80016008;
private const uint IOCTL_SET_BLACKLIST = 0x8001600C;
private const uint IOCTL_GET_ACTIVE = 0x80016010;
private const uint IOCTL_SET_ACTIVE = 0x80016014;
public HidHide()
{
Handle = Kernel32.CreateFile(@"\\.\HidHide", FileAccess.Read, FileShare.ReadWrite, IntPtr.Zero,
FileMode.Open, FileAttributes.Normal, IntPtr.Zero);
}
private SafeFileHandle Handle { get; set; }
public bool EnableHiding
{
get
{
var buffer = Marshal.AllocHGlobal(sizeof(bool));
try
{
Kernel32.DeviceIoControl(
Handle, IOCTL_GET_ACTIVE,
IntPtr.Zero, 0, // Input: none
buffer, 1, out _, // Output: buffer of length 1
IntPtr.Zero
);
return Convert.ToBoolean(Marshal.ReadByte(buffer));
}
finally
{
Marshal.FreeHGlobal(buffer);
}
}
set
{
var buffer = Marshal.AllocHGlobal(sizeof(bool));
// Enable blocking logic, if not enabled already
try
{
Marshal.WriteByte(buffer, Convert.ToByte(value));
// Check return value for success
Kernel32.DeviceIoControl(
Handle, IOCTL_SET_ACTIVE,
buffer, sizeof(bool), // Input: 1 bool
IntPtr.Zero, 0, out _, // Output: ignored
IntPtr.Zero
);
}
finally
{
Marshal.FreeHGlobal(buffer);
}
}
}
public bool SetDeviceHideStatus(string instance, bool state)
{
var buffer = IntPtr.Zero;
try
{
var blacklist = GetBlacklist();
var newlist = state
? blacklist.Concat(new[] { instance }).Distinct()
: blacklist.Where(e => e != instance).Distinct();
buffer = newlist.StringArrayToMultiSzPointer(out var length);
// Submit new list
// Check return value for success
return Kernel32.DeviceIoControl(
Handle, IOCTL_SET_BLACKLIST,
buffer, length,
IntPtr.Zero, 0, out _,
IntPtr.Zero
);
}
finally
{
Marshal.FreeHGlobal(buffer);
}
}
public bool WhitelistCurrentApplication()
{
var buffer = IntPtr.Zero;
// Manipulate allow-list and submit it
try
{
var appPath = Assembly.GetEntryAssembly()?.Location;
var dosPath = VolumeHelper.PathToDosDevicePath(appPath);
var whitelist = GetWhitelist().ToArray();
if (whitelist.Contains(dosPath))
return true;
buffer = whitelist
.Concat(new[] { dosPath })
.Distinct()
.StringArrayToMultiSzPointer(out var length);
// Submit new list
// Check return value for success
return Kernel32.DeviceIoControl(
Handle,
IOCTL_SET_WHITELIST,
buffer, length,
IntPtr.Zero, 0, out _,
IntPtr.Zero
);
}
finally
{
Marshal.FreeHGlobal(buffer);
}
}
private IEnumerable<string> GetBlacklist()
{
// List of blocked instances
var buffer = IntPtr.Zero;
// Get existing list of blocked instances
// This is important to not discard entries other processes potentially made
// Always get the current list before altering/submitting it
try
{
// Get required buffer size
Kernel32.DeviceIoControl(
Handle, IOCTL_GET_BLACKLIST,
IntPtr.Zero, 0, // Input: none
IntPtr.Zero, 0, out var required, // Output: buffer size
IntPtr.Zero
);
buffer = Marshal.AllocHGlobal(required);
// Get actual buffer content
Kernel32.DeviceIoControl(
Handle, IOCTL_GET_BLACKLIST,
IntPtr.Zero, 0, // Input: none
buffer, required, out _, // Output: buffer of known length
IntPtr.Zero
);
// Store existing block-list in a more manageable "C#" fashion
return buffer.MultiSzPointerToStringArray(required).ToList();
}
finally
{
Marshal.FreeHGlobal(buffer);
}
}
private IEnumerable<string> GetWhitelist()
{
// List of blocked instances
var buffer = IntPtr.Zero;
// Get existing list of blocked instances
// This is important to not discard entries other processes potentially made
// Always get the current list before altering/submitting it
try
{
// Get required buffer size
Kernel32.DeviceIoControl(
Handle, IOCTL_GET_WHITELIST,
IntPtr.Zero, 0, // Input: none
IntPtr.Zero, 0, out var required, // Output: buffer size
IntPtr.Zero
);
buffer = Marshal.AllocHGlobal(required);
// Get actual buffer content
Kernel32.DeviceIoControl(
Handle, IOCTL_GET_WHITELIST,
IntPtr.Zero, 0, // Input: none
buffer, required, out _, // Output: buffer of known length
IntPtr.Zero
);
// Store existing block-list in a more manageable "C#" fashion
return buffer.MultiSzPointerToStringArray(required).ToList();
}
finally
{
Marshal.FreeHGlobal(buffer);
}
}
public void Dispose()
{
Handle.Dispose();
}
}
}

View File

@ -0,0 +1,74 @@
using System;
using System.Linq;
using System.Threading;
using HidLibrary;
namespace MiController.Win32
{
public class HidMonitor
{
public event EventHandler<DeviceEventArgs> DeviceAttached;
public event EventHandler<DeviceEventArgs> DeviceRemoved;
private readonly Timer _monitorTimer;
private readonly string _filter;
private HidDevices.DeviceInfo[] _seenDevices;
public HidMonitor(string filter)
{
// Initializing HID device monitor with filter
_filter = filter;
_monitorTimer = new Timer(SearchForDevice);
_seenDevices = Array.Empty<HidDevices.DeviceInfo>();
}
public void Start()
{
// Start monitoring for filter
_monitorTimer.Change(0, 5000);
}
public void Stop()
{
// Stop monitoring for filter
_monitorTimer.Change(Timeout.Infinite, Timeout.Infinite);
}
private void SearchForDevice(object state)
{
var filter = _filter.ToLower();
var devices = HidDevices
.EnumerateDevices()
.Where(p => p.Path.ToLower().Contains(filter))
.ToArray();
var comp = new DeviceInfoEqualityComparer();
// Get all the devices that has connected since the last check
var newDevices = devices.Except(_seenDevices, comp);
// Get all the device that has disconnected since the last check
var removedDevices = _seenDevices.Except(devices, comp);
foreach (var device in newDevices)
{
// Detected attached HID devices matching filter
DeviceAttached?.Invoke(this, new DeviceEventArgs(device));
}
foreach (var device in removedDevices)
{
// Detected removed HID devices matching filter
DeviceRemoved?.Invoke(this, new DeviceEventArgs(device));
}
_seenDevices = devices;
}
public void Dispose()
{
// De-initilizing HID monitor
_monitorTimer.Dispose();
}
}
}

View File

@ -0,0 +1,31 @@
using System;
using System.IO;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
namespace MiController.Win32.Native
{
public static class Kernel32
{
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern SafeFileHandle CreateFile(
string lpFileName,
[MarshalAs(UnmanagedType.U4)] FileAccess dwDesiredAccess,
[MarshalAs(UnmanagedType.U4)] FileShare dwShareMode,
IntPtr lpSecurityAttributes,
[MarshalAs(UnmanagedType.U4)] FileMode dwCreationDisposition,
[MarshalAs(UnmanagedType.U4)] FileAttributes dwFlagsAndAttributes,
IntPtr hTemplateFile);
[DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true, CharSet = CharSet.Auto)]
public static extern bool DeviceIoControl(
SafeFileHandle hDevice,
uint dwIoControlCode,
IntPtr lpInBuffer,
int nInBufferSize,
IntPtr lpOutBuffer,
int nOutBufferSize,
out int lpBytesReturned,
IntPtr lpOverlapped);
}
}

View File

@ -0,0 +1,162 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
namespace MiController.Win32
{
// Taken from https://vigem.org/projects/HidHide/API-Documentation/
/// <summary>
/// Path manipulation and volume helper methods.
/// </summary>
internal static class VolumeHelper
{
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GetVolumePathNamesForVolumeNameW(
[MarshalAs(UnmanagedType.LPWStr)] string lpszVolumeName,
[MarshalAs(UnmanagedType.LPWStr)] [Out]
StringBuilder lpszVolumeNamePaths, uint cchBuferLength,
ref uint lpcchReturnLength);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr FindFirstVolume([Out] StringBuilder lpszVolumeName,
uint cchBufferLength);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool FindNextVolume(IntPtr hFindVolume, [Out] StringBuilder lpszVolumeName,
uint cchBufferLength);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern uint QueryDosDevice(string lpDeviceName, StringBuilder lpTargetPath, int ucchMax);
private class VolumeMeta
{
public string DriveLetter { get; set; }
public string VolumeName { get; set; }
public string DevicePath { get; set; }
}
/// <summary>
/// Curates and returns a collection of volume to path mappings.
/// </summary>
/// <returns>A collection of <see cref="VolumeMeta"/>.</returns>
private static IEnumerable<VolumeMeta> GetVolumeMappings()
{
var volumeName = new StringBuilder(ushort.MaxValue);
var pathName = new StringBuilder(ushort.MaxValue);
var mountPoint = new StringBuilder(ushort.MaxValue);
uint returnLength = 0;
var volumeHandle = FindFirstVolume(volumeName, ushort.MaxValue);
do
{
var volume = volumeName.ToString();
if (!GetVolumePathNamesForVolumeNameW(volume, mountPoint, ushort.MaxValue, ref returnLength))
continue;
// Extract volume name for use with QueryDosDevice
var deviceName = volume.Substring(4, volume.Length - 1 - 4);
// Grab device path
returnLength = QueryDosDevice(deviceName, pathName, ushort.MaxValue);
if (returnLength <= 0)
continue;
yield return new VolumeMeta
{
DriveLetter = mountPoint.ToString(),
VolumeName = volume,
DevicePath = pathName.ToString()
};
} while (FindNextVolume(volumeHandle, volumeName, ushort.MaxValue));
}
/// <summary>
/// Checks if a path is a junction point.
/// </summary>
/// <param name="di">A <see cref="FileSystemInfo"/> instance.</param>
/// <returns>True if it's a junction, false otherwise.</returns>
private static bool IsPathReparsePoint(FileSystemInfo di)
{
return di.Attributes.HasFlag(FileAttributes.ReparsePoint);
}
/// <summary>
/// Helper to make paths comparable.
/// </summary>
/// <param name="path">The source path.</param>
/// <returns>The normalized path.</returns>
private static string NormalizePath(string path)
{
return Path.GetFullPath(new Uri(path).LocalPath)
.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)
.ToUpperInvariant();
}
/// <summary>
/// Translates a user-land file path to "DOS device" path.
/// </summary>
/// <param name="path">The file path in normal namespace format.</param>
/// <returns>The device namespace path (DOS device).</returns>
public static string PathToDosDevicePath(string path)
{
if (!File.Exists(path))
throw new ArgumentException("The supplied file path doesn't exist", nameof(path));
var filePart = Path.GetFileName(path);
var pathPart = Path.GetDirectoryName(path);
if (string.IsNullOrEmpty(pathPart))
throw new IOException("Couldn't resolve directory");
var pathNoRoot = string.Empty;
var devicePath = string.Empty;
// Walk up the directory tree to get the "deepest" potential junction
for (var current = new DirectoryInfo(pathPart);
current != null && current.Exists;
current = Directory.GetParent(current.FullName))
{
if (!IsPathReparsePoint(current)) continue;
devicePath = GetVolumeMappings().FirstOrDefault(m =>
!string.IsNullOrEmpty(m.DriveLetter) &&
NormalizePath(m.DriveLetter) == NormalizePath(current.FullName))
?.DevicePath;
pathNoRoot = pathPart.Substring(current.FullName.Length);
break;
}
// No junctions found, translate original path
if (string.IsNullOrEmpty(devicePath))
{
var driveLetter = Path.GetPathRoot(pathPart);
devicePath = GetVolumeMappings().FirstOrDefault(m =>
m.DriveLetter.Equals(driveLetter, StringComparison.InvariantCultureIgnoreCase))?.DevicePath;
pathNoRoot = pathPart.Substring(Path.GetPathRoot(pathPart).Length);
}
if (string.IsNullOrEmpty(devicePath))
throw new IOException("Couldn't resolve device path");
var fullDevicePath = new StringBuilder();
// Build new DOS Device path
fullDevicePath.AppendFormat("{0}{1}", devicePath, Path.DirectorySeparatorChar);
fullDevicePath.Append(Path.Combine(pathNoRoot, filePart).TrimStart(Path.DirectorySeparatorChar));
return fullDevicePath.ToString();
}
}
}

View File

@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
namespace MiController.Win32
{
// Taken from https://vigem.org/projects/HidHide/API-Documentation/
/// <summary>
/// String manipulation helper methods.
/// </summary>
internal static class WideStringHelper
{
/// <summary>
/// Converts an array of <see cref="string" /> into a double-null-terminated multi-byte character memory block.
/// </summary>
/// <param name="instances">Source array of strings.</param>
/// <param name="length">The length of the resulting byte array.</param>
/// <returns>The allocated memory buffer.</returns>
public static IntPtr StringArrayToMultiSzPointer(this IEnumerable<string> instances, out int length)
{
// Temporary byte array
IEnumerable<byte> multiSz = new List<byte>();
// Convert each string into wide multi-byte and add NULL-terminator in between
multiSz = instances.Aggregate(multiSz,
(current, entry) => current.Concat(Encoding.Unicode.GetBytes(entry))
.Concat(Encoding.Unicode.GetBytes(new[] { char.MinValue })));
// Add another NULL-terminator to signal end of the list
multiSz = multiSz.Concat(Encoding.Unicode.GetBytes(new[] { char.MinValue }));
// Convert expression to array
var multiSzArray = multiSz.ToArray();
// Convert array to managed native buffer
var buffer = Marshal.AllocHGlobal(multiSzArray.Length);
Marshal.Copy(multiSzArray, 0, buffer, multiSzArray.Length);
length = multiSzArray.Length;
// Return usable buffer, don't forget to free!
return buffer;
}
/// <summary>
/// Converts a double-null-terminated multi-byte character memory block into a string array.
/// </summary>
/// <param name="buffer">The memory buffer.</param>
/// <param name="length">The size in bytes of the memory buffer.</param>
/// <returns>The extracted string array.</returns>
public static IEnumerable<string> MultiSzPointerToStringArray(this IntPtr buffer, int length)
{
// Temporary byte array
var rawBuffer = new byte[length];
// Grab data from buffer
Marshal.Copy(buffer, rawBuffer, 0, length);
// Trims away potential redundant NULL-characters and splits at NULL-terminator
return Encoding.Unicode.GetString(rawBuffer)
.TrimEnd(char.MinValue)
.Split(char.MinValue)
.Where(s => !String.IsNullOrWhiteSpace(s));
}
}
}

View File

@ -0,0 +1,132 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using MiController.Win32;
using Nefarius.ViGEm.Client;
namespace MiController
{
public class XInputManager : IDisposable
{
public event EventHandler GamepadRunning;
public event EventHandler GamepadRemoved;
private readonly Dictionary<string, XiaomiGamepad> _gamepads;
private readonly ViGEmClient _viGEmClient;
private readonly SynchronizationContext _syncContext;
public XInputManager()
{
//Initializing ViGEm client
_viGEmClient = new ViGEmClient();
_gamepads = new Dictionary<string, XiaomiGamepad>();
_syncContext = SynchronizationContext.Current;
}
public void Dispose()
{
// Cleaning up running gamepads
// When calling Stop() the device will get removed from the dictionary, do this to avoid exceptions in enumeration
var devices = _gamepads.Values.ToArray();
foreach (var device in devices)
{
StopAndRemove(device);
device.Dispose();
}
// Deinitializing ViGEm client
_viGEmClient.Dispose();
}
public bool AddAndStart(string device, string instance)
{
if (Contains(device))
{
// Requested addition of already existing device
return false;
}
// Adding device
var gamepad = new XiaomiGamepad(device, instance, _viGEmClient);
if (_gamepads.ContainsKey(device))
{
_gamepads[device].Dispose();
_gamepads.Remove(device);
}
_gamepads.Add(device, gamepad);
gamepad.Started += Gamepad_Started;
gamepad.Ended += Gamepad_Ended;
// Hiding device instance
using (var hh = new HidHide())
{
hh.SetDeviceHideStatus(gamepad.InstanceId, true);
}
// Starting device
gamepad.Start();
return true;
}
public void StopAndRemove(string device)
{
if (!Contains(device))
{
// Requested removal of non-existing device
return;
}
var gamepad = _gamepads[device];
StopAndRemove(gamepad);
}
private void StopAndRemove(XiaomiGamepad gamepad)
{
// Stopping device
if (gamepad.IsActive)
{
gamepad.Stop();
}
gamepad.Started -= Gamepad_Started;
gamepad.Ended -= Gamepad_Ended;
// Un-hiding device instance
using (var hh = new HidHide())
{
hh.SetDeviceHideStatus(gamepad.InstanceId, false);
}
// De-initializing and removing device
_gamepads.Remove(gamepad.Device.DevicePath);
gamepad.Dispose();
}
public bool Contains(string device) => _gamepads.ContainsKey(device);
private void Gamepad_Ended(object sender, EventArgs eventArgs)
{
_syncContext.Post(o =>
{
if (sender is XiaomiGamepad gamepad && !gamepad.CleanEnd)
{
StopAndRemove(gamepad);
}
GamepadRemoved?.Invoke(this, EventArgs.Empty);
}, null);
}
private void Gamepad_Started(object sender, EventArgs eventArgs)
{
_syncContext.Post(o => GamepadRunning?.Invoke(this, EventArgs.Empty), null);
}
}
}

View File

@ -1,200 +1,309 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using HidLibrary;
using ScpDriverInterface;
using System.Threading;
using System.Runtime.InteropServices;
using Nefarius.ViGEm.Client;
using Nefarius.ViGEm.Client.Exceptions;
using Nefarius.ViGEm.Client.Targets;
using Nefarius.ViGEm.Client.Targets.Xbox360;
namespace MiController
{
public class XiaomiGamepad
public class XiaomiGamepad : IDisposable
{
private byte[] Vibration = { 0x20, 0x00, 0x00 };
private Mutex rumble_mutex = new Mutex();
private static readonly Xbox360Button[][] HatSwitches = {
new [] { Xbox360Button.Up },
new [] { Xbox360Button.Up, Xbox360Button.Right },
new [] { Xbox360Button.Right },
new [] { Xbox360Button.Right, Xbox360Button.Down },
new [] { Xbox360Button.Down },
new [] { Xbox360Button.Down, Xbox360Button.Left },
new [] { Xbox360Button.Left },
new [] { Xbox360Button.Left, Xbox360Button.Up },
};
public XiaomiGamepad(HidDevice Device, ScpBus scpBus, int index)
public event EventHandler Started;
public event EventHandler Ended;
private readonly IXbox360Controller _target;
private readonly Thread _inputThread;
private readonly CancellationTokenSource _cts;
private readonly Timer _vibrationTimer;
private static readonly IHidEnumerator DeviceEnumerator = new HidFastReadEnumerator();
public XiaomiGamepad(string device, string instance, ViGEmClient client)
{
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)
Device = DeviceEnumerator.GetDevice(device) as HidFastReadDevice;
if (Device != null)
{
byte[] local_vibration = { 0x20, 0x00, 0x00 };
while (true)
Device.MonitorDeviceEvents = false;
}
_target = client.CreateXbox360Controller();
_target.AutoSubmitReport = false;
_target.FeedbackReceived += Target_OnFeedbackReceived;
// TODO mark the threads as background?
_inputThread = new Thread(DeviceWorker);
_cts = new CancellationTokenSource();
_vibrationTimer = new Timer(VibrationTimer_Trigger);
LedNumber = 0xFF;
InstanceId = instance;
}
public HidFastReadDevice Device { get; }
public ushort LedNumber { get; set; }
public ushort BatteryLevel { get; private set; }
public bool IsActive => _inputThread.IsAlive;
public bool CleanEnd => _cts.IsCancellationRequested;
public bool ExclusiveMode { get; private set; }
public string InstanceId { get; }
public void Dispose()
{
rumble_mutex.WaitOne();
if (local_vibration[2] != Vibration[2] || Vibration[1] != local_vibration[1])
// De-initializing XiaomiGamepad handler for device
if (_inputThread.IsAlive)
Stop();
Device.Dispose();
_cts.Dispose();
}
public void Start()
{
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]);
_inputThread.Start();
}
else
public void Stop()
{
rumble_mutex.ReleaseMutex();
}
Thread.Sleep(20);
}
}
private void input_thread(HidDevice Device, ScpBus scpBus, int index)
if (_cts.IsCancellationRequested)
{
scpBus.PlugIn(index);
X360Controller controller = new X360Controller();
int timeout = 30;
long last_changed = 0;
long last_mi_button = 0;
while (true)
// Thread stop for device already requested
return;
}
// Requesting thread stop for device
_cts.Cancel();
_inputThread.Join();
}
private void DeviceWorker()
{
HidDeviceData data = Device.Read(timeout);
var currentState = data.Data;
bool changed = false;
if (data.Status == HidDeviceData.ReadStatus.Success && currentState.Length >= 21 && currentState[0] == 4)
// Starting worker thread for device
// Open HID device to read input from the gamepad
Device.OpenDevice(DeviceMode.Overlapped, DeviceMode.Overlapped, ShareMode.Exclusive);
ExclusiveMode = true;
// If exclusive mode is not available, retry in shared mode.
if (!Device.IsOpen)
{
//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;
// Cannot access HID device in exclusive mode, retrying in shared mode
if ((currentState[2] & 32) != 0) Buttons |= X360Buttons.LeftStick;
if ((currentState[2] & 64) != 0) Buttons |= X360Buttons.RightStick;
Device.OpenDevice(DeviceMode.Overlapped, DeviceMode.Overlapped, ShareMode.ShareRead | ShareMode.ShareWrite);
ExclusiveMode = false;
if (currentState[4] != 15)
if (!Device.IsOpen)
{
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;
// Cannot open HID device
Device.CloseDevice();
Ended?.Invoke(this, EventArgs.Empty);
return;
}
}
if ((currentState[2] & 8) != 0) Buttons |= X360Buttons.Start;
if ((currentState[2] & 4) != 0) Buttons |= X360Buttons.Back;
// Init Xiaomi Gamepad vibration
Device.WriteFeatureData(new byte[] { 0x20, 0x00, 0x00 });
if ((currentState[20] & 1) != 0)
// Connect the virtual Xbox360 gamepad
try
{
last_mi_button = (DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond);
Buttons |= X360Buttons.Logo;
// Connecting to ViGEm client
_target.Connect();
}
if (last_mi_button != 0) Buttons |= X360Buttons.Logo;
if (controller.Buttons != Buttons)
catch (VigemAlreadyConnectedException)
{
changed = true;
controller.Buttons = Buttons;
// ViGEm client was already opened, closing and reopening it
_target.Disconnect();
_target.Connect();
}
short LeftStickX = (short)((Math.Max(-127.0, currentState[5] - 128) / 127) * 32767);
if (LeftStickX == -32767)
LeftStickX = -32768;
Started?.Invoke(this, EventArgs.Empty);
if (LeftStickX != controller.LeftStickX)
var token = _cts.Token;
while (!token.IsCancellationRequested)
{
changed = true;
controller.LeftStickX = LeftStickX;
}
// Is device has been closed, exit the loop
if (!Device.IsOpen)
break;
short LeftStickY = (short)((Math.Max(-127.0, currentState[6] - 128) / 127) * -32767);
if (LeftStickY == -32767)
LeftStickY = -32768;
// Otherwise read a report
var hidReport = Device.FastReadReport(1000);
if (LeftStickY != controller.LeftStickY)
if (hidReport.ReadStatus == HidDeviceData.ReadStatus.WaitTimedOut)
continue;
if (hidReport.ReadStatus != HidDeviceData.ReadStatus.Success)
{
changed = true;
controller.LeftStickY = LeftStickY;
// Cannot read HID report for device
break;
}
short RightStickX = (short)((Math.Max(-127.0, currentState[7] - 128) / 127) * 32767);
if (RightStickX == -32767)
RightStickX = -32768;
var data = hidReport.Data;
if (RightStickX != controller.RightStickX)
/*
[0] Buttons state, 1 bit per button
[1] Buttons state, 1 bit per button
[2] 0x00
[3] D-Pad
[4] Left thumb, X axis
[5] Left thumb, Y axis
[6] Right thumb, X axis
[7] Right thumb, Y axis
[8] 0x00
[9] 0x00
[10] L trigger
[11] R trigger
[12] Accelerometer axis 1
[13] Accelerometer axis 1
[14] Accelerometer axis 2
[15] Accelerometer axis 2
[16] Accelerometer axis 3
[17] Accelerometer axis 3
[18] Battery level
[19] MI button
*/
lock (_target)
{
changed = true;
controller.RightStickX = RightStickX;
}
_target.SetButtonState(Xbox360Button.A, GetBit(data[0], 0));
_target.SetButtonState(Xbox360Button.B, GetBit(data[0], 1));
_target.SetButtonState(Xbox360Button.X, GetBit(data[0], 3));
_target.SetButtonState(Xbox360Button.Y, GetBit(data[0], 4));
_target.SetButtonState(Xbox360Button.LeftShoulder, GetBit(data[0], 6));
_target.SetButtonState(Xbox360Button.RightShoulder, GetBit(data[0], 7));
short RightStickY = (short)((Math.Max(-127.0, currentState[8] - 128) / 127) * -32767);
if (RightStickY == -32767)
RightStickY = -32768;
_target.SetButtonState(Xbox360Button.Back, GetBit(data[1], 2));
_target.SetButtonState(Xbox360Button.Start, GetBit(data[1], 3));
_target.SetButtonState(Xbox360Button.LeftThumb, GetBit(data[1], 5));
_target.SetButtonState(Xbox360Button.RightThumb, GetBit(data[1], 6));
if (RightStickY != controller.RightStickY)
// Reset Hat switch status, as is set to 15 (all directions set, impossible state)
_target.SetButtonState(Xbox360Button.Up, false);
_target.SetButtonState(Xbox360Button.Left, false);
_target.SetButtonState(Xbox360Button.Down, false);
_target.SetButtonState(Xbox360Button.Right, false);
if (data[3] < 8)
{
changed = true;
controller.RightStickY = RightStickY;
var btns = HatSwitches[data[3]];
// Hat Switch is a number from 0 to 7, where 0 is Up, 1 is Up-Left, etc.
foreach (var b in btns)
_target.SetButtonState(b, true);
}
if (controller.LeftTrigger != currentState[11])
// Analog axis
_target.SetAxisValue(Xbox360Axis.LeftThumbX, MapAnalog(data[4]));
_target.SetAxisValue(Xbox360Axis.LeftThumbY, MapAnalog(data[5], true));
_target.SetAxisValue(Xbox360Axis.RightThumbX, MapAnalog(data[6]));
_target.SetAxisValue(Xbox360Axis.RightThumbY, MapAnalog(data[7], true));
// Triggers
_target.SetSliderValue(Xbox360Slider.LeftTrigger, data[10]);
_target.SetSliderValue(Xbox360Slider.RightTrigger, data[11]);
// Logo ("home") button
if (GetBit(data[19], 0))
{
changed = true;
controller.LeftTrigger = currentState[11];
_target.SetButtonState(Xbox360Button.Guide, true);
Task.Delay(200, token).ContinueWith(DelayedReleaseGuideButton);
}
if (controller.RightTrigger != currentState[12])
{
changed = true;
controller.RightTrigger = currentState[12];
// Update battery level
BatteryLevel = data[18];
_target.SubmitReport();
}
}
// Disconnect the virtual Xbox360 gamepad
// Let Dispose handle that, otherwise it will rise a NotPluggedIn exception
// Disconnecting ViGEm client
_target.Disconnect();
// Close the HID device
// Closing HID device
Device.CloseDevice();
// Exiting worker thread for device
Ended?.Invoke(this, EventArgs.Empty);
}
private static bool GetBit(byte b, int bit)
{
return ((b >> bit) & 1) != 0;
}
private static short MapAnalog(byte value, bool invert = false)
{
return (short)(value * 257 * (invert ? -1 : 1) + short.MinValue);
}
private void DelayedReleaseGuideButton(Task t)
{
lock (_target)
{
_target.SetButtonState(Xbox360Button.Guide, false);
_target.SubmitReport();
}
}
if (data.Status == HidDeviceData.ReadStatus.WaitTimedOut || (!changed && ((last_changed + timeout) < (DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond))))
private void VibrationTimer_Trigger(object o)
{
changed = true;
Task.Run(() => {
lock (_vibrationTimer)
{
if (Device.IsOpen)
Device.WriteFeatureData(new byte[] { 0x20, 0x00, 0x00 });
// Vibration feedback reset after 3 seconds for device
}
});
}
if (changed)
private void Target_OnFeedbackReceived(object sender, Xbox360FeedbackReceivedEventArgs e)
{
//Console.WriteLine("changed");
//Console.WriteLine((DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond));
byte[] outputReport = new byte[8];
scpBus.Report(index, controller.GetReport(), outputReport);
byte[] data = { 0x20, e.SmallMotor, e.LargeMotor };
if (outputReport[1] == 0x08)
Task.Run(() => {
lock (_vibrationTimer)
{
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 (!Device.IsOpen)
return;
Device.WriteFeatureData(data);
}
if (last_mi_button != 0)
var timeout = e.SmallMotor > 0 || e.LargeMotor > 0 ? 3000 : Timeout.Infinite;
_vibrationTimer.Change(timeout, Timeout.Infinite);
});
if (LedNumber != e.LedNumber)
{
if ((last_mi_button + 100) < (DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond))
{
last_mi_button = 0;
controller.Buttons ^= X360Buttons.Logo;
LedNumber = e.LedNumber;
// TODO raise event here
}
}
last_changed = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;
}
}
}
}
}

6
MiController/app.config Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8"/>
</startup>
</configuration>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Nefarius.ViGEm.Client" version="1.17.178" targetFramework="net48" />
</packages>