Use PawnIO driver directly in LibreHardwareMonitorLib (#1908)
* Use PawnIO driver directly. - Implemented PawnIO library in C# - Builds in AnyCPU, x64 and x86 - Fixes #1895 * Implement review changes for PawnIO. - Use existing Kernel32.CreateFile import. - Move new DeviceIOControl to Kernel32. - Replace Lazy Version loading with static property. - Remove bin resource cache. - Add constant for function name length. * More review changes for PawnIO. - Replace redundant internal checks with IsLoaded. - Convert Win32 error code to HRESULT before returning it in ExecuteHR() * Drop KernelDriver --------- Co-authored-by: PhyxionNL <7643972+PhyxionNL@users.noreply.github.com>
This commit is contained in:
co-authored by
PhyxionNL
parent
79a043e5bf
commit
c56cf3f042
@@ -171,7 +171,7 @@ public sealed partial class MainForm : Form
|
||||
|
||||
if (PawnIo.PawnIo.IsInstalled)
|
||||
{
|
||||
if (PawnIo.PawnIo.Version() < new Version(2, 0, 0, 0))
|
||||
if (PawnIo.PawnIo.Version < new Version(2, 0, 0, 0))
|
||||
{
|
||||
DialogResult result = MessageBox.Show("PawnIO is outdated, do you want to update it?", nameof(LibreHardwareMonitor), MessageBoxButtons.OKCancel);
|
||||
if (result == DialogResult.OK)
|
||||
|
||||
@@ -1,184 +0,0 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
|
||||
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
// Copyright (C) LibreHardwareMonitor and Contributors.
|
||||
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
|
||||
// All Rights Reserved.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.AccessControl;
|
||||
using LibreHardwareMonitor.Interop;
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
|
||||
namespace LibreHardwareMonitor.Hardware;
|
||||
|
||||
internal class KernelDriver
|
||||
{
|
||||
private readonly string _driverId;
|
||||
private readonly string _serviceName;
|
||||
private SafeFileHandle _device;
|
||||
|
||||
public KernelDriver(string serviceName, string driverId)
|
||||
{
|
||||
_serviceName = serviceName;
|
||||
_driverId = driverId;
|
||||
}
|
||||
|
||||
public bool IsOpen => _device != null;
|
||||
|
||||
public bool Install(string path, out string errorMessage)
|
||||
{
|
||||
IntPtr manager = AdvApi32.OpenSCManager(null, null, AdvApi32.SC_MANAGER_ACCESS_MASK.SC_MANAGER_CREATE_SERVICE);
|
||||
if (manager == IntPtr.Zero)
|
||||
{
|
||||
int errorCode = Marshal.GetLastWin32Error();
|
||||
errorMessage = $"OpenSCManager returned the error code: {errorCode:X8}.";
|
||||
return false;
|
||||
}
|
||||
|
||||
IntPtr service = AdvApi32.CreateService(manager,
|
||||
_serviceName,
|
||||
_serviceName,
|
||||
AdvApi32.SERVICE_ACCESS_MASK.SERVICE_ALL_ACCESS,
|
||||
AdvApi32.SERVICE_TYPE.SERVICE_KERNEL_DRIVER,
|
||||
AdvApi32.SERVICE_START.SERVICE_DEMAND_START,
|
||||
AdvApi32.SERVICE_ERROR.SERVICE_ERROR_NORMAL,
|
||||
path,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null);
|
||||
|
||||
if (service == IntPtr.Zero)
|
||||
{
|
||||
int errorCode = Marshal.GetLastWin32Error();
|
||||
if (errorCode == Kernel32.ERROR_SERVICE_EXISTS)
|
||||
{
|
||||
errorMessage = "Service already exists";
|
||||
return false;
|
||||
}
|
||||
|
||||
errorMessage = $"CreateService returned the error code: {errorCode:X8}.";
|
||||
AdvApi32.CloseServiceHandle(manager);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!AdvApi32.StartService(service, 0, null))
|
||||
{
|
||||
int errorCode = Marshal.GetLastWin32Error();
|
||||
if (errorCode != Kernel32.ERROR_SERVICE_ALREADY_RUNNING)
|
||||
{
|
||||
errorMessage = $"StartService returned the error code: {errorCode:X8}.";
|
||||
AdvApi32.CloseServiceHandle(service);
|
||||
AdvApi32.CloseServiceHandle(manager);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
AdvApi32.CloseServiceHandle(service);
|
||||
AdvApi32.CloseServiceHandle(manager);
|
||||
|
||||
try
|
||||
{
|
||||
// restrict the driver access to system (SY) and builtin admins (BA)
|
||||
// TODO: replace with a call to IoCreateDeviceSecure in the driver
|
||||
FileInfo fileInfo = new(@"\\.\" + _driverId);
|
||||
FileSecurity fileSecurity = fileInfo.GetAccessControl();
|
||||
fileSecurity.SetSecurityDescriptorSddlForm("O:BAG:SYD:(A;;FA;;;SY)(A;;FA;;;BA)");
|
||||
fileInfo.SetAccessControl(fileSecurity);
|
||||
}
|
||||
catch
|
||||
{ }
|
||||
|
||||
errorMessage = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Open()
|
||||
{
|
||||
IntPtr fileHandle = Kernel32.CreateFile(@"\\.\" + _driverId, 0xC0000000, FileShare.None, IntPtr.Zero, FileMode.Open, FileAttributes.Normal, IntPtr.Zero);
|
||||
|
||||
_device = new SafeFileHandle(fileHandle, true);
|
||||
if (_device.IsInvalid)
|
||||
Close();
|
||||
|
||||
return _device != null;
|
||||
}
|
||||
|
||||
public bool DeviceIOControl(Kernel32.IOControlCode ioControlCode, object inBuffer)
|
||||
{
|
||||
return _device != null && Kernel32.DeviceIoControl(_device, ioControlCode, inBuffer, inBuffer == null ? 0 : (uint)Marshal.SizeOf(inBuffer), null, 0, out uint _, IntPtr.Zero);
|
||||
}
|
||||
|
||||
public bool DeviceIOControl<T>(Kernel32.IOControlCode ioControlCode, object inBuffer, ref T outBuffer)
|
||||
{
|
||||
if (_device == null)
|
||||
return false;
|
||||
|
||||
object boxedOutBuffer = outBuffer;
|
||||
bool b = Kernel32.DeviceIoControl(_device,
|
||||
ioControlCode,
|
||||
inBuffer,
|
||||
inBuffer == null ? 0 : (uint)Marshal.SizeOf(inBuffer),
|
||||
boxedOutBuffer,
|
||||
(uint)Marshal.SizeOf(boxedOutBuffer),
|
||||
out uint _,
|
||||
IntPtr.Zero);
|
||||
|
||||
outBuffer = (T)boxedOutBuffer;
|
||||
return b;
|
||||
}
|
||||
|
||||
public bool DeviceIOControl<T>(Kernel32.IOControlCode ioControlCode, object inBuffer, ref T[] outBuffer)
|
||||
{
|
||||
if (_device == null)
|
||||
return false;
|
||||
|
||||
object boxedOutBuffer = outBuffer;
|
||||
bool b = Kernel32.DeviceIoControl(_device,
|
||||
ioControlCode,
|
||||
inBuffer,
|
||||
inBuffer == null ? 0 : (uint)Marshal.SizeOf(inBuffer),
|
||||
boxedOutBuffer,
|
||||
(uint)(Marshal.SizeOf(typeof(T)) * outBuffer.Length),
|
||||
out uint _,
|
||||
IntPtr.Zero);
|
||||
|
||||
outBuffer = (T[])boxedOutBuffer;
|
||||
return b;
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
if (_device != null)
|
||||
{
|
||||
_device.Close();
|
||||
_device.Dispose();
|
||||
_device = null;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Delete()
|
||||
{
|
||||
IntPtr manager = AdvApi32.OpenSCManager(null, null, AdvApi32.SC_MANAGER_ACCESS_MASK.SC_MANAGER_CONNECT);
|
||||
if (manager == IntPtr.Zero)
|
||||
return false;
|
||||
|
||||
IntPtr service = AdvApi32.OpenService(manager, _serviceName, AdvApi32.SERVICE_ACCESS_MASK.SERVICE_ALL_ACCESS);
|
||||
if (service == IntPtr.Zero)
|
||||
{
|
||||
AdvApi32.CloseServiceHandle(manager);
|
||||
return true;
|
||||
}
|
||||
|
||||
AdvApi32.SERVICE_STATUS status = new();
|
||||
AdvApi32.ControlService(service, AdvApi32.SERVICE_CONTROL.SERVICE_CONTROL_STOP, ref status);
|
||||
AdvApi32.DeleteService(service);
|
||||
AdvApi32.CloseServiceHandle(service);
|
||||
AdvApi32.CloseServiceHandle(manager);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -84,6 +84,18 @@ public class Kernel32
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert Win32 error code to HRESULT.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// HRESULT_FROM_WIN32 macro equivalent.
|
||||
/// </remarks>
|
||||
/// <param name="errorCode">Win32 error code</param>
|
||||
/// <returns>HRESULT</returns>
|
||||
internal static int HResultFromWin32(int errorCode) => errorCode <= 0
|
||||
? errorCode
|
||||
: (int)((errorCode & 0x0000FFFF) | 0x80070000);
|
||||
|
||||
internal static SafeFileHandle OpenDevice(string devicePath)
|
||||
{
|
||||
SafeFileHandle hDevice = CreateFile(devicePath, FileAccess.ReadWrite, FileShare.ReadWrite, IntPtr.Zero, FileMode.Open, FileAttributes.Normal, IntPtr.Zero);
|
||||
@@ -306,6 +318,20 @@ public class Kernel32
|
||||
out uint lpBytesReturned,
|
||||
IntPtr lpOverlapped);
|
||||
|
||||
[DllImport(DllName, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Auto, SetLastError = true)]
|
||||
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool DeviceIoControl
|
||||
(
|
||||
SafeFileHandle hDevice,
|
||||
uint dwIoControlCode,
|
||||
[In] byte[] lpInBuffer,
|
||||
uint nInBufferSize,
|
||||
[Out] byte[] lpOutBuffer,
|
||||
uint nOutBufferSize,
|
||||
ref uint lpBytesReturned,
|
||||
[In][Optional] IntPtr lpOverlapped);
|
||||
|
||||
[DllImport(DllName, CallingConvention = CallingConvention.Winapi)]
|
||||
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
|
||||
internal static extern IntPtr LocalAlloc(uint uFlags, ulong uBytes);
|
||||
|
||||
@@ -2,174 +2,98 @@
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using LibreHardwareMonitor.Interop;
|
||||
using Microsoft.Win32;
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
|
||||
namespace LibreHardwareMonitor.PawnIo;
|
||||
|
||||
public unsafe class PawnIo
|
||||
public class PawnIo
|
||||
{
|
||||
private IntPtr _handle;
|
||||
private const uint DEVICE_TYPE = 41394u << 16;
|
||||
private const uint IOCTL_PIO_LOAD_BINARY = 0x821 << 2;
|
||||
private const uint IOCTL_PIO_EXECUTE_FN = 0x841 << 2;
|
||||
private const int FN_NAME_LENGTH = 32;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the installation path of PawnIO, if it is installed on the system.
|
||||
/// </summary>
|
||||
public static string InstallPath
|
||||
private enum ControlCode : uint
|
||||
{
|
||||
get
|
||||
{
|
||||
if ((Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\PawnIO", "InstallLocation", null) ??
|
||||
Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\PawnIO", "Install_Dir", null) ??
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles) + Path.DirectorySeparatorChar + "PawnIO") is string
|
||||
{
|
||||
Length: > 0
|
||||
} path)
|
||||
{
|
||||
if (Directory.Exists(path))
|
||||
return path;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
LoadBinary = DEVICE_TYPE | IOCTL_PIO_LOAD_BINARY,
|
||||
Execute = DEVICE_TYPE | IOCTL_PIO_EXECUTE_FN
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether PawnIO is installed on the system.
|
||||
/// </summary>
|
||||
public static bool IsInstalled
|
||||
{
|
||||
get { return !string.IsNullOrEmpty(InstallPath); }
|
||||
}
|
||||
|
||||
public bool IsLoaded => _handle != IntPtr.Zero;
|
||||
|
||||
[DllImport("PawnIOLib", ExactSpelling = true, PreserveSig = false)]
|
||||
private static extern void pawnio_version(out uint version);
|
||||
|
||||
[DllImport("PawnIOLib", ExactSpelling = true, PreserveSig = false)]
|
||||
private static extern void pawnio_open(out IntPtr handle);
|
||||
|
||||
[DllImport("PawnIOLib", ExactSpelling = true, PreserveSig = false)]
|
||||
private static extern void pawnio_load(IntPtr handle, byte* blob, IntPtr size);
|
||||
|
||||
[DllImport("PawnIOLib", ExactSpelling = true, PreserveSig = false)]
|
||||
private static extern void pawnio_execute
|
||||
(
|
||||
IntPtr handle,
|
||||
[MarshalAs(UnmanagedType.LPStr)] string name,
|
||||
long[] inArray,
|
||||
IntPtr inSize,
|
||||
long[] outArray,
|
||||
IntPtr outSize,
|
||||
out IntPtr returnSize);
|
||||
|
||||
[DllImport("PawnIOLib", ExactSpelling = true, EntryPoint = "pawnio_execute")]
|
||||
private static extern int pawnio_execute_hr
|
||||
(
|
||||
IntPtr handle,
|
||||
[MarshalAs(UnmanagedType.LPStr)] string name,
|
||||
long[] inArray,
|
||||
IntPtr inSize,
|
||||
long[] outArray,
|
||||
IntPtr outSize,
|
||||
out IntPtr returnSize);
|
||||
|
||||
[DllImport("PawnIOLib", ExactSpelling = true, PreserveSig = false)]
|
||||
private static extern void pawnio_close(IntPtr handle);
|
||||
|
||||
private static void TryLoadLibrary()
|
||||
{
|
||||
try
|
||||
{
|
||||
// If already loaded, return immediately.
|
||||
pawnio_version(out uint _);
|
||||
return;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (IsInstalled)
|
||||
Kernel32.LoadLibrary(InstallPath + Path.DirectorySeparatorChar + "PawnIOLib");
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
public static bool IsInstalled => Version is not null;
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the version information for the underlying PawnIO library.
|
||||
/// Retrieves the version information for the installed PawnIO.
|
||||
/// </summary>
|
||||
public static Version Version()
|
||||
{
|
||||
try
|
||||
{
|
||||
TryLoadLibrary();
|
||||
pawnio_version(out uint version);
|
||||
public static Version Version { get; } = null;
|
||||
|
||||
return new Version((int)((version >> 16) & 0xFF),
|
||||
(int)((version >> 8) & 0xFF),
|
||||
(int)(version & 0xFF),
|
||||
0);
|
||||
}
|
||||
catch
|
||||
private readonly SafeFileHandle _handle;
|
||||
|
||||
static PawnIo()
|
||||
{
|
||||
using RegistryKey subKey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\PawnIO");
|
||||
|
||||
if (Version.TryParse(subKey?.GetValue("DisplayVersion") as string, out Version version))
|
||||
{
|
||||
return new Version();
|
||||
Version = version;
|
||||
}
|
||||
}
|
||||
|
||||
public void Close()
|
||||
private PawnIo(SafeFileHandle handle) => _handle = handle;
|
||||
|
||||
internal static PawnIo LoadModuleFromResource(Assembly assembly, string resourceName)
|
||||
{
|
||||
if (_handle != IntPtr.Zero)
|
||||
pawnio_close(_handle);
|
||||
SafeFileHandle handle = Kernel32.CreateFile(@"\\.\PawnIO", FileAccess.ReadWrite, FileShare.ReadWrite, IntPtr.Zero, FileMode.Open, FileAttributes.Normal, IntPtr.Zero);
|
||||
|
||||
if (handle.IsInvalid)
|
||||
return new(null);
|
||||
|
||||
uint read = 0;
|
||||
|
||||
using var stream = assembly.GetManifestResourceStream(resourceName);
|
||||
using MemoryStream memory = new();
|
||||
stream.CopyTo(memory);
|
||||
byte[] bin = memory.ToArray();
|
||||
|
||||
if (Kernel32.DeviceIoControl(handle, (uint)ControlCode.LoadBinary, bin, (uint)bin.Length, null, 0u, ref read, IntPtr.Zero))
|
||||
return new(handle);
|
||||
|
||||
return new(null);
|
||||
}
|
||||
|
||||
public static PawnIo LoadModuleFromResource(Assembly assembly, string resourceName)
|
||||
public bool IsLoaded => _handle is
|
||||
{
|
||||
var pawnIO = new PawnIo();
|
||||
IsInvalid: false,
|
||||
IsClosed: false
|
||||
};
|
||||
|
||||
using Stream s = assembly.GetManifestResourceStream(resourceName);
|
||||
|
||||
if (s is UnmanagedMemoryStream ums)
|
||||
{
|
||||
TryLoadLibrary();
|
||||
|
||||
try
|
||||
{
|
||||
pawnio_open(out IntPtr handle);
|
||||
pawnio_load(handle, ums.PositionPointer, (IntPtr)ums.Length);
|
||||
pawnIO._handle = handle;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// PawnIO is not available.
|
||||
}
|
||||
}
|
||||
|
||||
return pawnIO;
|
||||
}
|
||||
public void Close() => _handle.Close();
|
||||
|
||||
public long[] Execute(string name, long[] input, int outLength)
|
||||
{
|
||||
long[] result = new long[outLength];
|
||||
if (!IsLoaded)
|
||||
return [];
|
||||
|
||||
if (_handle == IntPtr.Zero)
|
||||
return result;
|
||||
uint read = 0;
|
||||
|
||||
pawnio_execute(_handle,
|
||||
name,
|
||||
input,
|
||||
(IntPtr)input.Length,
|
||||
result,
|
||||
(IntPtr)result.Length,
|
||||
out nint returnLength);
|
||||
byte[] output = new byte[outLength * sizeof(long)];
|
||||
byte[] inp = new byte[input.Length * sizeof(long) + FN_NAME_LENGTH];
|
||||
Buffer.BlockCopy(Encoding.ASCII.GetBytes(name), 0, inp, 0, Math.Min(FN_NAME_LENGTH - 1, name.Length));
|
||||
Buffer.BlockCopy(input, 0, inp, FN_NAME_LENGTH, input.Length * sizeof(long));
|
||||
|
||||
Array.Resize(ref result, (int)returnLength);
|
||||
return result;
|
||||
if (Kernel32.DeviceIoControl(_handle, (uint)ControlCode.Execute, inp, (uint)inp.Length, output, (uint)output.Length, ref read))
|
||||
{
|
||||
long[] outp = new long[read / sizeof(long)];
|
||||
Buffer.BlockCopy(output, 0, outp, 0, (int)read);
|
||||
return outp;
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
public int ExecuteHr(string name, long[] inBuffer, uint inSize, long[] outBuffer, uint outSize, out uint returnSize)
|
||||
@@ -180,16 +104,28 @@ public unsafe class PawnIo
|
||||
if (outBuffer.Length < outSize)
|
||||
throw new ArgumentOutOfRangeException(nameof(outSize));
|
||||
|
||||
if (_handle == IntPtr.Zero)
|
||||
if (!IsLoaded)
|
||||
{
|
||||
returnSize = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ret = pawnio_execute_hr(_handle, name, inBuffer, (IntPtr)inSize, outBuffer, (IntPtr)outSize, out IntPtr retSize);
|
||||
uint read = 0;
|
||||
|
||||
returnSize = (uint)retSize;
|
||||
byte[] output = new byte[outSize * sizeof(long)];
|
||||
byte[] inp = new byte[inSize * sizeof(long) + FN_NAME_LENGTH];
|
||||
Buffer.BlockCopy(Encoding.ASCII.GetBytes(name), 0, inp, 0, Math.Min(FN_NAME_LENGTH - 1, name.Length));
|
||||
Buffer.BlockCopy(inBuffer, 0, inp, FN_NAME_LENGTH, inBuffer.Length * sizeof(long));
|
||||
|
||||
return ret;
|
||||
if (Kernel32.DeviceIoControl(_handle, (uint)ControlCode.Execute, inp, (uint)inp.Length, output, (uint)output.Length, ref read))
|
||||
{
|
||||
Buffer.BlockCopy(output, 0, outBuffer, 0, Math.Min((int)read, outBuffer.Length * sizeof(long)));
|
||||
returnSize = read / sizeof(long);
|
||||
return 0;
|
||||
}
|
||||
|
||||
returnSize = 0;
|
||||
|
||||
return Kernel32.HResultFromWin32(Marshal.GetLastWin32Error());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user