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;
}
}
}