From c56cf3f04287b31293f52f75b3fe0aa6fb4d82cc Mon Sep 17 00:00:00 2001 From: Teemu Korhonen <80648513+ts-korhonen@users.noreply.github.com> Date: Mon, 29 Sep 2025 17:41:57 +0300 Subject: [PATCH] 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> --- LibreHardwareMonitor/UI/MainForm.cs | 2 +- .../Hardware/KernelDriver.cs | 184 --------------- LibreHardwareMonitorLib/Interop/Kernel32.cs | 26 +++ LibreHardwareMonitorLib/PawnIo/PawnIo.cs | 216 ++++++------------ 4 files changed, 103 insertions(+), 325 deletions(-) delete mode 100644 LibreHardwareMonitorLib/Hardware/KernelDriver.cs diff --git a/LibreHardwareMonitor/UI/MainForm.cs b/LibreHardwareMonitor/UI/MainForm.cs index 5249f98..bd9e545 100644 --- a/LibreHardwareMonitor/UI/MainForm.cs +++ b/LibreHardwareMonitor/UI/MainForm.cs @@ -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) diff --git a/LibreHardwareMonitorLib/Hardware/KernelDriver.cs b/LibreHardwareMonitorLib/Hardware/KernelDriver.cs deleted file mode 100644 index 98cb752..0000000 --- a/LibreHardwareMonitorLib/Hardware/KernelDriver.cs +++ /dev/null @@ -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 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(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(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; - } -} diff --git a/LibreHardwareMonitorLib/Interop/Kernel32.cs b/LibreHardwareMonitorLib/Interop/Kernel32.cs index cd2e7a7..f0c179c 100644 --- a/LibreHardwareMonitorLib/Interop/Kernel32.cs +++ b/LibreHardwareMonitorLib/Interop/Kernel32.cs @@ -84,6 +84,18 @@ public class Kernel32 return result; } + /// + /// Convert Win32 error code to HRESULT. + /// + /// + /// HRESULT_FROM_WIN32 macro equivalent. + /// + /// Win32 error code + /// HRESULT + 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); diff --git a/LibreHardwareMonitorLib/PawnIo/PawnIo.cs b/LibreHardwareMonitorLib/PawnIo/PawnIo.cs index d8f0d3b..fbd4683 100644 --- a/LibreHardwareMonitorLib/PawnIo/PawnIo.cs +++ b/LibreHardwareMonitorLib/PawnIo/PawnIo.cs @@ -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; - /// - /// Gets the installation path of PawnIO, if it is installed on the system. - /// - 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 } /// /// Gets a value indicating whether PawnIO is installed on the system. /// - 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; /// - /// Retrieves the version information for the underlying PawnIO library. + /// Retrieves the version information for the installed PawnIO. /// - 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()); } }