198 lines
7.3 KiB
C#
198 lines
7.3 KiB
C#
#pragma warning disable CA1416
|
|
|
|
using Microsoft.Win32;
|
|
using System;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text;
|
|
|
|
namespace LANCommander.SDK.Utilities;
|
|
|
|
/// <summary>
|
|
/// Parses and imports a Windows “.reg” file into the live registry.
|
|
/// Supports files encoded as UTF-8 (with BOM) or ANSI.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// - Handles key sections ([HIVE\Path\…]) and value lines ("Name"=…, @=…).
|
|
/// - Recognizes REG_SZ, REG_EXPAND_SZ, REG_DWORD, REG_QWORD, REG_MULTI_SZ, REG_BINARY.
|
|
/// - Ignores security/ACLs; only writes values.
|
|
/// </remarks>
|
|
/// <example>
|
|
/// // 1) Import directly from a file stream (UTF-8 BOM is auto-detected):
|
|
/// var importer = new RegistryImportUtility();
|
|
/// using var fs = File.OpenRead(@"C:\temp\export.reg");
|
|
/// importer.Import(fs);
|
|
///
|
|
/// // 2) Or read into a string and import:
|
|
/// string content = File.ReadAllText(@"C:\temp\export.reg", Encoding.UTF8);
|
|
/// importer.ImportFromString(content);
|
|
/// </example>
|
|
public class RegistryImportUtility
|
|
{
|
|
/// <summary>
|
|
/// Reads the .reg data from <paramref name="regStream"/>, detects a UTF-8 BOM,
|
|
/// and imports all keys & values into the registry.
|
|
/// </summary>
|
|
/// <param name="regStream">Stream containing .reg text (UTF-8 or ANSI).</param>
|
|
public void Import(Stream regStream)
|
|
{
|
|
// detectEncodingFromByteOrderMarks = true will strip BOM for us
|
|
using var reader = new StreamReader(regStream, Encoding.UTF8, detectEncodingFromByteOrderMarks: true);
|
|
string content = reader.ReadToEnd();
|
|
ImportFromString(content);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parses the raw .reg-format text and writes all keys & values.
|
|
/// </summary>
|
|
/// <param name="regFileContent">The entire .reg file contents as a string.</param>
|
|
/// <remarks>Takes the full .reg text in a string (no encoding concern here).</remarks>
|
|
public void ImportFromString(string regFileContent)
|
|
{
|
|
using var reader = new StringReader(regFileContent);
|
|
string? currentFullPath = null;
|
|
RegistryKey? currentKey = null;
|
|
|
|
string? line;
|
|
while ((line = reader.ReadLine()) != null)
|
|
{
|
|
line = line.Trim();
|
|
if (line.Length == 0 || line.StartsWith(";"))
|
|
continue;
|
|
|
|
// Skip header
|
|
if (line.StartsWith("Windows Registry Editor Version", StringComparison.OrdinalIgnoreCase))
|
|
continue;
|
|
if (line.Equals("REGEDIT4", StringComparison.OrdinalIgnoreCase))
|
|
continue;
|
|
|
|
// New section: [HIVE\SubKey\…]
|
|
if (line.StartsWith("[") && line.EndsWith("]"))
|
|
{
|
|
// close previous
|
|
currentKey?.Dispose();
|
|
currentFullPath = line[1..^1];
|
|
var (hive, subKey) = ParseHive(currentFullPath);
|
|
currentKey = hive.CreateSubKey(subKey, writable: true);
|
|
continue;
|
|
}
|
|
|
|
// Value line: "Name"=type:data or @=...
|
|
if (currentKey == null)
|
|
throw new InvalidOperationException("Value outside of any key section.");
|
|
|
|
var eq = line.IndexOf('=');
|
|
if (eq < 0)
|
|
continue;
|
|
|
|
var nameToken = line[..eq].Trim();
|
|
var dataToken = line[(eq + 1)..].Trim();
|
|
var valueName = nameToken == "@" ? "" : Unquote(nameToken);
|
|
|
|
// Dispatch by prefix
|
|
if (dataToken.StartsWith("\""))
|
|
{
|
|
// simple string
|
|
var s = Unquote(dataToken);
|
|
currentKey.SetValue(valueName, UnescapeString(s), RegistryValueKind.String);
|
|
}
|
|
else if (dataToken.StartsWith("dword:", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
var hex = dataToken["dword:".Length..];
|
|
var d = Convert.ToUInt32(hex, 16);
|
|
currentKey.SetValue(valueName, (int)d, RegistryValueKind.DWord);
|
|
}
|
|
else if (dataToken.StartsWith("hex(", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
// e.g. hex(2):00,FF,00,… or hex(b):… or hex(7):…
|
|
var kindEnd = dataToken.IndexOf(')');
|
|
var kindId = dataToken["hex(".Length..kindEnd];
|
|
var payload = dataToken[(kindEnd + 2)..]; // skip "):"
|
|
var bytes = ParseHex(payload);
|
|
|
|
var rvk = kindId switch
|
|
{
|
|
"0" => RegistryValueKind.None,
|
|
"1" => RegistryValueKind.String,
|
|
"2" => RegistryValueKind.ExpandString,
|
|
"3" => RegistryValueKind.Binary,
|
|
"7" => RegistryValueKind.MultiString,
|
|
"b" => RegistryValueKind.QWord,
|
|
_ => RegistryValueKind.Binary
|
|
};
|
|
|
|
object finalData = rvk switch
|
|
{
|
|
RegistryValueKind.ExpandString => Encoding.Unicode.GetString(bytes).TrimEnd('\0'),
|
|
RegistryValueKind.MultiString => ParseMultiString(bytes),
|
|
RegistryValueKind.QWord => BitConverter.ToUInt64(bytes, 0),
|
|
_ => bytes
|
|
};
|
|
|
|
currentKey.SetValue(valueName, finalData, rvk);
|
|
}
|
|
else if (dataToken.StartsWith("hex:", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
// raw binary
|
|
var payload = dataToken["hex:".Length..];
|
|
var bytes = ParseHex(payload);
|
|
currentKey.SetValue(valueName, bytes, RegistryValueKind.Binary);
|
|
}
|
|
else
|
|
{
|
|
// unknown, skip
|
|
}
|
|
}
|
|
|
|
currentKey?.Dispose();
|
|
}
|
|
|
|
private static (RegistryKey hive, string subKey) ParseHive(string full)
|
|
{
|
|
var parts = full.Split(new[] { '\\' }, 2);
|
|
var root = parts[0].ToUpperInvariant().Trim().Trim(':');
|
|
var tail = parts.Length > 1 ? parts[1] : "";
|
|
|
|
return root switch
|
|
{
|
|
"HKLM" or "HKEY_LOCAL_MACHINE" => (Registry.LocalMachine, tail),
|
|
"HKCU" or "HKEY_CURRENT_USER" => (Registry.CurrentUser, tail),
|
|
"HKCR" or "HKEY_CLASSES_ROOT" => (Registry.ClassesRoot, tail),
|
|
"HKU" or "HKEY_USERS" => (Registry.Users, tail),
|
|
"HKCC" or "HKEY_CURRENT_CONFIG" => (Registry.CurrentConfig, tail),
|
|
_ => throw new ArgumentException($"Unknown hive: {root}")
|
|
};
|
|
}
|
|
|
|
private static string Unquote(string s) =>
|
|
s.Length >= 2 && s[0] == '"' && s[^1] == '"'
|
|
? s[1..^1]
|
|
: s;
|
|
|
|
private static string UnescapeString(string s) =>
|
|
s.Replace(@"\\", @"\").Replace("\\\"", "\"");
|
|
|
|
private static byte[] ParseHex(string hexData)
|
|
{
|
|
// split by commas, remove any trailing commas or backslashes
|
|
var tokens = hexData
|
|
.TrimEnd('\\')
|
|
.Split(',')
|
|
.Where(tok => tok.Length > 0)
|
|
.ToArray();
|
|
|
|
var bytes = new byte[tokens.Length];
|
|
for (int i = 0; i < tokens.Length; i++)
|
|
bytes[i] = Convert.ToByte(tokens[i], 16);
|
|
return bytes;
|
|
}
|
|
|
|
private static string[] ParseMultiString(byte[] raw)
|
|
{
|
|
// UTF-8, strings nul-terminated, ends with double null
|
|
var all = Encoding.UTF8.GetString(raw);
|
|
return all
|
|
.TrimEnd('\0')
|
|
.Split('\0', StringSplitOptions.RemoveEmptyEntries);
|
|
}
|
|
}
|