using System; namespace ADOFAIRenderer.Util.OS; /// /// Read-only access to HKEY_LOCAL_MACHINE (64-bit registry view) through reflection. /// /// The mod targets netstandard2.1, which does not expose Microsoft.Win32.RegistryKey; the game runtime (Mono) /// provides it in its core library. The types and methods are looked up once in the static constructor and invoked through /// reflection. /// /// /// Intended for Windows. On other platforms the result depends on the runtime's registry support; when the types or methods /// are not found, or a call throws, every method returns null. /// All failures (missing key, access denied, reflection errors) are caught and reported as null; nothing is logged. /// Stateless and safe to call from any thread; each call opens and closes its own keys. /// /// internal static class RegistryUtil { // Reflected Microsoft.Win32 types; null when the runtime does not provide them. private static readonly Type? RegistryKeyType; private static readonly Type? RegistryHiveType; private static readonly Type? RegistryViewType; // RegistryKey.OpenBaseKey(RegistryHive, RegistryView) (static), OpenSubKey(string) (read-only open), // GetSubKeyNames() and GetValue(string). private static readonly System.Reflection.MethodInfo? OpenBaseKeyMethod; private static readonly System.Reflection.MethodInfo? OpenSubKeyMethod; private static readonly System.Reflection.MethodInfo? GetSubKeyNamesMethod; private static readonly System.Reflection.MethodInfo? GetValueMethod; /// /// Resolves the registry types by name (, then the core library that defines /// ) and their methods. Leaves the fields null on any failure. /// static RegistryUtil() { try { RegistryKeyType = Type.GetType("Microsoft.Win32.RegistryKey"); RegistryHiveType = Type.GetType("Microsoft.Win32.RegistryHive"); RegistryViewType = Type.GetType("Microsoft.Win32.RegistryView"); if (RegistryKeyType == null || RegistryHiveType == null || RegistryViewType == null) { var mscorlib = typeof(object).Assembly; RegistryKeyType = mscorlib.GetType("Microsoft.Win32.RegistryKey"); RegistryHiveType = mscorlib.GetType("Microsoft.Win32.RegistryHive"); RegistryViewType = mscorlib.GetType("Microsoft.Win32.RegistryView"); } if (RegistryKeyType != null) { OpenBaseKeyMethod = RegistryKeyType.GetMethod("OpenBaseKey", new[] { RegistryHiveType!, RegistryViewType! }); OpenSubKeyMethod = RegistryKeyType.GetMethod("OpenSubKey", new[] { typeof(string) }); GetSubKeyNamesMethod = RegistryKeyType.GetMethod("GetSubKeyNames"); GetValueMethod = RegistryKeyType.GetMethod("GetValue", new[] { typeof(string) }); } } catch { // ignored } } /// Closes a reflected RegistryKey (Dispose, or Close when it is not IDisposable). Ignores null and errors. /// RegistryKey instance returned by a reflected call, or null. private static void CloseKey(object? key) { if (key == null || RegistryKeyType == null) return; try { if (key is IDisposable disposable) { disposable.Dispose(); } else { var closeMethod = RegistryKeyType.GetMethod("Close"); closeMethod?.Invoke(key, null); } } catch { // ignored } } /// /// Returns the names of the direct subkeys of HKEY_LOCAL_MACHINE\<parentKeyPath> in the 64-bit registry view. /// /// Key path relative to HKEY_LOCAL_MACHINE, with backslash separators. /// Subkey names, or null when the key does not exist, cannot be opened, or the registry is unavailable. // ReSharper disable once InconsistentNaming public static string[]? GetHKLMSubKeyNames(string parentKeyPath) { if (RegistryKeyType == null || OpenBaseKeyMethod == null || OpenSubKeyMethod == null || GetSubKeyNamesMethod == null) return null; try { // RegistryHive.LocalMachine = 0x80000002 (-2147483646), RegistryView.Registry64 = 256. // On a 32-bit OS a Registry64 request returns the 32-bit view. // Reference: https://learn.microsoft.com/en-us/dotnet/api/microsoft.win32.registryhive // Reference: https://learn.microsoft.com/en-us/dotnet/api/microsoft.win32.registryview object hiveLocalMachine = Enum.ToObject(RegistryHiveType!, 0x80000002); object viewRegistry64 = Enum.ToObject(RegistryViewType!, 256); object? baseKey = OpenBaseKeyMethod.Invoke(null, new[] { hiveLocalMachine, viewRegistry64 }); if (baseKey == null) return null; object? key = OpenSubKeyMethod.Invoke(baseKey, new object[] { parentKeyPath }); if (key == null) { CloseKey(baseKey); return null; } string[]? subkeyNames = GetSubKeyNamesMethod.Invoke(key, null) as string[]; CloseKey(key); CloseKey(baseKey); return subkeyNames; } catch { return null; } } /// /// Reads a string value of HKEY_LOCAL_MACHINE\<keyPath> in the 64-bit registry view. /// /// Key path relative to HKEY_LOCAL_MACHINE, with backslash separators. /// Value name; an empty string reads the key's default value. /// /// The value when it exists and is a string (REG_SZ / REG_EXPAND_SZ); null when the key or value does not exist, the value /// has another type, or the registry is unavailable. /// // ReSharper disable once InconsistentNaming public static string? GetHKLMValue(string keyPath, string valueName) { if (RegistryKeyType == null || OpenBaseKeyMethod == null || OpenSubKeyMethod == null || GetValueMethod == null) return null; try { // Same hive and view constants as GetHKLMSubKeyNames. object hiveLocalMachine = Enum.ToObject(RegistryHiveType!, 0x80000002); object viewRegistry64 = Enum.ToObject(RegistryViewType!, 256); object? baseKey = OpenBaseKeyMethod.Invoke(null, new[] { hiveLocalMachine, viewRegistry64 }); if (baseKey == null) return null; object? key = OpenSubKeyMethod.Invoke(baseKey, new object[] { keyPath }); if (key == null) { CloseKey(baseKey); return null; } string? value = GetValueMethod.Invoke(key, new object[] { valueName }) as string; CloseKey(key); CloseKey(baseKey); return value; } catch { return null; } } }