/* * Thanks to gr0ss for the inspiration. * * https://github.com/gr0ss/RegistryMonitor * * 11/08/2019 */ using System; using System.ComponentModel; using System.IO; using System.Threading; using System.Runtime.InteropServices; using Microsoft.Win32; namespace BgTools.PlayerPrefsEditor { public class RegistryMonitor : IDisposable { #region P/Invoke [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] private static extern int RegOpenKeyEx(IntPtr hKey, string subKey, uint options, int samDesired, out IntPtr phkResult); [DllImport("advapi32.dll", SetLastError = true)] private static extern int RegNotifyChangeKeyValue(IntPtr hKey, bool bWatchSubtree, RegChangeNotifyFilter dwNotifyFilter, IntPtr hEvent, bool fAsynchronous); [DllImport("advapi32.dll", SetLastError = true)] private static extern int RegCloseKey(IntPtr hKey); private const int KEY_QUERY_VALUE = 0x0001; private const int KEY_NOTIFY = 0x0010; private const int STANDARD_RIGHTS_READ = 0x00020000; private static readonly IntPtr HKEY_CLASSES_ROOT = new IntPtr(unchecked((int)0x80000000)); private static readonly IntPtr HKEY_CURRENT_USER = new IntPtr(unchecked((int)0x80000001)); private static readonly IntPtr HKEY_LOCAL_MACHINE = new IntPtr(unchecked((int)0x80000002)); private static readonly IntPtr HKEY_USERS = new IntPtr(unchecked((int)0x80000003)); private static readonly IntPtr HKEY_PERFORMANCE_DATA = new IntPtr(unchecked((int)0x80000004)); private static readonly IntPtr HKEY_CURRENT_CONFIG = new IntPtr(unchecked((int)0x80000005)); private static readonly IntPtr HKEY_DYN_DATA = new IntPtr(unchecked((int)0x80000006)); #endregion #region Event handling /// /// Occurs when the specified registry key has changed. /// public event EventHandler RegChanged; /// /// Raises the event. /// /// ///

/// OnRegChanged is called when the specified registry key has changed. ///

/// /// When overriding in a derived class, be sure to call /// the base class's method. /// ///
protected virtual void OnRegChanged() { EventHandler handler = RegChanged; if (handler != null) handler(this, null); } /// /// Occurs when the access to the registry fails. /// public event ErrorEventHandler Error; /// /// Raises the event. /// /// The which occured while watching the registry. /// ///

/// OnError is called when an exception occurs while watching the registry. ///

/// /// When overriding in a derived class, be sure to call /// the base class's method. /// ///
protected virtual void OnError(Exception e) { ErrorEventHandler handler = Error; if (handler != null) handler(this, new ErrorEventArgs(e)); } #endregion #region Private member variables private IntPtr _registryHive; private string _registrySubName; private object _threadLock = new object(); private Thread _thread; private bool _disposed = false; private ManualResetEvent _eventTerminate = new ManualResetEvent(false); private RegChangeNotifyFilter _regFilter = RegChangeNotifyFilter.Key | RegChangeNotifyFilter.Attribute | RegChangeNotifyFilter.Value | RegChangeNotifyFilter.Security; #endregion /// /// Initializes a new instance of the class. /// /// The registry key to monitor. public RegistryMonitor(RegistryKey registryKey) { InitRegistryKey(registryKey.Name); } /// /// Initializes a new instance of the class. /// /// The name. public RegistryMonitor(string name) { if (name == null || name.Length == 0) throw new ArgumentNullException("name"); InitRegistryKey(name); } /// /// Initializes a new instance of the class. /// /// The registry hive. /// The sub key. public RegistryMonitor(RegistryHive registryHive, string subKey) { InitRegistryKey(registryHive, subKey); } /// /// Disposes this object. /// public void Dispose() { Stop(); _disposed = true; GC.SuppressFinalize(this); } /// /// Gets or sets the RegChangeNotifyFilter. /// public RegChangeNotifyFilter RegChangeNotifyFilter { get { return _regFilter; } set { lock (_threadLock) { if (IsMonitoring) throw new InvalidOperationException("Monitoring thread is already running"); _regFilter = value; } } } #region Initialization private void InitRegistryKey(RegistryHive hive, string name) { switch (hive) { case RegistryHive.ClassesRoot: _registryHive = HKEY_CLASSES_ROOT; break; case RegistryHive.CurrentConfig: _registryHive = HKEY_CURRENT_CONFIG; break; case RegistryHive.CurrentUser: _registryHive = HKEY_CURRENT_USER; break; case RegistryHive.DynData: _registryHive = HKEY_DYN_DATA; break; case RegistryHive.LocalMachine: _registryHive = HKEY_LOCAL_MACHINE; break; case RegistryHive.PerformanceData: _registryHive = HKEY_PERFORMANCE_DATA; break; case RegistryHive.Users: _registryHive = HKEY_USERS; break; default: throw new InvalidEnumArgumentException("hive", (int)hive, typeof(RegistryHive)); } _registrySubName = name; } private void InitRegistryKey(string name) { string[] nameParts = name.Split('\\'); switch (nameParts[0]) { case "HKEY_CLASSES_ROOT": case "HKCR": _registryHive = HKEY_CLASSES_ROOT; break; case "HKEY_CURRENT_USER": case "HKCU": _registryHive = HKEY_CURRENT_USER; break; case "HKEY_LOCAL_MACHINE": case "HKLM": _registryHive = HKEY_LOCAL_MACHINE; break; case "HKEY_USERS": _registryHive = HKEY_USERS; break; case "HKEY_CURRENT_CONFIG": _registryHive = HKEY_CURRENT_CONFIG; break; default: _registryHive = IntPtr.Zero; throw new ArgumentException("The registry hive '" + nameParts[0] + "' is not supported", "value"); } _registrySubName = String.Join("\\", nameParts, 1, nameParts.Length - 1); } #endregion /// /// true if this object is currently monitoring; /// otherwise, false. /// public bool IsMonitoring { get { return _thread != null; } } /// /// Start monitoring. /// public void Start() { if (_disposed) throw new ObjectDisposedException(null, "This instance is already disposed"); lock (_threadLock) { if (!IsMonitoring) { _eventTerminate.Reset(); _thread = new Thread(new ThreadStart(MonitorThread)) { IsBackground = true }; _thread.Start(); } } } /// /// Stops the monitoring thread. /// public void Stop() { if (_disposed) throw new ObjectDisposedException(null, "This instance is already disposed"); lock (_threadLock) { Thread thread = _thread; if (thread != null) { _eventTerminate.Set(); thread.Join(); } } } private void MonitorThread() { try { ThreadLoop(); } catch (Exception e) { OnError(e); } _thread = null; } private void ThreadLoop() { IntPtr registryKey; int result = RegOpenKeyEx(_registryHive, _registrySubName, 0, STANDARD_RIGHTS_READ | KEY_QUERY_VALUE | KEY_NOTIFY, out registryKey); if (result != 0) { throw new Win32Exception(result); } try { AutoResetEvent _eventNotify = new AutoResetEvent(false); WaitHandle[] waitHandles = new WaitHandle[] { _eventNotify, _eventTerminate }; while (!_eventTerminate.WaitOne(0, true)) { result = RegNotifyChangeKeyValue(registryKey, true, _regFilter, _eventNotify.SafeWaitHandle.DangerousGetHandle(), true); if (result != 0) { throw new Win32Exception(result); } if (WaitHandle.WaitAny(waitHandles) == 0) { OnRegChanged(); } } } finally { if (registryKey != IntPtr.Zero) { RegCloseKey(registryKey); } } } } /// /// Filter for notifications reported by . /// [Flags] public enum RegChangeNotifyFilter { /// Notify the caller if a subkey is added or deleted. Key = 1, /// Notify the caller of changes to the attributes of the key, /// such as the security descriptor information. Attribute = 2, /// Notify the caller of changes to a value of the key. This can /// include adding or deleting a value, or changing an existing value. Value = 4, /// Notify the caller of changes to the security descriptor /// of the key. Security = 8, } }