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:
Teemu Korhonen
2025-09-29 16:41:57 +02:00
committed by GitHub
co-authored by PhyxionNL
parent 79a043e5bf
commit c56cf3f042
4 changed files with 103 additions and 325 deletions
+1 -1
View File
@@ -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);
+76 -140
View File
@@ -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());
}
}