commit 2d88a5a10334bddf3bd0b8bc98744ea6f239ce3a (tree)
parent 0a412853aae9815eb663a88a8a2d37b91c614317
Author: Andrew Kelley <andrew@ziglang.org>
Date: Tue, 3 Mar 2026 18:26:12 +0100
Merge pull request 'Another dll dependency bites the dust (advapi32.dll)' (#31384) from squeek502/zig:delete-advapi32 into master
Reviewed-on: https://codeberg.org/ziglang/zig/pulls/31384
Reviewed-by: Andrew Kelley <andrew@ziglang.org>
Diffstat:
5 files changed, 696 insertions(+), 494 deletions(-)
diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig
@@ -15,7 +15,6 @@ const math = std.math;
const maxInt = std.math.maxInt;
const UnexpectedError = std.posix.UnexpectedError;
-pub const advapi32 = @import("windows/advapi32.zig");
pub const kernel32 = @import("windows/kernel32.zig");
pub const ntdll = @import("windows/ntdll.zig");
pub const ws2_32 = @import("windows/ws2_32.zig");
@@ -3506,6 +3505,16 @@ pub const GUID = extern struct {
}
return @as(GUID, @bitCast(bytes));
}
+
+ pub fn format(self: GUID, w: *std.Io.Writer) std.Io.Writer.Error!void {
+ return w.print("{{{x:0>8}-{x:0>4}-{x:0>4}-{x}-{x}}}", .{
+ self.Data1,
+ self.Data2,
+ self.Data3,
+ self.Data4[0..2],
+ self.Data4[2..8],
+ });
+ }
};
test GUID {
@@ -3518,6 +3527,16 @@ test GUID {
},
GUID.parse("{01234567-89AB-EF10-3254-7698badcfe91}"),
);
+ try std.testing.expectFmt(
+ "{01234567-89ab-ef10-3254-7698badcfe91}",
+ "{f}",
+ .{GUID.parse("{01234567-89AB-EF10-3254-7698badcfe91}")},
+ );
+ try std.testing.expectFmt(
+ "{00000001-0001-0001-0001-000000000001}",
+ "{f}",
+ .{GUID{ .Data1 = 1, .Data2 = 1, .Data3 = 1, .Data4 = [_]u8{ 0, 1, 0, 0, 0, 0, 0, 1 } }},
+ );
}
pub const COORD = extern struct {
@@ -3560,7 +3579,7 @@ pub const RTL_QUERY_REGISTRY_TABLE = extern struct {
Flags: ULONG,
Name: ?PWSTR,
EntryContext: ?*anyopaque,
- DefaultType: ULONG,
+ DefaultType: REG.ValueType,
DefaultData: ?*anyopaque,
DefaultLength: ULONG,
};
@@ -3625,34 +3644,120 @@ pub const RTL_QUERY_REGISTRY_DELETE = 0x00000040;
/// If the types do not match, the call fails.
pub const RTL_QUERY_REGISTRY_TYPECHECK = 0x00000100;
+/// REG_ is a crowded namespace with a lot of overlapping and unrelated
+/// defines in the Windows headers, so instead of strictly following the
+/// Windows headers names, extra namespaces are added here for clarity.
pub const REG = struct {
- /// No value type
- pub const NONE: ULONG = 0;
- /// Unicode nul terminated string
- pub const SZ: ULONG = 1;
- /// Unicode nul terminated string (with environment variable references)
- pub const EXPAND_SZ: ULONG = 2;
- /// Free form binary
- pub const BINARY: ULONG = 3;
- /// 32-bit number
- pub const DWORD: ULONG = 4;
- /// 32-bit number (same as REG_DWORD)
- pub const DWORD_LITTLE_ENDIAN: ULONG = 4;
- /// 32-bit number
- pub const DWORD_BIG_ENDIAN: ULONG = 5;
- /// Symbolic Link (unicode)
- pub const LINK: ULONG = 6;
- /// Multiple Unicode strings
- pub const MULTI_SZ: ULONG = 7;
- /// Resource list in the resource map
- pub const RESOURCE_LIST: ULONG = 8;
- /// Resource list in the hardware description
- pub const FULL_RESOURCE_DESCRIPTOR: ULONG = 9;
- pub const RESOURCE_REQUIREMENTS_LIST: ULONG = 10;
- /// 64-bit number
- pub const QWORD: ULONG = 11;
- /// 64-bit number (same as REG_QWORD)
- pub const QWORD_LITTLE_ENDIAN: ULONG = 11;
+ pub const ValueType = enum(ULONG) {
+ /// No value type
+ NONE = 0,
+ /// Unicode nul terminated string
+ SZ = 1,
+ /// Unicode nul terminated string (with environment variable references)
+ EXPAND_SZ = 2,
+ /// Free form binary
+ BINARY = 3,
+ /// 32-bit number
+ DWORD = 4,
+ /// 32-bit number
+ DWORD_BIG_ENDIAN = 5,
+ /// Symbolic Link (unicode)
+ LINK = 6,
+ /// Multiple Unicode strings
+ MULTI_SZ = 7,
+ /// Resource list in the resource map
+ RESOURCE_LIST = 8,
+ /// Resource list in the hardware description
+ FULL_RESOURCE_DESCRIPTOR = 9,
+ RESOURCE_REQUIREMENTS_LIST = 10,
+ /// 64-bit number
+ QWORD = 11,
+ _,
+
+ /// 32-bit number (same as REG_DWORD)
+ pub const DWORD_LITTLE_ENDIAN: ValueType = .DWORD;
+ /// 64-bit number (same as REG_QWORD)
+ pub const QWORD_LITTLE_ENDIAN: ValueType = .QWORD;
+ };
+
+ /// Used with NtOpenKeyEx, maybe others
+ pub const OpenOptions = packed struct(ULONG) {
+ Reserved0: u2 = 0,
+ /// Open for backup or restore
+ /// special access rules privilege required
+ BACKUP_RESTORE: bool = false,
+ /// Open symbolic link
+ OPEN_LINK: bool = false,
+ Reserved3: u28 = 0,
+ };
+
+ /// Used with NtLoadKeyEx, maybe others
+ pub const LoadOptions = packed struct(ULONG) {
+ /// Restore whole hive volatile
+ WHOLE_HIVE_VOLATILE: bool = false,
+ /// Unwind changes to last flush
+ REFRESH_HIVE: bool = false,
+ /// Never lazy flush this hive
+ NO_LAZY_FLUSH: bool = false,
+ /// Force the restore process even when we have open handles on subkeys
+ FORCE_RESTORE: bool = false,
+ /// Loads the hive visible to the calling process
+ APP_HIVE: bool = false,
+ /// Hive cannot be mounted by any other process while in use
+ PROCESS_PRIVATE: bool = false,
+ /// Starts Hive Journal
+ START_JOURNAL: bool = false,
+ /// Grow hive file in exact 4k increments
+ HIVE_EXACT_FILE_GROWTH: bool = false,
+ /// No RM is started for this hive (no transactions)
+ HIVE_NO_RM: bool = false,
+ /// Legacy single logging is used for this hive
+ HIVE_SINGLE_LOG: bool = false,
+ /// This hive might be used by the OS loader
+ BOOT_HIVE: bool = false,
+ /// Load the hive and return a handle to its root kcb
+ LOAD_HIVE_OPEN_HANDLE: bool = false,
+ /// Flush changes to primary hive file size as part of all flushes
+ FLUSH_HIVE_FILE_GROWTH: bool = false,
+ /// Open a hive's files in read-only mode
+ /// The same flag is used for REG_APP_HIVE_OPEN_READ_ONLY:
+ /// Open an app hive's files in read-only mode (if the hive was not previously loaded).
+ OPEN_READ_ONLY: bool = false,
+ /// Load the hive, but don't allow any modification of it
+ IMMUTABLE: bool = false,
+ /// Do not fall back to impersonating the caller if hive file access fails
+ NO_IMPERSONATION_FALLBACK: bool = false,
+ Reserved16: u16 = 0,
+ };
+};
+
+pub const KEY = struct {
+ pub const VALUE = struct {
+ /// https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/ne-wdm-_key_value_information_class
+ pub const INFORMATION_CLASS = enum(c_int) {
+ Basic = 0,
+ Full = 1,
+ Partial = 2,
+ FullAlign64 = 3,
+ PartialAlign64 = 4,
+ Layer = 5,
+ _,
+
+ pub const Max: @typeInfo(@This()).@"enum".tag_type = @typeInfo(@This()).@"enum".fields.len;
+ };
+
+ pub const PARTIAL_INFORMATION = extern struct {
+ TitleIndex: ULONG,
+ Type: REG.ValueType,
+ DataLength: ULONG,
+ Data: [0]UCHAR,
+
+ pub fn data(info: *const PARTIAL_INFORMATION) []const UCHAR {
+ const ptr: [*]const UCHAR = @ptrCast(&info.Data);
+ return ptr[0..info.DataLength];
+ }
+ };
+ };
};
pub const ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x4;
diff --git a/lib/std/os/windows/advapi32.zig b/lib/std/os/windows/advapi32.zig
@@ -1,67 +0,0 @@
-const std = @import("../../std.zig");
-const windows = std.os.windows;
-const BOOL = windows.BOOL;
-const DWORD = windows.DWORD;
-const HKEY = windows.HKEY;
-const BYTE = windows.BYTE;
-const LPCWSTR = windows.LPCWSTR;
-const LSTATUS = windows.LSTATUS;
-const REGSAM = windows.REGSAM;
-const ULONG = windows.ULONG;
-
-pub extern "advapi32" fn RegOpenKeyExW(
- hKey: HKEY,
- lpSubKey: LPCWSTR,
- ulOptions: DWORD,
- samDesired: REGSAM,
- phkResult: *HKEY,
-) callconv(.winapi) LSTATUS;
-
-pub extern "advapi32" fn RegQueryValueExW(
- hKey: HKEY,
- lpValueName: LPCWSTR,
- lpReserved: ?*DWORD,
- lpType: ?*DWORD,
- lpData: ?*BYTE,
- lpcbData: ?*DWORD,
-) callconv(.winapi) LSTATUS;
-
-pub extern "advapi32" fn RegCloseKey(hKey: HKEY) callconv(.winapi) LSTATUS;
-
-pub const RRF = struct {
- pub const RT_ANY: DWORD = 0x0000ffff;
-
- pub const RT_DWORD: DWORD = 0x00000018;
- pub const RT_QWORD: DWORD = 0x00000048;
-
- pub const RT_REG_BINARY: DWORD = 0x00000008;
- pub const RT_REG_DWORD: DWORD = 0x00000010;
- pub const RT_REG_EXPAND_SZ: DWORD = 0x00000004;
- pub const RT_REG_MULTI_SZ: DWORD = 0x00000020;
- pub const RT_REG_NONE: DWORD = 0x00000001;
- pub const RT_REG_QWORD: DWORD = 0x00000040;
- pub const RT_REG_SZ: DWORD = 0x00000002;
-
- pub const NOEXPAND: DWORD = 0x10000000;
- pub const ZEROONFAILURE: DWORD = 0x20000000;
- pub const SUBKEY_WOW6464KEY: DWORD = 0x00010000;
- pub const SUBKEY_WOW6432KEY: DWORD = 0x00020000;
-};
-
-pub extern "advapi32" fn RegGetValueW(
- hkey: HKEY,
- lpSubKey: LPCWSTR,
- lpValue: LPCWSTR,
- dwFlags: DWORD,
- pdwType: ?*DWORD,
- pvData: ?*anyopaque,
- pcbData: ?*DWORD,
-) callconv(.winapi) LSTATUS;
-
-pub extern "advapi32" fn RegLoadAppKeyW(
- lpFile: LPCWSTR,
- phkResult: *HKEY,
- samDesired: REGSAM,
- dwOptions: DWORD,
- reserved: DWORD,
-) callconv(.winapi) LSTATUS;
diff --git a/lib/std/os/windows/ntdll.zig b/lib/std/os/windows/ntdll.zig
@@ -22,6 +22,7 @@ const HANDLE = windows.HANDLE;
const HEAP = windows.HEAP;
const IO_APC_ROUTINE = windows.IO_APC_ROUTINE;
const IO_STATUS_BLOCK = windows.IO_STATUS_BLOCK;
+const KEY = windows.KEY;
const KNONVOLATILE_CONTEXT_POINTERS = windows.KNONVOLATILE_CONTEXT_POINTERS;
const LARGE_INTEGER = windows.LARGE_INTEGER;
const LDR = windows.LDR;
@@ -37,6 +38,7 @@ const PCWSTR = windows.PCWSTR;
const PROCESS = windows.PROCESS;
const PVOID = windows.PVOID;
const PWSTR = windows.PWSTR;
+const REG = windows.REG;
const RTL_OSVERSIONINFOW = windows.RTL_OSVERSIONINFOW;
const RTL_QUERY_REGISTRY_TABLE = windows.RTL_QUERY_REGISTRY_TABLE;
const RUNTIME_FUNCTION = windows.RUNTIME_FUNCTION;
@@ -724,3 +726,36 @@ pub extern "ntdll" fn RtlWakeConditionVariable(
pub extern "ntdll" fn RtlWakeAllConditionVariable(
ConditionVariable: *CONDITION_VARIABLE,
) callconv(.winapi) void;
+
+pub extern "ntdll" fn NtOpenKeyEx(
+ KeyHandle: *HANDLE,
+ DesiredAccess: ACCESS_MASK,
+ ObjectAttributes: *const OBJECT.ATTRIBUTES,
+ OpenOptions: REG.OpenOptions,
+) callconv(.winapi) NTSTATUS;
+pub extern "ntdll" fn RtlOpenCurrentUser(
+ DesiredAccess: ACCESS_MASK,
+ CurrentUserKey: *HANDLE,
+) callconv(.winapi) NTSTATUS;
+pub extern "ntdll" fn NtQueryValueKey(
+ KeyHandle: HANDLE,
+ ValueName: *const UNICODE_STRING,
+ KeyValueInformationClass: KEY.VALUE.INFORMATION_CLASS,
+ KeyValueInformation: *anyopaque,
+ /// Length of KeyValueInformation buffer in bytes
+ Length: ULONG,
+ /// On STATUS_SUCCESS, contains the length of the populated portion of the
+ /// provided buffer. On STATUS_BUFFER_OVERFLOW or STATUS_BUFFER_TOO_SMALL,
+ /// contains the minimum `Length` value that would be required to hold the information.
+ ResultLength: *ULONG,
+) callconv(.winapi) NTSTATUS;
+pub extern "ntdll" fn NtLoadKeyEx(
+ TargetKey: *const OBJECT.ATTRIBUTES,
+ SourceFile: *const OBJECT.ATTRIBUTES,
+ Flags: REG.LoadOptions,
+ TrustClassKey: ?HANDLE,
+ Event: ?HANDLE,
+ DesiredAccess: ACCESS_MASK,
+ RootHandle: ?*HANDLE,
+ Reserved: ?*anyopaque,
+) callconv(.winapi) NTSTATUS;
diff --git a/lib/std/zig/WindowsSdk.zig b/lib/std/zig/WindowsSdk.zig
@@ -7,19 +7,20 @@ const Dir = std.Io.Dir;
const Writer = std.Io.Writer;
const Allocator = std.mem.Allocator;
const Environ = std.process.Environ;
+const L = std.unicode.wtf8ToWtf16LeStringLiteral;
+const is_32_bit = @bitSizeOf(usize) == 32;
windows10sdk: ?Installation,
windows81sdk: ?Installation,
msvc_lib_dir: ?[]const u8,
const windows = std.os.windows;
-const RRF = windows.advapi32.RRF;
-const windows_kits_reg_key = "SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots";
+const windows_kits_reg_key = "Microsoft\\Windows Kits\\Installed Roots";
// https://learn.microsoft.com/en-us/windows/win32/msi/productversion
const version_major_minor_max_length = "255.255".len;
-// note(bratishkaerik): i think ProductVersion in registry (created by Visual Studio installer) also follows this rule
+// ProductVersion in registry (created by Visual Studio installer) probably also follows this rule
const product_version_max_length = version_major_minor_max_length + ".65535".len;
/// Find path and version of Windows 10 SDK and Windows 8.1 SDK, and find path to MSVC's `lib/` directory.
@@ -33,13 +34,16 @@ pub fn find(
) error{ OutOfMemory, NotFound, PathTooLong }!WindowsSdk {
if (builtin.os.tag != .windows) return error.NotFound;
- //note(dimenus): If this key doesn't exist, neither the Win 8 SDK nor the Win 10 SDK is installed
- const roots_key = RegistryWtf8.openKey(windows.HKEY_LOCAL_MACHINE, windows_kits_reg_key, .{ .wow64_32 = true }) catch |err| switch (err) {
+ var registry: Registry = .{};
+ defer registry.deinit();
+
+ // If this key doesn't exist, neither the Win 8 SDK nor the Win 10 SDK is installed
+ const roots_key = registry.openSoftwareKey(.{ .root = .local_machine, .wow64 = .wow64_32 }, L(windows_kits_reg_key)) catch |err| switch (err) {
error.KeyNotFound => return error.NotFound,
};
- defer roots_key.closeKey();
+ defer roots_key.close();
- const windows10sdk = Installation.find(gpa, io, roots_key, "KitsRoot10", "", "v10.0") catch |err| switch (err) {
+ const windows10sdk = Installation.find(gpa, io, ®istry, roots_key, L("KitsRoot10"), "", L("v10.0")) catch |err| switch (err) {
error.InstallationNotFound => null,
error.PathTooLong => null,
error.VersionTooLong => null,
@@ -47,7 +51,7 @@ pub fn find(
};
errdefer if (windows10sdk) |*w| w.free(gpa);
- const windows81sdk = Installation.find(gpa, io, roots_key, "KitsRoot81", "winver", "v8.1") catch |err| switch (err) {
+ const windows81sdk = Installation.find(gpa, io, ®istry, roots_key, L("KitsRoot81"), "winver", L("v8.1")) catch |err| switch (err) {
error.InstallationNotFound => null,
error.PathTooLong => null,
error.VersionTooLong => null,
@@ -55,7 +59,7 @@ pub fn find(
};
errdefer if (windows81sdk) |*w| w.free(gpa);
- const msvc_lib_dir: ?[]const u8 = MsvcLibDir.find(gpa, io, arch, environ_map) catch |err| switch (err) {
+ const msvc_lib_dir: ?[]const u8 = MsvcLibDir.find(gpa, io, ®istry, arch, environ_map) catch |err| switch (err) {
error.MsvcLibDirNotFound => null,
error.OutOfMemory => return error.OutOfMemory,
};
@@ -149,274 +153,306 @@ fn iterateAndFilterByVersion(
return dirs.toOwnedSlice();
}
-const OpenOptions = struct {
- /// Sets the KEY_WOW64_32KEY access flag.
- /// https://learn.microsoft.com/en-us/windows/win32/winprog64/accessing-an-alternate-registry-view
- wow64_32: bool = false,
-};
-
-const RegistryWtf8 = struct {
- key: windows.HKEY,
-
- /// Assert that `key` is valid WTF-8 string
- pub fn openKey(hkey: windows.HKEY, key: []const u8, options: OpenOptions) error{KeyNotFound}!RegistryWtf8 {
- const key_wtf16le: [:0]const u16 = key_wtf16le: {
- var key_wtf16le_buf: [RegistryWtf16Le.key_name_max_len]u16 = undefined;
- const key_wtf16le_len: usize = std.unicode.wtf8ToWtf16Le(key_wtf16le_buf[0..], key) catch |err| switch (err) {
- error.InvalidWtf8 => unreachable,
- };
- key_wtf16le_buf[key_wtf16le_len] = 0;
- break :key_wtf16le key_wtf16le_buf[0..key_wtf16le_len :0];
- };
-
- const registry_wtf16le = try RegistryWtf16Le.openKey(hkey, key_wtf16le, options);
- return .{ .key = registry_wtf16le.key };
- }
-
- /// Closes key, after that usage is invalid
- pub fn closeKey(reg: RegistryWtf8) void {
- const return_code_int: windows.HRESULT = windows.advapi32.RegCloseKey(reg.key);
- const return_code: windows.Win32Error = @enumFromInt(return_code_int);
- switch (return_code) {
- .SUCCESS => {},
- else => {},
+/// Not a general purpose implementation of an ntdll-based Registry API.
+/// Only intended to support the particular calls necessary for the purposes of finding
+/// the SDK/MSVC installation paths.
+///
+/// The advapi32 APIs internally open and cache `\Registry\Machine` and the current user
+/// key when HKEY_LOCAL_MACHINE (HKLM) and HKEY_CURRENT_USER (HKCU) are passed, and also
+/// rewrite key path values to go through WOW6432Node when appropriate.
+///
+/// For example, when opening `Software\Foo` relative to `HKEY_LOCAL_MACHINE` with
+/// the WOW64_32KEY option set, that will end up as a call to NtLoadKeyEx with the path
+/// rewritten to `Software\WOW6432Node\Foo` relative to a cached `\REGISTRY\Machine`
+/// key.
+///
+/// For our purposes, we really only care about 4 potential variations of the `Software` key:
+/// - Relative to HKLM, no redirection through WOW6432Node
+/// - Relative to HKLM, redirected through WOW6432Node
+/// - Relative to HKCU, no redirection through WOW6432Node
+/// - Relative to HKCU, redirected through WOW6432Node
+/// (e.g. all the values we care about are within one of those `Software` keys)
+///
+/// So, we cache those variants of the Software keys instead of HKLM/HKCU and treat them
+/// as the "root" keys that the user can specify, which in turn (1) allows all provided key
+/// paths to be agnostic to WOW6432Node, (2) avoids the need for internal path rewriting,
+/// and (3) works correctly on 32-bit targets without any special support.
+///
+/// For example, instead of an advapi32 call with `Software\Foo` relative to
+/// `HKEY_LOCAL_MACHINE` which may get rewritten to `Software\WOW6432Node\Foo`,
+/// the equivalent is now a call to open `Foo` relative to some Software key variant.
+const Registry = struct {
+ cache: Cache = .{},
+
+ pub fn deinit(self: Registry) void {
+ if (!is_32_bit) {
+ if (self.cache.hklm_software_foreign) |key| windows.CloseHandle(key);
+ if (self.cache.hkcu_software_foreign) |key| windows.CloseHandle(key);
}
+ if (self.cache.hklm_software_native) |key| windows.CloseHandle(key);
+ if (self.cache.hkcu_software_native) |key| windows.CloseHandle(key);
+ if (self.cache.hkcu) |key| windows.CloseHandle(key);
}
- /// Get string from registry.
- /// Caller owns result.
- pub fn getString(reg: RegistryWtf8, gpa: Allocator, subkey: []const u8, value_name: []const u8) error{ OutOfMemory, ValueNameNotFound, NotAString, StringNotFound }![]u8 {
- const subkey_wtf16le: [:0]const u16 = subkey_wtf16le: {
- var subkey_wtf16le_buf: [RegistryWtf16Le.key_name_max_len]u16 = undefined;
- const subkey_wtf16le_len: usize = std.unicode.wtf8ToWtf16Le(subkey_wtf16le_buf[0..], subkey) catch unreachable;
- subkey_wtf16le_buf[subkey_wtf16le_len] = 0;
- break :subkey_wtf16le subkey_wtf16le_buf[0..subkey_wtf16le_len :0];
- };
-
- const value_name_wtf16le: [:0]const u16 = value_name_wtf16le: {
- var value_name_wtf16le_buf: [RegistryWtf16Le.value_name_max_len]u16 = undefined;
- const value_name_wtf16le_len: usize = std.unicode.wtf8ToWtf16Le(value_name_wtf16le_buf[0..], value_name) catch unreachable;
- value_name_wtf16le_buf[value_name_wtf16le_len] = 0;
- break :value_name_wtf16le value_name_wtf16le_buf[0..value_name_wtf16le_len :0];
- };
-
- const registry_wtf16le: RegistryWtf16Le = .{ .key = reg.key };
- const value_wtf16le = try registry_wtf16le.getString(gpa, subkey_wtf16le, value_name_wtf16le);
- defer gpa.free(value_wtf16le);
-
- const value_wtf8: []u8 = try std.unicode.wtf16LeToWtf8Alloc(gpa, value_wtf16le);
- errdefer gpa.free(value_wtf8);
-
- return value_wtf8;
- }
-
- /// Get DWORD (u32) from registry.
- pub fn getDword(reg: RegistryWtf8, subkey: []const u8, value_name: []const u8) error{ ValueNameNotFound, NotADword, DwordTooLong, DwordNotFound }!u32 {
- const subkey_wtf16le: [:0]const u16 = subkey_wtf16le: {
- var subkey_wtf16le_buf: [RegistryWtf16Le.key_name_max_len]u16 = undefined;
- const subkey_wtf16le_len: usize = std.unicode.wtf8ToWtf16Le(subkey_wtf16le_buf[0..], subkey) catch unreachable;
- subkey_wtf16le_buf[subkey_wtf16le_len] = 0;
- break :subkey_wtf16le subkey_wtf16le_buf[0..subkey_wtf16le_len :0];
- };
+ const Cache = struct {
+ hklm_software_foreign: if (is_32_bit) void else ?windows.HANDLE = if (is_32_bit) {} else null,
+ hkcu_software_foreign: if (is_32_bit) void else ?windows.HANDLE = if (is_32_bit) {} else null,
+ hklm_software_native: ?windows.HANDLE = null,
+ hkcu_software_native: ?windows.HANDLE = null,
+ hkcu: ?windows.HANDLE = null,
+
+ fn getSoftware(cache: *const Cache, variant: Software) ?windows.HANDLE {
+ if (!is_32_bit and variant.wow64 == .wow64_32) {
+ return switch (variant.root) {
+ .local_machine => cache.hklm_software_foreign,
+ .current_user => cache.hkcu_software_foreign,
+ };
+ }
+ return switch (variant.root) {
+ .local_machine => cache.hklm_software_native,
+ .current_user => cache.hkcu_software_native,
+ };
+ }
+ };
- const value_name_wtf16le: [:0]const u16 = value_name_wtf16le: {
- var value_name_wtf16le_buf: [RegistryWtf16Le.value_name_max_len]u16 = undefined;
- const value_name_wtf16le_len: usize = std.unicode.wtf8ToWtf16Le(value_name_wtf16le_buf[0..], value_name) catch unreachable;
- value_name_wtf16le_buf[value_name_wtf16le_len] = 0;
- break :value_name_wtf16le value_name_wtf16le_buf[0..value_name_wtf16le_len :0];
+ // This does not correspond to HKEY_LOCAL_MACHINE/HKEY_CURRENT_USER
+ // since WOW64 redirection is applicable to e.g. `HKLM\Software` instead of
+ // HKLM/HKCU directly. Since we are only ever interested in the
+ // `Software` key, it makes more sense to treat `Software` as the "root"
+ // since that allows us to work entirely with relative paths that are agnostic
+ // to WOW6432Node redirection.
+ const Software = struct {
+ root: Root,
+ wow64: Wow64 = .native,
+
+ const Root = enum {
+ local_machine,
+ current_user,
};
- const registry_wtf16le: RegistryWtf16Le = .{ .key = reg.key };
- return registry_wtf16le.getDword(subkey_wtf16le, value_name_wtf16le);
- }
-
- /// Under private space with flags:
- /// KEY_QUERY_VALUE and KEY_ENUMERATE_SUB_KEYS.
- /// After finishing work, call `closeKey`.
- pub fn loadFromPath(absolute_path: []const u8) error{KeyNotFound}!RegistryWtf8 {
- const absolute_path_wtf16le: [:0]const u16 = absolute_path_wtf16le: {
- var absolute_path_wtf16le_buf: [RegistryWtf16Le.value_name_max_len]u16 = undefined;
- const absolute_path_wtf16le_len: usize = std.unicode.wtf8ToWtf16Le(absolute_path_wtf16le_buf[0..], absolute_path) catch unreachable;
- absolute_path_wtf16le_buf[absolute_path_wtf16le_len] = 0;
- break :absolute_path_wtf16le absolute_path_wtf16le_buf[0..absolute_path_wtf16le_len :0];
- };
+ fn getOrOpenKey(self: Software, registry: *Registry) !Key {
+ if (registry.cache.getSoftware(self)) |handle| {
+ return .{ .handle = handle };
+ }
- const registry_wtf16le = try RegistryWtf16Le.loadFromPath(absolute_path_wtf16le);
- return .{ .key = registry_wtf16le.key };
- }
-};
+ const is_foreign = !is_32_bit and self.wow64 == .wow64_32;
+ switch (self.root) {
+ .local_machine => {
+ const path = if (is_foreign) L("\\Registry\\Machine\\Software\\WOW6432Node") else L("\\Registry\\Machine\\Software");
+ var key: Key = undefined;
+ const attr: windows.OBJECT.ATTRIBUTES = .{
+ .RootDirectory = null,
+ .Attributes = .{},
+ .ObjectName = @constCast(&windows.UNICODE_STRING.init(path)),
+ .SecurityDescriptor = null,
+ .SecurityQualityOfService = null,
+ };
+ const status = windows.ntdll.NtOpenKeyEx(
+ &key.handle,
+ .{ .MAXIMUM_ALLOWED = true },
+ &attr,
+ .{},
+ );
+ switch (status) {
+ .SUCCESS => {},
+ else => return error.KeyNotFound,
+ }
+ if (is_foreign) {
+ registry.cache.hklm_software_foreign = key.handle;
+ } else {
+ registry.cache.hklm_software_native = key.handle;
+ }
+ return key;
+ },
+ .current_user => {
+ const cu_handle: windows.HANDLE = registry.cache.hkcu orelse hkcu: {
+ var cu_handle: windows.HANDLE = undefined;
+ const status = windows.ntdll.RtlOpenCurrentUser(
+ .{ .MAXIMUM_ALLOWED = true },
+ &cu_handle,
+ );
+ switch (status) {
+ .SUCCESS => {},
+ else => return error.KeyNotFound,
+ }
+ registry.cache.hkcu = cu_handle;
+ break :hkcu cu_handle;
+ };
+ const cu_key: Registry.Key = .{ .handle = cu_handle };
+ const path = if (is_foreign) L("Software\\WOW6432Node") else L("Software");
+ const key = try cu_key.open(path);
+ if (is_foreign) {
+ registry.cache.hkcu_software_foreign = key.handle;
+ } else {
+ registry.cache.hkcu_software_native = key.handle;
+ }
+ return key;
+ },
+ }
+ }
+ };
-const RegistryWtf16Le = struct {
- key: windows.HKEY,
-
- /// Includes root key (f.e. HKEY_LOCAL_MACHINE).
- /// https://learn.microsoft.com/en-us/windows/win32/sysinfo/registry-element-size-limits
- pub const key_name_max_len = 255;
- /// In Unicode characters.
- /// https://learn.microsoft.com/en-us/windows/win32/sysinfo/registry-element-size-limits
- pub const value_name_max_len = 16_383;
-
- /// Under HKEY_LOCAL_MACHINE with flags:
- /// KEY_QUERY_VALUE, KEY_ENUMERATE_SUB_KEYS, optionally KEY_WOW64_32KEY.
- /// After finishing work, call `closeKey`.
- fn openKey(hkey: windows.HKEY, key_wtf16le: [:0]const u16, options: OpenOptions) error{KeyNotFound}!RegistryWtf16Le {
- var key: windows.HKEY = undefined;
- const return_code_int: windows.HRESULT = windows.advapi32.RegOpenKeyExW(
- hkey,
- key_wtf16le,
- 0,
- .{ .SPECIFIC = .{ .KEY = .{
- .QUERY_VALUE = true,
- .ENUMERATE_SUB_KEYS = true,
- .WOW64_32KEY = options.wow64_32,
- } } },
- &key,
- );
- const return_code: windows.Win32Error = @enumFromInt(return_code_int);
- switch (return_code) {
- .SUCCESS => {},
- .FILE_NOT_FOUND => return error.KeyNotFound,
+ /// For 32-bit programs on a 64-bit operating system, the WOW64
+ /// version of ntdll.dll handles the WOW6432Node redirection before
+ /// calling into ntdll.dll proper, so no special handling is needed
+ /// and this setting is irrelevant in that case.
+ const Wow64 = enum {
+ /// Use 64-bit registry on 64-bit targets and 32-bit registry on
+ /// 32-bit targets.
+ native,
+ /// Go through WOW6432Node on both 32-bit and 64-bit targets,
+ /// if relevant (ignored for 32-bit programs executed on a 32-bit
+ /// OS).
+ wow64_32,
+ };
- else => return error.KeyNotFound,
+ fn tryOpenSoftwareKeyWithPrecedence(registry: *Registry, variants: []const Software, sub_path: []const u16) error{KeyNotFound}!Key {
+ for (variants) |variant| {
+ return registry.openSoftwareKey(variant, sub_path) catch continue;
}
- return .{ .key = key };
+ return error.KeyNotFound;
}
- /// Closes key, after that usage is invalid
- fn closeKey(reg: RegistryWtf16Le) void {
- const return_code_int: windows.HRESULT = windows.advapi32.RegCloseKey(reg.key);
- const return_code: windows.Win32Error = @enumFromInt(return_code_int);
- switch (return_code) {
- .SUCCESS => {},
- else => {},
- }
+ fn openSoftwareKey(registry: *Registry, software: Software, sub_path: []const u16) error{KeyNotFound}!Key {
+ const software_key = try software.getOrOpenKey(registry);
+ return software_key.open(sub_path);
}
- /// Get string ([:0]const u16) from registry.
- fn getString(reg: RegistryWtf16Le, gpa: Allocator, subkey_wtf16le: [:0]const u16, value_name_wtf16le: [:0]const u16) error{ OutOfMemory, ValueNameNotFound, NotAString, StringNotFound }![]const u16 {
- var actual_type: windows.ULONG = undefined;
-
- // Calculating length to allocate
- var value_wtf16le_buf_size: u32 = 0; // in bytes, including any terminating NUL character or characters.
- var return_code_int: windows.HRESULT = windows.advapi32.RegGetValueW(
- reg.key,
- subkey_wtf16le,
- value_name_wtf16le,
- RRF.RT_REG_SZ,
- &actual_type,
- null,
- &value_wtf16le_buf_size,
- );
+ const Key = struct {
+ handle: windows.HANDLE,
- // Check returned code and type
- var return_code: windows.Win32Error = @enumFromInt(return_code_int);
- switch (return_code) {
- .SUCCESS => std.debug.assert(value_wtf16le_buf_size != 0),
- .MORE_DATA => unreachable, // We are only reading length
- .FILE_NOT_FOUND => return error.ValueNameNotFound,
- .INVALID_PARAMETER => unreachable, // We didn't combine RRF.SUBKEY_WOW6464KEY and RRF.SUBKEY_WOW6432KEY
- else => return error.StringNotFound,
+ fn close(self: Key) void {
+ windows.CloseHandle(self.handle);
}
- switch (actual_type) {
- windows.REG.SZ => {},
- else => return error.NotAString,
- }
-
- const value_wtf16le_buf: []u16 = try gpa.alloc(u16, std.math.divCeil(u32, value_wtf16le_buf_size, 2) catch unreachable);
- errdefer gpa.free(value_wtf16le_buf);
-
- return_code_int = windows.advapi32.RegGetValueW(
- reg.key,
- subkey_wtf16le,
- value_name_wtf16le,
- RRF.RT_REG_SZ,
- &actual_type,
- value_wtf16le_buf.ptr,
- &value_wtf16le_buf_size,
- );
- // Check returned code and (just in case) type again.
- return_code = @enumFromInt(return_code_int);
- switch (return_code) {
- .SUCCESS => {},
- .MORE_DATA => unreachable, // Calculated first time length should be enough, even overestimated
- .FILE_NOT_FOUND => return error.ValueNameNotFound,
- .INVALID_PARAMETER => unreachable, // We didn't combine RRF.SUBKEY_WOW6464KEY and RRF.SUBKEY_WOW6432KEY
- else => return error.StringNotFound,
- }
- switch (actual_type) {
- windows.REG.SZ => {},
- else => return error.NotAString,
+ fn open(self: Key, sub_path: []const u16) error{KeyNotFound}!Key {
+ var key: Key = undefined;
+ const attr: windows.OBJECT.ATTRIBUTES = .{
+ .RootDirectory = self.handle,
+ .Attributes = .{},
+ .ObjectName = @constCast(&windows.UNICODE_STRING.init(sub_path)),
+ .SecurityDescriptor = null,
+ .SecurityQualityOfService = null,
+ };
+ const status = windows.ntdll.NtOpenKeyEx(
+ &key.handle,
+ .{ .SPECIFIC = .{
+ .KEY = .{
+ .QUERY_VALUE = true,
+ .ENUMERATE_SUB_KEYS = true,
+ },
+ } },
+ &attr,
+ .{},
+ );
+ switch (status) {
+ .SUCCESS => return key,
+ else => return error.KeyNotFound,
+ }
}
- const value_wtf16le: []const u16 = value_wtf16le: {
- // note(bratishkaerik): somehow returned value in `buf_len` is overestimated by Windows and contains extra space
- // we will just search for zero termination and forget length
- // Windows sure is strange
- const value_wtf16le_overestimated: [*:0]const u16 = @ptrCast(value_wtf16le_buf.ptr);
- break :value_wtf16le std.mem.span(value_wtf16le_overestimated);
+ const ValueEntry = union(enum) {
+ default: void,
+ name: []const u16,
};
- _ = gpa.resize(value_wtf16le_buf, value_wtf16le.len);
- return value_wtf16le;
- }
+ fn getString(
+ key: Key,
+ gpa: Allocator,
+ entry: ValueEntry,
+ comptime result_encoding: enum { wtf16, wtf8 },
+ ) error{ OutOfMemory, ValueNameNotFound, NotAString, StringNotFound }!(switch (result_encoding) {
+ .wtf8 => []u8,
+ .wtf16 => []u16,
+ }) {
+ const num_data_bytes = windows.MAX_PATH * 2;
+ const stack_buf_len = @sizeOf(windows.KEY.VALUE.PARTIAL_INFORMATION) + num_data_bytes;
+ var stack_info_buf: [stack_buf_len]u8 align(@alignOf(windows.KEY.VALUE.PARTIAL_INFORMATION)) = undefined;
+ var result_len: windows.ULONG = undefined;
+ const rc = windows.ntdll.NtQueryValueKey(
+ key.handle,
+ switch (entry) {
+ .name => |name| @constCast(&windows.UNICODE_STRING.init(name)),
+ .default => @constCast(&windows.UNICODE_STRING.empty),
+ },
+ .Partial,
+ &stack_info_buf,
+ stack_buf_len,
+ &result_len,
+ );
+ var heap_info_buf: ?[]align(@alignOf(windows.KEY.VALUE.PARTIAL_INFORMATION)) u8 = null;
+ defer if (heap_info_buf) |buf| gpa.free(buf);
+
+ const info: *const windows.KEY.VALUE.PARTIAL_INFORMATION = switch (rc) {
+ .SUCCESS => @ptrCast(&stack_info_buf),
+ .BUFFER_OVERFLOW, .BUFFER_TOO_SMALL => heap_info: {
+ heap_info_buf = try gpa.alignedAlloc(u8, .of(windows.KEY.VALUE.PARTIAL_INFORMATION), result_len);
+ const heap_rc = windows.ntdll.NtQueryValueKey(
+ key.handle,
+ switch (entry) {
+ .name => |name| @constCast(&windows.UNICODE_STRING.init(name)),
+ .default => @constCast(&windows.UNICODE_STRING.empty),
+ },
+ .Partial,
+ heap_info_buf.?.ptr,
+ @intCast(heap_info_buf.?.len),
+ &result_len,
+ );
+ switch (heap_rc) {
+ .SUCCESS => break :heap_info @ptrCast(heap_info_buf.?.ptr),
+ .OBJECT_NAME_NOT_FOUND => return error.ValueNameNotFound,
+ else => return error.StringNotFound,
+ }
+ },
+ .OBJECT_NAME_NOT_FOUND => return error.ValueNameNotFound,
+ else => return error.StringNotFound,
+ };
- /// Get DWORD (u32) from registry.
- fn getDword(reg: RegistryWtf16Le, subkey_wtf16le: [:0]const u16, value_name_wtf16le: [:0]const u16) error{ ValueNameNotFound, NotADword, DwordTooLong, DwordNotFound }!u32 {
- var actual_type: windows.ULONG = undefined;
- var reg_size: u32 = @sizeOf(u32);
- var reg_value: u32 = 0;
-
- const return_code_int: windows.HRESULT = windows.advapi32.RegGetValueW(
- reg.key,
- subkey_wtf16le,
- value_name_wtf16le,
- RRF.RT_REG_DWORD,
- &actual_type,
- ®_value,
- ®_size,
- );
- const return_code: windows.Win32Error = @enumFromInt(return_code_int);
- switch (return_code) {
- .SUCCESS => {},
- .MORE_DATA => return error.DwordTooLong,
- .FILE_NOT_FOUND => return error.ValueNameNotFound,
- .INVALID_PARAMETER => unreachable, // We didn't combine RRF.SUBKEY_WOW6464KEY and RRF.SUBKEY_WOW6432KEY
- else => return error.DwordNotFound,
- }
+ switch (info.Type) {
+ .SZ => {},
+ else => return error.NotAString,
+ }
- switch (actual_type) {
- windows.REG.DWORD => {},
- else => return error.NotADword,
+ const data_wtf16_with_nul = @as([*]const u16, @ptrCast(@alignCast(info.data())))[0..@divExact(info.DataLength, 2)];
+ const data_wtf16 = std.mem.trimEnd(u16, data_wtf16_with_nul, L("\x00"));
+ switch (result_encoding) {
+ .wtf16 => return gpa.dupe(u16, data_wtf16),
+ .wtf8 => return std.unicode.wtf16LeToWtf8Alloc(gpa, data_wtf16),
+ }
}
- return reg_value;
- }
+ fn getDword(key: Key, entry: ValueEntry) error{ ValueNameNotFound, NotADword, DwordNotFound }!windows.DWORD {
+ const num_data_bytes = @sizeOf(windows.DWORD);
+ const buf_len = @sizeOf(windows.KEY.VALUE.PARTIAL_INFORMATION) + num_data_bytes;
+ var info_buf: [buf_len]u8 align(@alignOf(windows.KEY.VALUE.PARTIAL_INFORMATION)) = undefined;
+ var result_len: windows.ULONG = undefined;
+ const rc = windows.ntdll.NtQueryValueKey(
+ key.handle,
+ switch (entry) {
+ .name => |name| @constCast(&windows.UNICODE_STRING.init(name)),
+ .default => @constCast(&windows.UNICODE_STRING.empty),
+ },
+ .Partial,
+ &info_buf,
+ buf_len,
+ &result_len,
+ );
+ switch (rc) {
+ .SUCCESS => {},
+ .OBJECT_NAME_NOT_FOUND => return error.ValueNameNotFound,
+ else => return error.DwordNotFound,
+ }
- /// Under private space with flags:
- /// KEY_QUERY_VALUE and KEY_ENUMERATE_SUB_KEYS.
- /// After finishing work, call `closeKey`.
- fn loadFromPath(absolute_path_as_wtf16le: [:0]const u16) error{KeyNotFound}!RegistryWtf16Le {
- var key: windows.HKEY = undefined;
-
- const return_code_int: windows.HRESULT = std.os.windows.advapi32.RegLoadAppKeyW(
- absolute_path_as_wtf16le,
- &key,
- .{ .SPECIFIC = .{ .KEY = .{
- .QUERY_VALUE = true,
- .ENUMERATE_SUB_KEYS = true,
- } } },
- 0,
- 0,
- );
- const return_code: windows.Win32Error = @enumFromInt(return_code_int);
- switch (return_code) {
- .SUCCESS => {},
- else => return error.KeyNotFound,
- }
+ const info: *const windows.KEY.VALUE.PARTIAL_INFORMATION = @ptrCast(&info_buf);
- return .{ .key = key };
- }
+ switch (info.Type) {
+ .DWORD => {},
+ else => return error.NotADword,
+ }
+
+ return std.mem.bytesToValue(windows.DWORD, info.data());
+ }
+ };
};
pub const Installation = struct {
@@ -428,20 +464,21 @@ pub const Installation = struct {
fn find(
gpa: Allocator,
io: Io,
- roots_key: RegistryWtf8,
- roots_subkey: []const u8,
+ registry: *Registry,
+ roots_key: Registry.Key,
+ roots_subkey: []const u16,
prefix: []const u8,
- version_key_name: []const u8,
+ version_key_name: []const u16,
) error{ OutOfMemory, InstallationNotFound, PathTooLong, VersionTooLong }!Installation {
roots: {
const installation = findFromRoot(gpa, io, roots_key, roots_subkey, prefix) catch
break :roots;
- if (installation.isValidVersion()) return installation;
+ if (installation.isValidVersion(roots_key)) return installation;
installation.free(gpa);
}
{
- const installation = try findFromInstallationFolder(gpa, version_key_name);
- if (installation.isValidVersion()) return installation;
+ const installation = try findFromInstallationFolder(gpa, registry, version_key_name);
+ if (installation.isValidVersion(roots_key)) return installation;
installation.free(gpa);
}
return error.InstallationNotFound;
@@ -450,29 +487,27 @@ pub const Installation = struct {
fn findFromRoot(
gpa: Allocator,
io: Io,
- roots_key: RegistryWtf8,
- roots_subkey: []const u8,
+ roots_key: Registry.Key,
+ roots_subkey: []const u16,
prefix: []const u8,
) error{ OutOfMemory, InstallationNotFound, PathTooLong, VersionTooLong }!Installation {
const path = path: {
- const path_maybe_with_trailing_slash = roots_key.getString(gpa, "", roots_subkey) catch |err| switch (err) {
- error.NotAString => return error.InstallationNotFound,
- error.ValueNameNotFound => return error.InstallationNotFound,
- error.StringNotFound => return error.InstallationNotFound,
+ const path_w_maybe_with_trailing_slash = roots_key.getString(gpa, .{ .name = roots_subkey }, .wtf16) catch |err| switch (err) {
+ error.NotAString,
+ error.ValueNameNotFound,
+ error.StringNotFound,
+ => return error.InstallationNotFound,
error.OutOfMemory => return error.OutOfMemory,
};
- if (path_maybe_with_trailing_slash.len > Dir.max_path_bytes or !Dir.path.isAbsolute(path_maybe_with_trailing_slash)) {
- gpa.free(path_maybe_with_trailing_slash);
- return error.PathTooLong;
- }
+ defer gpa.free(path_w_maybe_with_trailing_slash);
- var path = std.array_list.Managed(u8).fromOwnedSlice(gpa, path_maybe_with_trailing_slash);
- errdefer path.deinit();
+ if (!std.fs.path.isAbsoluteWindowsWtf16(path_w_maybe_with_trailing_slash)) {
+ return error.InstallationNotFound;
+ }
- // String might contain trailing slash, so trim it here
- if (path.items.len > "C:\\".len and path.getLast() == '\\') _ = path.pop();
- break :path try path.toOwnedSlice();
+ const path_w = std.mem.trimEnd(u16, path_w_maybe_with_trailing_slash, L("\\/"));
+ break :path try std.unicode.wtf16LeToWtf8Alloc(gpa, path_w);
};
errdefer gpa.free(path);
@@ -508,70 +543,71 @@ pub const Installation = struct {
fn findFromInstallationFolder(
gpa: Allocator,
- version_key_name: []const u8,
+ registry: *Registry,
+ version_key_name: []const u16,
) error{ OutOfMemory, InstallationNotFound, PathTooLong, VersionTooLong }!Installation {
- var key_name_buf: [RegistryWtf16Le.key_name_max_len]u8 = undefined;
- const key_name = std.fmt.bufPrint(
- &key_name_buf,
- "SOFTWARE\\Microsoft\\Microsoft SDKs\\Windows\\{s}",
- .{version_key_name},
- ) catch unreachable;
- const key = key: for ([_]bool{ true, false }) |wow6432node| {
- for ([_]windows.HKEY{ windows.HKEY_LOCAL_MACHINE, windows.HKEY_CURRENT_USER }) |hkey| {
- break :key RegistryWtf8.openKey(hkey, key_name, .{ .wow64_32 = wow6432node }) catch |err| switch (err) {
- error.KeyNotFound => return error.InstallationNotFound,
- };
- }
- } else return error.InstallationNotFound;
- defer key.closeKey();
+ const key_name = try std.mem.concat(gpa, u16, &.{ L("Microsoft\\Microsoft SDKs\\Windows\\"), version_key_name });
+ defer gpa.free(key_name);
+
+ const key = registry.tryOpenSoftwareKeyWithPrecedence(switch (is_32_bit) {
+ true => &.{
+ .{ .root = .local_machine },
+ .{ .root = .current_user },
+ },
+ false => &.{
+ .{ .root = .local_machine, .wow64 = .wow64_32 },
+ .{ .root = .current_user, .wow64 = .wow64_32 },
+ .{ .root = .local_machine, .wow64 = .native },
+ .{ .root = .current_user, .wow64 = .native },
+ },
+ }, key_name) catch {
+ return error.InstallationNotFound;
+ };
+ defer key.close();
const path: []const u8 = path: {
- const path_maybe_with_trailing_slash = key.getString(gpa, "", "InstallationFolder") catch |err| switch (err) {
- error.NotAString => return error.InstallationNotFound,
- error.ValueNameNotFound => return error.InstallationNotFound,
- error.StringNotFound => return error.InstallationNotFound,
+ const path_w_maybe_with_trailing_slash = key.getString(gpa, .{ .name = L("InstallationFolder") }, .wtf16) catch |err| switch (err) {
+ error.NotAString,
+ error.ValueNameNotFound,
+ error.StringNotFound,
+ => return error.InstallationNotFound,
error.OutOfMemory => return error.OutOfMemory,
};
+ defer gpa.free(path_w_maybe_with_trailing_slash);
- if (path_maybe_with_trailing_slash.len > Dir.max_path_bytes or !Dir.path.isAbsolute(path_maybe_with_trailing_slash)) {
- gpa.free(path_maybe_with_trailing_slash);
- return error.PathTooLong;
+ if (!std.fs.path.isAbsoluteWindowsWtf16(path_w_maybe_with_trailing_slash)) {
+ return error.InstallationNotFound;
}
- var path = std.array_list.Managed(u8).fromOwnedSlice(gpa, path_maybe_with_trailing_slash);
- errdefer path.deinit();
-
- // String might contain trailing slash, so trim it here
- if (path.items.len > "C:\\".len and path.getLast() == '\\') _ = path.pop();
-
- const path_without_trailing_slash = try path.toOwnedSlice();
- break :path path_without_trailing_slash;
+ const path_w = std.mem.trimEnd(u16, path_w_maybe_with_trailing_slash, L("\\/"));
+ break :path try std.unicode.wtf16LeToWtf8Alloc(gpa, path_w);
};
errdefer gpa.free(path);
const version: []const u8 = version: {
-
- // note(dimenus): Microsoft doesn't include the .0 in the ProductVersion key....
- const version_without_0 = key.getString(gpa, "", "ProductVersion") catch |err| switch (err) {
- error.NotAString => return error.InstallationNotFound,
- error.ValueNameNotFound => return error.InstallationNotFound,
- error.StringNotFound => return error.InstallationNotFound,
+ // Microsoft doesn't include the .0 in the ProductVersion key
+ const version_without_0 = key.getString(gpa, .{ .name = L("ProductVersion") }, .wtf16) catch |err| switch (err) {
+ error.NotAString,
+ error.ValueNameNotFound,
+ error.StringNotFound,
+ => return error.InstallationNotFound,
error.OutOfMemory => return error.OutOfMemory,
};
+ defer gpa.free(version_without_0);
+
if (version_without_0.len + ".0".len > product_version_max_length) {
- gpa.free(version_without_0);
return error.VersionTooLong;
}
- var version = std.array_list.Managed(u8).fromOwnedSlice(gpa, version_without_0);
+ var version: std.array_list.Managed(u8) = try .initCapacity(gpa, version_without_0.len + 2);
errdefer version.deinit();
+ try std.unicode.wtf16LeToWtf8ArrayList(&version, version_without_0);
try version.appendSlice(".0");
- const version_with_0 = try version.toOwnedSlice();
- break :version version_with_0;
+ break :version try version.toOwnedSlice();
};
errdefer gpa.free(version);
@@ -579,23 +615,22 @@ pub const Installation = struct {
}
/// Check whether this version is enumerated in registry.
- fn isValidVersion(installation: Installation) bool {
- var buf: [Dir.max_path_bytes]u8 = undefined;
- const reg_query_as_wtf8 = std.fmt.bufPrint(buf[0..], "{s}\\{s}\\Installed Options", .{
- windows_kits_reg_key,
- installation.version,
- }) catch |err| switch (err) {
- error.NoSpaceLeft => return false,
- };
-
- const options_key = RegistryWtf8.openKey(
- windows.HKEY_LOCAL_MACHINE,
- reg_query_as_wtf8,
- .{ .wow64_32 = true },
- ) catch |err| switch (err) {
+ fn isValidVersion(installation: Installation, roots_key: Registry.Key) bool {
+ var version_buf: [product_version_max_length]u16 = undefined;
+ const version_len = std.unicode.wtf8ToWtf16Le(&version_buf, installation.version) catch return false;
+ const version = version_buf[0..version_len];
+ const options_key_name = "Installed Options";
+ const buf_len = product_version_max_length + options_key_name.len + 2;
+ var buf: [buf_len]u16 = undefined;
+ var query: std.ArrayList(u16) = .initBuffer(&buf);
+ query.appendSliceAssumeCapacity(version);
+ query.appendAssumeCapacity('\\');
+ query.appendSliceAssumeCapacity(L(options_key_name));
+
+ const options_key = roots_key.open(query.items) catch |err| switch (err) {
error.KeyNotFound => return false,
};
- defer options_key.closeKey();
+ defer options_key.close();
const option_name = comptime switch (builtin.target.cpu.arch) {
.thumb => "OptionId.DesktopCPParm",
@@ -605,7 +640,7 @@ pub const Installation = struct {
else => |tag| @compileError("Windows SDK cannot be detected on architecture " ++ tag),
};
- const reg_value = options_key.getDword("", option_name) catch return false;
+ const reg_value = options_key.getDword(.{ .name = L(option_name) }) catch return false;
return (reg_value == 1);
}
@@ -616,14 +651,14 @@ pub const Installation = struct {
};
const MsvcLibDir = struct {
- fn findInstancesDirViaSetup(gpa: Allocator, io: Io) error{ OutOfMemory, PathNotFound }!Dir {
- const vs_setup_key_path = "SOFTWARE\\Microsoft\\VisualStudio\\Setup";
- const vs_setup_key = RegistryWtf8.openKey(windows.HKEY_LOCAL_MACHINE, vs_setup_key_path, .{}) catch |err| switch (err) {
+ fn findInstancesDirViaSetup(gpa: Allocator, io: Io, registry: *Registry) error{ OutOfMemory, PathNotFound }!Dir {
+ const vs_setup_key_path = L("Microsoft\\VisualStudio\\Setup");
+ const vs_setup_key = registry.openSoftwareKey(.{ .root = .local_machine }, vs_setup_key_path) catch |err| switch (err) {
error.KeyNotFound => return error.PathNotFound,
};
- defer vs_setup_key.closeKey();
+ defer vs_setup_key.close();
- const packages_path = vs_setup_key.getString(gpa, "", "CachePath") catch |err| switch (err) {
+ const packages_path = vs_setup_key.getString(gpa, .{ .name = L("CachePath") }, .wtf8) catch |err| switch (err) {
error.NotAString,
error.ValueNameNotFound,
error.StringNotFound,
@@ -633,22 +668,41 @@ const MsvcLibDir = struct {
};
defer gpa.free(packages_path);
- if (!Dir.path.isAbsolute(packages_path)) return error.PathNotFound;
+ if (!std.fs.path.isAbsolute(packages_path)) return error.PathNotFound;
- const instances_path = try Dir.path.join(gpa, &.{ packages_path, "_Instances" });
+ const instances_path = try std.fs.path.join(gpa, &.{ packages_path, "_Instances" });
defer gpa.free(instances_path);
return Dir.openDirAbsolute(io, instances_path, .{ .iterate = true }) catch return error.PathNotFound;
}
- fn findInstancesDirViaCLSID(gpa: Allocator, io: Io) error{ OutOfMemory, PathNotFound }!Dir {
+ fn findInstancesDirViaCLSID(gpa: Allocator, io: Io, registry: *Registry) error{ OutOfMemory, PathNotFound }!Dir {
const setup_configuration_clsid = "{177f0c4a-1cd3-4de7-a32c-71dbbb9fa36d}";
- const setup_config_key = RegistryWtf8.openKey(windows.HKEY_CLASSES_ROOT, "CLSID\\" ++ setup_configuration_clsid, .{}) catch |err| switch (err) {
+
+ // HKEY_CLASSES_ROOT is not a single key but instead a combination of
+ // HKCU\Software\Classes and HKLM\Software\Classes with HKCU taking precedent
+ // https://learn.microsoft.com/en-us/windows/win32/sysinfo/hkey-classes-root-key
+ //
+ // Instead of a CLASSES_ROOT abstraction, we emulate the behavior with a more
+ // general abstraction, which also means we need to include `Classes` in the path since
+ // we're starting from the `Software` keys instead of the "classes root".
+ //
+ // The advapi32 APIs with `HKEY_CLASSES_ROOT` go through `\REGISTRY\USER\<SID>_Classes`
+ // instead of `\REGISTRY\USER\<SID>\Software\Classes`, but we go through the latter
+ // because it allows us to take advantage of `RtlOpenCurrentUser` to avoid needing to implement
+ // the logic for getting the current user registry path, and it appears that the two keys are
+ // effectively equivalent. Further investigation of the relationship of these keys would probably
+ // be beneficial, though.
+ const setup_config_key = registry.tryOpenSoftwareKeyWithPrecedence(&.{
+ .{ .root = .current_user },
+ .{ .root = .local_machine },
+ }, L("Classes\\CLSID\\" ++ setup_configuration_clsid)) catch |err| switch (err) {
error.KeyNotFound => return error.PathNotFound,
};
- defer setup_config_key.closeKey();
+ defer setup_config_key.close();
- const dll_path = setup_config_key.getString(gpa, "InprocServer32", "") catch |err| switch (err) {
+ const inproc_server = setup_config_key.open(L("InprocServer32")) catch return error.PathNotFound;
+ const dll_path = inproc_server.getString(gpa, .default, .wtf8) catch |err| switch (err) {
error.NotAString,
error.ValueNameNotFound,
error.StringNotFound,
@@ -658,9 +712,9 @@ const MsvcLibDir = struct {
};
defer gpa.free(dll_path);
- if (!Dir.path.isAbsolute(dll_path)) return error.PathNotFound;
+ if (!std.fs.path.isAbsolute(dll_path)) return error.PathNotFound;
- var path_it = Dir.path.componentIterator(dll_path);
+ var path_it = std.fs.path.componentIterator(dll_path);
// the .dll filename
_ = path_it.last();
const root_path = while (path_it.previous()) |dir_component| {
@@ -671,7 +725,7 @@ const MsvcLibDir = struct {
return error.PathNotFound;
};
- const instances_path = try Dir.path.join(gpa, &.{ root_path, "Packages", "_Instances" });
+ const instances_path = try std.fs.path.join(gpa, &.{ root_path, "Packages", "_Instances" });
defer gpa.free(instances_path);
return Dir.openDirAbsolute(io, instances_path, .{ .iterate = true }) catch return error.PathNotFound;
@@ -680,12 +734,13 @@ const MsvcLibDir = struct {
fn findInstancesDir(
gpa: Allocator,
io: Io,
+ registry: *Registry,
environ_map: *const Environ.Map,
) error{ OutOfMemory, PathNotFound }!Dir {
// First, try getting the packages cache path from the registry.
// This only seems to exist when the path is different from the default.
method1: {
- return findInstancesDirViaSetup(gpa, io) catch |err| switch (err) {
+ return findInstancesDirViaSetup(gpa, io, registry) catch |err| switch (err) {
error.OutOfMemory => |e| return e,
error.PathNotFound => break :method1,
};
@@ -693,7 +748,7 @@ const MsvcLibDir = struct {
// Otherwise, try to get the path from the .dll that would have been
// loaded via COM for SetupConfiguration.
method2: {
- return findInstancesDirViaCLSID(gpa, io) catch |err| switch (err) {
+ return findInstancesDirViaCLSID(gpa, io, registry) catch |err| switch (err) {
error.OutOfMemory => |e| return e,
error.PathNotFound => break :method2,
};
@@ -703,7 +758,7 @@ const MsvcLibDir = struct {
method3: {
const program_data = std.zig.EnvVar.PROGRAMDATA.get(environ_map) orelse break :method3;
- if (!Dir.path.isAbsolute(program_data)) break :method3;
+ if (!std.fs.path.isAbsolute(program_data)) break :method3;
const instances_path = try Dir.path.join(gpa, &.{
program_data, "Microsoft", "VisualStudio", "Packages", "_Instances",
@@ -764,6 +819,7 @@ const MsvcLibDir = struct {
fn findViaCOM(
gpa: Allocator,
io: Io,
+ registry: *Registry,
arch: std.Target.Cpu.Arch,
environ_map: *const Environ.Map,
) error{ OutOfMemory, PathNotFound }![]const u8 {
@@ -771,7 +827,7 @@ const MsvcLibDir = struct {
// This will contain directories with names of instance IDs like 80a758ca,
// which will contain `state.json` files that have the version and
// installation directory.
- var instances_dir = try findInstancesDir(gpa, io, environ_map);
+ var instances_dir = try findInstancesDir(gpa, io, registry, environ_map);
defer instances_dir.close(io);
var state_subpath_buf: [Dir.max_name_bytes + 32]u8 = undefined;
@@ -874,7 +930,6 @@ const MsvcLibDir = struct {
arch: std.Target.Cpu.Arch,
environ_map: *const Environ.Map,
) error{ OutOfMemory, PathNotFound }![]const u8 {
-
// %localappdata%\Microsoft\VisualStudio\
// %appdata%\Local\Microsoft\VisualStudio\
const local_app_data_path = std.zig.EnvVar.LOCALAPPDATA.get(environ_map) orelse return error.PathNotFound;
@@ -883,15 +938,18 @@ const MsvcLibDir = struct {
});
defer gpa.free(visualstudio_folder_path);
+ if (!Dir.path.isAbsolute(visualstudio_folder_path)) return error.PathNotFound;
+ // To make things easier later on, we open the VisualStudio directory here which
+ // allows us to pass relative paths to NtLoadKeyEx in order to avoid dealing with
+ // conversion to NT namespace paths.
+ var visualstudio_folder = Dir.openDirAbsolute(io, visualstudio_folder_path, .{
+ .iterate = true,
+ }) catch return error.PathNotFound;
+ defer visualstudio_folder.close(io);
+
const vs_versions: []const []const u8 = vs_versions: {
- if (!Dir.path.isAbsolute(visualstudio_folder_path)) return error.PathNotFound;
// enumerate folders that contain `privateregistry.bin`, looking for all versions
// f.i. %localappdata%\Microsoft\VisualStudio\17.0_9e9cbb98\
- var visualstudio_folder = Dir.openDirAbsolute(io, visualstudio_folder_path, .{
- .iterate = true,
- }) catch return error.PathNotFound;
- defer visualstudio_folder.close(io);
-
var iterator = visualstudio_folder.iterate();
break :vs_versions try iterateAndFilterByVersion(&iterator, gpa, io, "");
};
@@ -899,25 +957,94 @@ const MsvcLibDir = struct {
for (vs_versions) |vs_version| gpa.free(vs_version);
gpa.free(vs_versions);
}
- var config_subkey_buf: [RegistryWtf16Le.key_name_max_len * 2]u8 = undefined;
+ var key_path_buf: [windows.NAME_MAX * 2]u16 = undefined;
+ var sub_path_buf: [windows.NAME_MAX * 2]u16 = undefined;
const source_directories: []const u8 = source_directories: for (vs_versions) |vs_version| {
- const privateregistry_absolute_path = Dir.path.join(gpa, &.{ visualstudio_folder_path, vs_version, "privateregistry.bin" }) catch continue;
- defer gpa.free(privateregistry_absolute_path);
- if (!Dir.path.isAbsolute(privateregistry_absolute_path)) continue;
+ const sub_path = blk: {
+ var buf: std.ArrayList(u16) = .initBuffer(&sub_path_buf);
+ buf.items.len += std.unicode.wtf8ToWtf16Le(buf.unusedCapacitySlice(), vs_version) catch unreachable;
+ buf.appendSliceAssumeCapacity(L("\\privateregistry.bin"));
+ break :blk buf.items;
+ };
- const visualstudio_registry = RegistryWtf8.loadFromPath(privateregistry_absolute_path) catch continue;
- defer visualstudio_registry.closeKey();
+ // The goal is to emulate advapi32.RegLoadAppKeyW with a direct call
+ // to NtLoadKeyEx instead.
+ //
+ // RegLoadAppKeyW loads the hive into a registry key of the format:
+ // \REGISTRY\A\{fdb2baa5-8ca8-ef03-78d0-3b1f868fd2a9}
+ // where `\REGISTRY\A` is a special unenumerable location intended for
+ // per-app hives, and the GUID is randomly generated (in testing, it
+ // was different for each run of the program).
+ //
+ // The OS is responsible for cleaning up `\REGISTRY\A` whenever all handles
+ // to one of its keys are closed, so we don't have to do anything special
+ // with regards to that.
+
+ const temp_key_path = blk: {
+ var guid: windows.GUID = undefined;
+ io.random(std.mem.asBytes(&guid));
+
+ var guid_buf: [38]u8 = undefined;
+ const guid_str = std.fmt.bufPrint(&guid_buf, "{f}", .{guid}) catch unreachable;
+
+ var buf: std.ArrayList(u16) = .initBuffer(&key_path_buf);
+ buf.appendSliceAssumeCapacity(L("\\REGISTRY\\A\\"));
+ buf.items.len += std.unicode.wtf8ToWtf16Le(buf.unusedCapacitySlice(), guid_str) catch unreachable;
+ break :blk buf.items;
+ };
- const config_subkey = std.fmt.bufPrint(config_subkey_buf[0..], "Software\\Microsoft\\VisualStudio\\{s}_Config", .{vs_version}) catch unreachable;
+ const target_key: windows.OBJECT.ATTRIBUTES = .{
+ .RootDirectory = null,
+ .Attributes = .{},
+ .ObjectName = @constCast(&windows.UNICODE_STRING.init(temp_key_path)),
+ .SecurityDescriptor = null,
+ };
+ const source_file: windows.OBJECT.ATTRIBUTES = .{
+ .RootDirectory = visualstudio_folder.handle,
+ .Attributes = .{},
+ .ObjectName = @constCast(&windows.UNICODE_STRING.init(sub_path)),
+ .SecurityDescriptor = null,
+ };
+ var root_key: Registry.Key = undefined;
+ const rc = windows.ntdll.NtLoadKeyEx(
+ &target_key,
+ &source_file,
+ .{
+ .APP_HIVE = true,
+ // This wasn't set by RegLoadAppKeyW, but it seems relevant
+ // since we aren't intending to do any modifcation of the hive.
+ .OPEN_READ_ONLY = true,
+ },
+ null,
+ null,
+ .{ .SPECIFIC = .{
+ .KEY = .{
+ .QUERY_VALUE = true,
+ .ENUMERATE_SUB_KEYS = true,
+ },
+ } },
+ &root_key.handle,
+ null,
+ );
+ switch (rc) {
+ .SUCCESS => {},
+ else => continue,
+ }
+ defer root_key.close();
+
+ const config_path = blk: {
+ var buf: std.ArrayList(u16) = .initBuffer(&key_path_buf);
+ buf.appendSliceAssumeCapacity(L("Software\\Microsoft\\VisualStudio\\"));
+ buf.items.len += std.unicode.wtf8ToWtf16Le(buf.unusedCapacitySlice(), vs_version) catch unreachable;
+ buf.appendSliceAssumeCapacity(L("_Config"));
+ break :blk buf.items;
+ };
+ const config_key = root_key.open(config_path) catch continue;
- const source_directories_value = visualstudio_registry.getString(gpa, config_subkey, "Source Directories") catch |err| switch (err) {
+ const source_directories_value = config_key.getString(gpa, .{ .name = L("Source Directories") }, .wtf8) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
else => continue,
};
- if (source_directories_value.len > (Dir.max_path_bytes * 30)) { // note(bratishkaerik): guessing from the fact that on my computer it has 15 paths and at least some of them are not of max length
- gpa.free(source_directories_value);
- continue;
- }
break :source_directories source_directories_value;
} else return error.PathNotFound;
@@ -967,6 +1094,7 @@ const MsvcLibDir = struct {
fn findViaVs7Key(
gpa: Allocator,
io: Io,
+ registry: *Registry,
arch: std.Target.Cpu.Arch,
environ_map: *const Environ.Map,
) error{ OutOfMemory, PathNotFound }![]const u8 {
@@ -986,10 +1114,10 @@ const MsvcLibDir = struct {
}
}
- const vs7_key = RegistryWtf8.openKey(windows.HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\VisualStudio\\SxS\\VS7", .{ .wow64_32 = true }) catch return error.PathNotFound;
- defer vs7_key.closeKey();
+ const vs7_key = registry.openSoftwareKey(.{ .root = .local_machine, .wow64 = .wow64_32 }, L("Microsoft\\VisualStudio\\SxS\\VS7")) catch return error.PathNotFound;
+ defer vs7_key.close();
try_vs7_key: {
- const path_maybe_with_trailing_slash = vs7_key.getString(gpa, "", "14.0") catch |err| switch (err) {
+ const path_maybe_with_trailing_slash = vs7_key.getString(gpa, .{ .name = L("14.0") }, .wtf8) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
else => break :try_vs7_key,
};
@@ -1045,14 +1173,15 @@ const MsvcLibDir = struct {
pub fn find(
gpa: Allocator,
io: Io,
+ registry: *Registry,
arch: std.Target.Cpu.Arch,
environ_map: *const Environ.Map,
) error{ OutOfMemory, MsvcLibDirNotFound }![]const u8 {
- const full_path = MsvcLibDir.findViaCOM(gpa, io, arch, environ_map) catch |err1| switch (err1) {
+ const full_path = MsvcLibDir.findViaCOM(gpa, io, registry, arch, environ_map) catch |err1| switch (err1) {
error.OutOfMemory => return error.OutOfMemory,
error.PathNotFound => MsvcLibDir.findViaRegistry(gpa, io, arch, environ_map) catch |err2| switch (err2) {
error.OutOfMemory => return error.OutOfMemory,
- error.PathNotFound => MsvcLibDir.findViaVs7Key(gpa, io, arch, environ_map) catch |err3| switch (err3) {
+ error.PathNotFound => MsvcLibDir.findViaVs7Key(gpa, io, registry, arch, environ_map) catch |err3| switch (err3) {
error.OutOfMemory => return error.OutOfMemory,
error.PathNotFound => return error.MsvcLibDirNotFound,
},
diff --git a/lib/std/zig/system/windows.zig b/lib/std/zig/system/windows.zig
@@ -85,7 +85,7 @@ fn getCpuInfoFromRegistry(core: usize, args: anytype) !void {
.Flags = std.os.windows.RTL_QUERY_REGISTRY_SUBKEY | std.os.windows.RTL_QUERY_REGISTRY_REQUIRED,
.Name = subkey[0..subkey_len :0],
.EntryContext = null,
- .DefaultType = REG.NONE,
+ .DefaultType = .NONE,
.DefaultData = null,
.DefaultLength = 0,
};
@@ -95,9 +95,9 @@ fn getCpuInfoFromRegistry(core: usize, args: anytype) !void {
inline for (fields_info, 0..) |field, i| {
const ctx: *anyopaque = blk: {
switch (@field(args, field.name).value_type) {
- REG.SZ,
- REG.EXPAND_SZ,
- REG.MULTI_SZ,
+ .SZ,
+ .EXPAND_SZ,
+ .MULTI_SZ,
=> {
comptime assert(@sizeOf(std.os.windows.UNICODE_STRING) % 2 == 0);
const unicode: *std.os.windows.UNICODE_STRING = @ptrCast(&tmp_bufs[i]);
@@ -109,9 +109,9 @@ fn getCpuInfoFromRegistry(core: usize, args: anytype) !void {
break :blk unicode;
},
- REG.DWORD,
- REG.DWORD_BIG_ENDIAN,
- REG.QWORD,
+ .DWORD,
+ .DWORD_BIG_ENDIAN,
+ .QWORD,
=> break :blk &tmp_bufs[i],
else => unreachable,
@@ -127,7 +127,7 @@ fn getCpuInfoFromRegistry(core: usize, args: anytype) !void {
.Flags = std.os.windows.RTL_QUERY_REGISTRY_DIRECT | std.os.windows.RTL_QUERY_REGISTRY_REQUIRED,
.Name = key_buf[0..key_len :0],
.EntryContext = ctx,
- .DefaultType = REG.NONE,
+ .DefaultType = .NONE,
.DefaultData = null,
.DefaultLength = 0,
};
@@ -139,7 +139,7 @@ fn getCpuInfoFromRegistry(core: usize, args: anytype) !void {
.Flags = 0,
.Name = null,
.EntryContext = null,
- .DefaultType = 0,
+ .DefaultType = .NONE,
.DefaultData = null,
.DefaultLength = 0,
};
@@ -154,9 +154,9 @@ fn getCpuInfoFromRegistry(core: usize, args: anytype) !void {
switch (res) {
.SUCCESS => {
inline for (fields_info, 0..) |field, i| switch (@field(args, field.name).value_type) {
- REG.SZ,
- REG.EXPAND_SZ,
- REG.MULTI_SZ,
+ .SZ,
+ .EXPAND_SZ,
+ .MULTI_SZ,
=> {
var buf = @field(args, field.name).value_buf;
const entry: *const std.os.windows.UNICODE_STRING = @ptrCast(table[i + 1].EntryContext);
@@ -164,16 +164,16 @@ fn getCpuInfoFromRegistry(core: usize, args: anytype) !void {
buf[len] = 0;
},
- REG.DWORD,
- REG.DWORD_BIG_ENDIAN,
- REG.QWORD,
+ .DWORD,
+ .DWORD_BIG_ENDIAN,
+ .QWORD,
=> {
const entry: [*]const u8 = @ptrCast(table[i + 1].EntryContext);
switch (@field(args, field.name).value_type) {
- REG.DWORD, REG.DWORD_BIG_ENDIAN => {
+ .DWORD, .DWORD_BIG_ENDIAN => {
@memcpy(@field(args, field.name).value_buf[0..4], entry[0..4]);
},
- REG.QWORD => {
+ .QWORD => {
@memcpy(@field(args, field.name).value_buf[0..8], entry[0..8]);
},
else => unreachable,
@@ -254,18 +254,18 @@ pub fn detectNativeCpuAndFeatures() ?Target.Cpu {
// CP 4039 -> ID_AA64MMFR1_EL1
// CP 403A -> ID_AA64MMFR2_EL1
getCpuInfoFromRegistry(i, .{
- .{ .key = "CP 4000", .value_type = REG.QWORD, .value_buf = @as(*[8]u8, @ptrCast(®isters[0])) },
- .{ .key = "CP 4020", .value_type = REG.QWORD, .value_buf = @as(*[8]u8, @ptrCast(®isters[1])) },
- .{ .key = "CP 4021", .value_type = REG.QWORD, .value_buf = @as(*[8]u8, @ptrCast(®isters[2])) },
- .{ .key = "CP 4028", .value_type = REG.QWORD, .value_buf = @as(*[8]u8, @ptrCast(®isters[3])) },
- .{ .key = "CP 4029", .value_type = REG.QWORD, .value_buf = @as(*[8]u8, @ptrCast(®isters[4])) },
- .{ .key = "CP 402C", .value_type = REG.QWORD, .value_buf = @as(*[8]u8, @ptrCast(®isters[5])) },
- .{ .key = "CP 402D", .value_type = REG.QWORD, .value_buf = @as(*[8]u8, @ptrCast(®isters[6])) },
- .{ .key = "CP 4030", .value_type = REG.QWORD, .value_buf = @as(*[8]u8, @ptrCast(®isters[7])) },
- .{ .key = "CP 4031", .value_type = REG.QWORD, .value_buf = @as(*[8]u8, @ptrCast(®isters[8])) },
- .{ .key = "CP 4038", .value_type = REG.QWORD, .value_buf = @as(*[8]u8, @ptrCast(®isters[9])) },
- .{ .key = "CP 4039", .value_type = REG.QWORD, .value_buf = @as(*[8]u8, @ptrCast(®isters[10])) },
- .{ .key = "CP 403A", .value_type = REG.QWORD, .value_buf = @as(*[8]u8, @ptrCast(®isters[11])) },
+ .{ .key = "CP 4000", .value_type = REG.ValueType.QWORD, .value_buf = @as(*[8]u8, @ptrCast(®isters[0])) },
+ .{ .key = "CP 4020", .value_type = REG.ValueType.QWORD, .value_buf = @as(*[8]u8, @ptrCast(®isters[1])) },
+ .{ .key = "CP 4021", .value_type = REG.ValueType.QWORD, .value_buf = @as(*[8]u8, @ptrCast(®isters[2])) },
+ .{ .key = "CP 4028", .value_type = REG.ValueType.QWORD, .value_buf = @as(*[8]u8, @ptrCast(®isters[3])) },
+ .{ .key = "CP 4029", .value_type = REG.ValueType.QWORD, .value_buf = @as(*[8]u8, @ptrCast(®isters[4])) },
+ .{ .key = "CP 402C", .value_type = REG.ValueType.QWORD, .value_buf = @as(*[8]u8, @ptrCast(®isters[5])) },
+ .{ .key = "CP 402D", .value_type = REG.ValueType.QWORD, .value_buf = @as(*[8]u8, @ptrCast(®isters[6])) },
+ .{ .key = "CP 4030", .value_type = REG.ValueType.QWORD, .value_buf = @as(*[8]u8, @ptrCast(®isters[7])) },
+ .{ .key = "CP 4031", .value_type = REG.ValueType.QWORD, .value_buf = @as(*[8]u8, @ptrCast(®isters[8])) },
+ .{ .key = "CP 4038", .value_type = REG.ValueType.QWORD, .value_buf = @as(*[8]u8, @ptrCast(®isters[9])) },
+ .{ .key = "CP 4039", .value_type = REG.ValueType.QWORD, .value_buf = @as(*[8]u8, @ptrCast(®isters[10])) },
+ .{ .key = "CP 403A", .value_type = REG.ValueType.QWORD, .value_buf = @as(*[8]u8, @ptrCast(®isters[11])) },
}) catch break :blk null;
cores[i] = @import("arm.zig").aarch64.detectNativeCpuAndFeatures(current_arch, registers) orelse