authorgravatar for squeek502@hotmail.comRyan Liptak <squeek502@hotmail.com> 2026-02-26 23:02:11-08:00
committergravatar for squeek502@hotmail.comRyan Liptak <squeek502@hotmail.com> 2026-03-03 01:12:47-08:00
log70058471e341870dc6991cf9cb92a590a94912c2
tree8b608fd7f028d8008f6b914b8420842c3c9971b2
parent421c3c3cc57f05bf53678ee79230c66200a69081

WindowsSdk: Use ntdll instead of advapi32 APIs for registry querying

The dependency on advapi32.dll actually silently brings along 3 other dlls at runtime (msvcrt.dll, sechost.dll, bcrypt.dll), even if no advapi32 APIs are called. So, this commit actually reduces the number of dlls loaded at runtime by 4 (but only when LLVM is not linked, since LLVM has its own dependency on advapi32.dll). The data is not super conclusive, but the ntdll version of WindowsSdk appears to run slightly faster than the previous advapi32 version: Benchmark 1: libc-ntdll.exe .. Time (mean ± σ): 6.0 ms ± 0.6 ms [User: 3.9 ms, System: 7.1 ms] Range (min … max): 4.8 ms … 7.9 ms 112 runs Benchmark 2: libc-advapi32.exe .. Time (mean ± σ): 7.2 ms ± 0.5 ms [User: 5.4 ms, System: 9.2 ms] Range (min … max): 6.1 ms … 8.9 ms 103 runs Summary 'libc-ntdll.exe ..' ran 1.21 ± 0.15 times faster than 'libc-advapi32.exe ..' and this mostly seems to be due to changes in the implementation (the advapi32 APIs do a lot of NtQueryKey calls that the new implementation doesn't do) rather than due to the decrease in dll loading. LLVM-less zig binaries don't show the same reduction (the only difference here is the DLLs being loaded): Benchmark 1: stage4-ntdll\bin\zig.exe version Time (mean ± σ): 3.0 ms ± 0.6 ms [User: 5.3 ms, System: 4.8 ms] Range (min … max): 1.3 ms … 4.2 ms 112 runs Benchmark 2: stage4-advapi32\bin\zig.exe version Time (mean ± σ): 3.5 ms ± 0.6 ms [User: 6.9 ms, System: 5.5 ms] Range (min … max): 2.5 ms … 5.9 ms 111 runs Summary 'stage4-ntdll\bin\zig.exe version' ran 1.16 ± 0.28 times faster than 'stage4-advapi32\bin\zig.exe version' --- With the removal of the advapi32 dependency, the non-ntdll dependencies that remain in an LLVM-less Zig binary are ws2_32.dll (which brings along rpcrt4.dll at runtime), kernel32.dll (which brings along kernelbase.dll at runtime), and crypt32.dll (which brings along ucrtbase.dll at runtime).

4 files changed, 696 insertions(+), 426 deletions(-)

lib/std/os/windows.zig+134-28
......@@ -3506,6 +3506,16 @@ pub const GUID = extern struct {
35063506 }
35073507 return @as(GUID, @bitCast(bytes));
35083508 }
3509
3510 pub fn format(self: GUID, w: *std.Io.Writer) std.Io.Writer.Error!void {
3511 return w.print("{{{x:0>8}-{x:0>4}-{x:0>4}-{x}-{x}}}", .{
3512 self.Data1,
3513 self.Data2,
3514 self.Data3,
3515 self.Data4[0..2],
3516 self.Data4[2..8],
3517 });
3518 }
35093519};
35103520
35113521test GUID {
......@@ -3518,6 +3528,16 @@ test GUID {
35183528 },
35193529 GUID.parse("{01234567-89AB-EF10-3254-7698badcfe91}"),
35203530 );
3531 try std.testing.expectFmt(
3532 "{01234567-89ab-ef10-3254-7698badcfe91}",
3533 "{f}",
3534 .{GUID.parse("{01234567-89AB-EF10-3254-7698badcfe91}")},
3535 );
3536 try std.testing.expectFmt(
3537 "{00000001-0001-0001-0001-000000000001}",
3538 "{f}",
3539 .{GUID{ .Data1 = 1, .Data2 = 1, .Data3 = 1, .Data4 = [_]u8{ 0, 1, 0, 0, 0, 0, 0, 1 } }},
3540 );
35213541}
35223542
35233543pub const COORD = extern struct {
......@@ -3560,7 +3580,7 @@ pub const RTL_QUERY_REGISTRY_TABLE = extern struct {
35603580 Flags: ULONG,
35613581 Name: ?PWSTR,
35623582 EntryContext: ?*anyopaque,
3563 DefaultType: ULONG,
3583 DefaultType: REG.ValueType,
35643584 DefaultData: ?*anyopaque,
35653585 DefaultLength: ULONG,
35663586};
......@@ -3625,34 +3645,120 @@ pub const RTL_QUERY_REGISTRY_DELETE = 0x00000040;
36253645/// If the types do not match, the call fails.
36263646pub const RTL_QUERY_REGISTRY_TYPECHECK = 0x00000100;
36273647
3648/// REG_ is a crowded namespace with a lot of overlapping and unrelated
3649/// defines in the Windows headers, so instead of strictly following the
3650/// Windows headers names, extra namespaces are added here for clarity.
36283651pub const REG = struct {
3629 /// No value type
3630 pub const NONE: ULONG = 0;
3631 /// Unicode nul terminated string
3632 pub const SZ: ULONG = 1;
3633 /// Unicode nul terminated string (with environment variable references)
3634 pub const EXPAND_SZ: ULONG = 2;
3635 /// Free form binary
3636 pub const BINARY: ULONG = 3;
3637 /// 32-bit number
3638 pub const DWORD: ULONG = 4;
3639 /// 32-bit number (same as REG_DWORD)
3640 pub const DWORD_LITTLE_ENDIAN: ULONG = 4;
3641 /// 32-bit number
3642 pub const DWORD_BIG_ENDIAN: ULONG = 5;
3643 /// Symbolic Link (unicode)
3644 pub const LINK: ULONG = 6;
3645 /// Multiple Unicode strings
3646 pub const MULTI_SZ: ULONG = 7;
3647 /// Resource list in the resource map
3648 pub const RESOURCE_LIST: ULONG = 8;
3649 /// Resource list in the hardware description
3650 pub const FULL_RESOURCE_DESCRIPTOR: ULONG = 9;
3651 pub const RESOURCE_REQUIREMENTS_LIST: ULONG = 10;
3652 /// 64-bit number
3653 pub const QWORD: ULONG = 11;
3654 /// 64-bit number (same as REG_QWORD)
3655 pub const QWORD_LITTLE_ENDIAN: ULONG = 11;
3652 pub const ValueType = enum(ULONG) {
3653 /// No value type
3654 NONE = 0,
3655 /// Unicode nul terminated string
3656 SZ = 1,
3657 /// Unicode nul terminated string (with environment variable references)
3658 EXPAND_SZ = 2,
3659 /// Free form binary
3660 BINARY = 3,
3661 /// 32-bit number
3662 DWORD = 4,
3663 /// 32-bit number
3664 DWORD_BIG_ENDIAN = 5,
3665 /// Symbolic Link (unicode)
3666 LINK = 6,
3667 /// Multiple Unicode strings
3668 MULTI_SZ = 7,
3669 /// Resource list in the resource map
3670 RESOURCE_LIST = 8,
3671 /// Resource list in the hardware description
3672 FULL_RESOURCE_DESCRIPTOR = 9,
3673 RESOURCE_REQUIREMENTS_LIST = 10,
3674 /// 64-bit number
3675 QWORD = 11,
3676 _,
3677
3678 /// 32-bit number (same as REG_DWORD)
3679 pub const DWORD_LITTLE_ENDIAN: ValueType = .DWORD;
3680 /// 64-bit number (same as REG_QWORD)
3681 pub const QWORD_LITTLE_ENDIAN: ValueType = .QWORD;
3682 };
3683
3684 /// Used with NtOpenKeyEx, maybe others
3685 pub const OpenOptions = packed struct(ULONG) {
3686 Reserved0: u2 = 0,
3687 /// Open for backup or restore
3688 /// special access rules privilege required
3689 BACKUP_RESTORE: bool = false,
3690 /// Open symbolic link
3691 OPEN_LINK: bool = false,
3692 Reserved3: u28 = 0,
3693 };
3694
3695 /// Used with NtLoadKeyEx, maybe others
3696 pub const LoadOptions = packed struct(ULONG) {
3697 /// Restore whole hive volatile
3698 WHOLE_HIVE_VOLATILE: bool = false,
3699 /// Unwind changes to last flush
3700 REFRESH_HIVE: bool = false,
3701 /// Never lazy flush this hive
3702 NO_LAZY_FLUSH: bool = false,
3703 /// Force the restore process even when we have open handles on subkeys
3704 FORCE_RESTORE: bool = false,
3705 /// Loads the hive visible to the calling process
3706 APP_HIVE: bool = false,
3707 /// Hive cannot be mounted by any other process while in use
3708 PROCESS_PRIVATE: bool = false,
3709 /// Starts Hive Journal
3710 START_JOURNAL: bool = false,
3711 /// Grow hive file in exact 4k increments
3712 HIVE_EXACT_FILE_GROWTH: bool = false,
3713 /// No RM is started for this hive (no transactions)
3714 HIVE_NO_RM: bool = false,
3715 /// Legacy single logging is used for this hive
3716 HIVE_SINGLE_LOG: bool = false,
3717 /// This hive might be used by the OS loader
3718 BOOT_HIVE: bool = false,
3719 /// Load the hive and return a handle to its root kcb
3720 LOAD_HIVE_OPEN_HANDLE: bool = false,
3721 /// Flush changes to primary hive file size as part of all flushes
3722 FLUSH_HIVE_FILE_GROWTH: bool = false,
3723 /// Open a hive's files in read-only mode
3724 /// The same flag is used for REG_APP_HIVE_OPEN_READ_ONLY:
3725 /// Open an app hive's files in read-only mode (if the hive was not previously loaded).
3726 OPEN_READ_ONLY: bool = false,
3727 /// Load the hive, but don't allow any modification of it
3728 IMMUTABLE: bool = false,
3729 /// Do not fall back to impersonating the caller if hive file access fails
3730 NO_IMPERSONATION_FALLBACK: bool = false,
3731 Reserved16: u16 = 0,
3732 };
3733};
3734
3735pub const KEY = struct {
3736 pub const VALUE = struct {
3737 /// https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/ne-wdm-_key_value_information_class
3738 pub const INFORMATION_CLASS = enum(c_int) {
3739 Basic = 0,
3740 Full = 1,
3741 Partial = 2,
3742 FullAlign64 = 3,
3743 PartialAlign64 = 4,
3744 Layer = 5,
3745 _,
3746
3747 pub const Max: @typeInfo(@This()).@"enum".tag_type = @typeInfo(@This()).@"enum".fields.len;
3748 };
3749
3750 pub const PARTIAL_INFORMATION = extern struct {
3751 TitleIndex: ULONG,
3752 Type: REG.ValueType,
3753 DataLength: ULONG,
3754 Data: [0]UCHAR,
3755
3756 pub fn data(info: *const PARTIAL_INFORMATION) []const UCHAR {
3757 const ptr: [*]const UCHAR = @ptrCast(&info.Data);
3758 return ptr[0..info.DataLength];
3759 }
3760 };
3761 };
36563762};
36573763
36583764pub const ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x4;
lib/std/os/windows/ntdll.zig+35
......@@ -22,6 +22,7 @@ const HANDLE = windows.HANDLE;
2222const HEAP = windows.HEAP;
2323const IO_APC_ROUTINE = windows.IO_APC_ROUTINE;
2424const IO_STATUS_BLOCK = windows.IO_STATUS_BLOCK;
25const KEY = windows.KEY;
2526const KNONVOLATILE_CONTEXT_POINTERS = windows.KNONVOLATILE_CONTEXT_POINTERS;
2627const LARGE_INTEGER = windows.LARGE_INTEGER;
2728const LDR = windows.LDR;
......@@ -37,6 +38,7 @@ const PCWSTR = windows.PCWSTR;
3738const PROCESS = windows.PROCESS;
3839const PVOID = windows.PVOID;
3940const PWSTR = windows.PWSTR;
41const REG = windows.REG;
4042const RTL_OSVERSIONINFOW = windows.RTL_OSVERSIONINFOW;
4143const RTL_QUERY_REGISTRY_TABLE = windows.RTL_QUERY_REGISTRY_TABLE;
4244const RUNTIME_FUNCTION = windows.RUNTIME_FUNCTION;
......@@ -724,3 +726,36 @@ pub extern "ntdll" fn RtlWakeConditionVariable(
724726pub extern "ntdll" fn RtlWakeAllConditionVariable(
725727 ConditionVariable: *CONDITION_VARIABLE,
726728) callconv(.winapi) void;
729
730pub extern "ntdll" fn NtOpenKeyEx(
731 KeyHandle: *HANDLE,
732 DesiredAccess: ACCESS_MASK,
733 ObjectAttributes: *const OBJECT.ATTRIBUTES,
734 OpenOptions: REG.OpenOptions,
735) callconv(.winapi) NTSTATUS;
736pub extern "ntdll" fn RtlOpenCurrentUser(
737 DesiredAccess: ACCESS_MASK,
738 CurrentUserKey: *HANDLE,
739) callconv(.winapi) NTSTATUS;
740pub extern "ntdll" fn NtQueryValueKey(
741 KeyHandle: HANDLE,
742 ValueName: *const UNICODE_STRING,
743 KeyValueInformationClass: KEY.VALUE.INFORMATION_CLASS,
744 KeyValueInformation: *anyopaque,
745 /// Length of KeyValueInformation buffer in bytes
746 Length: ULONG,
747 /// On STATUS_SUCCESS, contains the length of the populated portion of the
748 /// provided buffer. On STATUS_BUFFER_OVERFLOW or STATUS_BUFFER_TOO_SMALL,
749 /// contains the minimum `Length` value that would be required to hold the information.
750 ResultLength: *ULONG,
751) callconv(.winapi) NTSTATUS;
752pub extern "ntdll" fn NtLoadKeyEx(
753 TargetKey: *const OBJECT.ATTRIBUTES,
754 SourceFile: *const OBJECT.ATTRIBUTES,
755 Flags: REG.LoadOptions,
756 TrustClassKey: ?HANDLE,
757 Event: ?HANDLE,
758 DesiredAccess: ACCESS_MASK,
759 RootHandle: ?*HANDLE,
760 Reserved: ?*anyopaque,
761) callconv(.winapi) NTSTATUS;
lib/std/zig/WindowsSdk.zig+498-369
......@@ -7,19 +7,20 @@ const Dir = std.Io.Dir;
77const Writer = std.Io.Writer;
88const Allocator = std.mem.Allocator;
99const Environ = std.process.Environ;
10const L = std.unicode.wtf8ToWtf16LeStringLiteral;
11const is_32_bit = @bitSizeOf(usize) == 32;
1012
1113windows10sdk: ?Installation,
1214windows81sdk: ?Installation,
1315msvc_lib_dir: ?[]const u8,
1416
1517const windows = std.os.windows;
16const RRF = windows.advapi32.RRF;
1718
18const windows_kits_reg_key = "SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots";
19const windows_kits_reg_key = "Microsoft\\Windows Kits\\Installed Roots";
1920
2021// https://learn.microsoft.com/en-us/windows/win32/msi/productversion
2122const version_major_minor_max_length = "255.255".len;
22// note(bratishkaerik): i think ProductVersion in registry (created by Visual Studio installer) also follows this rule
23// ProductVersion in registry (created by Visual Studio installer) probably also follows this rule
2324const product_version_max_length = version_major_minor_max_length + ".65535".len;
2425
2526/// 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(
3334) error{ OutOfMemory, NotFound, PathTooLong }!WindowsSdk {
3435 if (builtin.os.tag != .windows) return error.NotFound;
3536
36 //note(dimenus): If this key doesn't exist, neither the Win 8 SDK nor the Win 10 SDK is installed
37 const roots_key = RegistryWtf8.openKey(windows.HKEY_LOCAL_MACHINE, windows_kits_reg_key, .{ .wow64_32 = true }) catch |err| switch (err) {
37 var registry: Registry = .{};
38 defer registry.deinit();
39
40 // If this key doesn't exist, neither the Win 8 SDK nor the Win 10 SDK is installed
41 const roots_key = registry.openSoftwareKey(.{ .root = .local_machine, .wow64 = .wow64_32 }, L(windows_kits_reg_key)) catch |err| switch (err) {
3842 error.KeyNotFound => return error.NotFound,
3943 };
40 defer roots_key.closeKey();
44 defer roots_key.close();
4145
42 const windows10sdk = Installation.find(gpa, io, roots_key, "KitsRoot10", "", "v10.0") catch |err| switch (err) {
46 const windows10sdk = Installation.find(gpa, io, &registry, roots_key, L("KitsRoot10"), "", L("v10.0")) catch |err| switch (err) {
4347 error.InstallationNotFound => null,
4448 error.PathTooLong => null,
4549 error.VersionTooLong => null,
......@@ -47,7 +51,7 @@ pub fn find(
4751 };
4852 errdefer if (windows10sdk) |*w| w.free(gpa);
4953
50 const windows81sdk = Installation.find(gpa, io, roots_key, "KitsRoot81", "winver", "v8.1") catch |err| switch (err) {
54 const windows81sdk = Installation.find(gpa, io, &registry, roots_key, L("KitsRoot81"), "winver", L("v8.1")) catch |err| switch (err) {
5155 error.InstallationNotFound => null,
5256 error.PathTooLong => null,
5357 error.VersionTooLong => null,
......@@ -55,7 +59,7 @@ pub fn find(
5559 };
5660 errdefer if (windows81sdk) |*w| w.free(gpa);
5761
58 const msvc_lib_dir: ?[]const u8 = MsvcLibDir.find(gpa, io, arch, environ_map) catch |err| switch (err) {
62 const msvc_lib_dir: ?[]const u8 = MsvcLibDir.find(gpa, io, &registry, arch, environ_map) catch |err| switch (err) {
5963 error.MsvcLibDirNotFound => null,
6064 error.OutOfMemory => return error.OutOfMemory,
6165 };
......@@ -149,274 +153,306 @@ fn iterateAndFilterByVersion(
149153 return dirs.toOwnedSlice();
150154}
151155
152const OpenOptions = struct {
153 /// Sets the KEY_WOW64_32KEY access flag.
154 /// https://learn.microsoft.com/en-us/windows/win32/winprog64/accessing-an-alternate-registry-view
155 wow64_32: bool = false,
156};
157
158const RegistryWtf8 = struct {
159 key: windows.HKEY,
160
161 /// Assert that `key` is valid WTF-8 string
162 pub fn openKey(hkey: windows.HKEY, key: []const u8, options: OpenOptions) error{KeyNotFound}!RegistryWtf8 {
163 const key_wtf16le: [:0]const u16 = key_wtf16le: {
164 var key_wtf16le_buf: [RegistryWtf16Le.key_name_max_len]u16 = undefined;
165 const key_wtf16le_len: usize = std.unicode.wtf8ToWtf16Le(key_wtf16le_buf[0..], key) catch |err| switch (err) {
166 error.InvalidWtf8 => unreachable,
167 };
168 key_wtf16le_buf[key_wtf16le_len] = 0;
169 break :key_wtf16le key_wtf16le_buf[0..key_wtf16le_len :0];
170 };
171
172 const registry_wtf16le = try RegistryWtf16Le.openKey(hkey, key_wtf16le, options);
173 return .{ .key = registry_wtf16le.key };
174 }
175
176 /// Closes key, after that usage is invalid
177 pub fn closeKey(reg: RegistryWtf8) void {
178 const return_code_int: windows.HRESULT = windows.advapi32.RegCloseKey(reg.key);
179 const return_code: windows.Win32Error = @enumFromInt(return_code_int);
180 switch (return_code) {
181 .SUCCESS => {},
182 else => {},
156/// Not a general purpose implementation of an ntdll-based Registry API.
157/// Only intended to support the particular calls necessary for the purposes of finding
158/// the SDK/MSVC installation paths.
159///
160/// The advapi32 APIs internally open and cache `\Registry\Machine` and the current user
161/// key when HKEY_LOCAL_MACHINE (HKLM) and HKEY_CURRENT_USER (HKCU) are passed, and also
162/// rewrite key path values to go through WOW6432Node when appropriate.
163///
164/// For example, when opening `Software\Foo` relative to `HKEY_LOCAL_MACHINE` with
165/// the WOW64_32KEY option set, that will end up as a call to NtLoadKeyEx with the path
166/// rewritten to `Software\WOW6432Node\Foo` relative to a cached `\REGISTRY\Machine`
167/// key.
168///
169/// For our purposes, we really only care about 4 potential variations of the `Software` key:
170/// - Relative to HKLM, no redirection through WOW6432Node
171/// - Relative to HKLM, redirected through WOW6432Node
172/// - Relative to HKCU, no redirection through WOW6432Node
173/// - Relative to HKCU, redirected through WOW6432Node
174/// (e.g. all the values we care about are within one of those `Software` keys)
175///
176/// So, we cache those variants of the Software keys instead of HKLM/HKCU and treat them
177/// as the "root" keys that the user can specify, which in turn (1) allows all provided key
178/// paths to be agnostic to WOW6432Node, (2) avoids the need for internal path rewriting,
179/// and (3) works correctly on 32-bit targets without any special support.
180///
181/// For example, instead of an advapi32 call with `Software\Foo` relative to
182/// `HKEY_LOCAL_MACHINE` which may get rewritten to `Software\WOW6432Node\Foo`,
183/// the equivalent is now a call to open `Foo` relative to some Software key variant.
184const Registry = struct {
185 cache: Cache = .{},
186
187 pub fn deinit(self: Registry) void {
188 if (!is_32_bit) {
189 if (self.cache.hklm_software_foreign) |key| windows.CloseHandle(key);
190 if (self.cache.hkcu_software_foreign) |key| windows.CloseHandle(key);
183191 }
192 if (self.cache.hklm_software_native) |key| windows.CloseHandle(key);
193 if (self.cache.hkcu_software_native) |key| windows.CloseHandle(key);
194 if (self.cache.hkcu) |key| windows.CloseHandle(key);
184195 }
185196
186 /// Get string from registry.
187 /// Caller owns result.
188 pub fn getString(reg: RegistryWtf8, gpa: Allocator, subkey: []const u8, value_name: []const u8) error{ OutOfMemory, ValueNameNotFound, NotAString, StringNotFound }![]u8 {
189 const subkey_wtf16le: [:0]const u16 = subkey_wtf16le: {
190 var subkey_wtf16le_buf: [RegistryWtf16Le.key_name_max_len]u16 = undefined;
191 const subkey_wtf16le_len: usize = std.unicode.wtf8ToWtf16Le(subkey_wtf16le_buf[0..], subkey) catch unreachable;
192 subkey_wtf16le_buf[subkey_wtf16le_len] = 0;
193 break :subkey_wtf16le subkey_wtf16le_buf[0..subkey_wtf16le_len :0];
194 };
195
196 const value_name_wtf16le: [:0]const u16 = value_name_wtf16le: {
197 var value_name_wtf16le_buf: [RegistryWtf16Le.value_name_max_len]u16 = undefined;
198 const value_name_wtf16le_len: usize = std.unicode.wtf8ToWtf16Le(value_name_wtf16le_buf[0..], value_name) catch unreachable;
199 value_name_wtf16le_buf[value_name_wtf16le_len] = 0;
200 break :value_name_wtf16le value_name_wtf16le_buf[0..value_name_wtf16le_len :0];
201 };
202
203 const registry_wtf16le: RegistryWtf16Le = .{ .key = reg.key };
204 const value_wtf16le = try registry_wtf16le.getString(gpa, subkey_wtf16le, value_name_wtf16le);
205 defer gpa.free(value_wtf16le);
206
207 const value_wtf8: []u8 = try std.unicode.wtf16LeToWtf8Alloc(gpa, value_wtf16le);
208 errdefer gpa.free(value_wtf8);
209
210 return value_wtf8;
211 }
212
213 /// Get DWORD (u32) from registry.
214 pub fn getDword(reg: RegistryWtf8, subkey: []const u8, value_name: []const u8) error{ ValueNameNotFound, NotADword, DwordTooLong, DwordNotFound }!u32 {
215 const subkey_wtf16le: [:0]const u16 = subkey_wtf16le: {
216 var subkey_wtf16le_buf: [RegistryWtf16Le.key_name_max_len]u16 = undefined;
217 const subkey_wtf16le_len: usize = std.unicode.wtf8ToWtf16Le(subkey_wtf16le_buf[0..], subkey) catch unreachable;
218 subkey_wtf16le_buf[subkey_wtf16le_len] = 0;
219 break :subkey_wtf16le subkey_wtf16le_buf[0..subkey_wtf16le_len :0];
220 };
197 const Cache = struct {
198 hklm_software_foreign: if (is_32_bit) void else ?windows.HANDLE = if (is_32_bit) {} else null,
199 hkcu_software_foreign: if (is_32_bit) void else ?windows.HANDLE = if (is_32_bit) {} else null,
200 hklm_software_native: ?windows.HANDLE = null,
201 hkcu_software_native: ?windows.HANDLE = null,
202 hkcu: ?windows.HANDLE = null,
203
204 fn getSoftware(cache: *const Cache, variant: Software) ?windows.HANDLE {
205 if (!is_32_bit and variant.wow64 == .wow64_32) {
206 return switch (variant.root) {
207 .local_machine => cache.hklm_software_foreign,
208 .current_user => cache.hkcu_software_foreign,
209 };
210 }
211 return switch (variant.root) {
212 .local_machine => cache.hklm_software_native,
213 .current_user => cache.hkcu_software_native,
214 };
215 }
216 };
221217
222 const value_name_wtf16le: [:0]const u16 = value_name_wtf16le: {
223 var value_name_wtf16le_buf: [RegistryWtf16Le.value_name_max_len]u16 = undefined;
224 const value_name_wtf16le_len: usize = std.unicode.wtf8ToWtf16Le(value_name_wtf16le_buf[0..], value_name) catch unreachable;
225 value_name_wtf16le_buf[value_name_wtf16le_len] = 0;
226 break :value_name_wtf16le value_name_wtf16le_buf[0..value_name_wtf16le_len :0];
218 // This does not correspond to HKEY_LOCAL_MACHINE/HKEY_CURRENT_USER
219 // since WOW64 redirection is applicable to e.g. `HKLM\Software` instead of
220 // HKLM/HKCU directly. Since we are only ever interested in the
221 // `Software` key, it makes more sense to treat `Software` as the "root"
222 // since that allows us to work entirely with relative paths that are agnostic
223 // to WOW6432Node redirection.
224 const Software = struct {
225 root: Root,
226 wow64: Wow64 = .native,
227
228 const Root = enum {
229 local_machine,
230 current_user,
227231 };
228232
229 const registry_wtf16le: RegistryWtf16Le = .{ .key = reg.key };
230 return registry_wtf16le.getDword(subkey_wtf16le, value_name_wtf16le);
231 }
232
233 /// Under private space with flags:
234 /// KEY_QUERY_VALUE and KEY_ENUMERATE_SUB_KEYS.
235 /// After finishing work, call `closeKey`.
236 pub fn loadFromPath(absolute_path: []const u8) error{KeyNotFound}!RegistryWtf8 {
237 const absolute_path_wtf16le: [:0]const u16 = absolute_path_wtf16le: {
238 var absolute_path_wtf16le_buf: [RegistryWtf16Le.value_name_max_len]u16 = undefined;
239 const absolute_path_wtf16le_len: usize = std.unicode.wtf8ToWtf16Le(absolute_path_wtf16le_buf[0..], absolute_path) catch unreachable;
240 absolute_path_wtf16le_buf[absolute_path_wtf16le_len] = 0;
241 break :absolute_path_wtf16le absolute_path_wtf16le_buf[0..absolute_path_wtf16le_len :0];
242 };
233 fn getOrOpenKey(self: Software, registry: *Registry) !Key {
234 if (registry.cache.getSoftware(self)) |handle| {
235 return .{ .handle = handle };
236 }
243237
244 const registry_wtf16le = try RegistryWtf16Le.loadFromPath(absolute_path_wtf16le);
245 return .{ .key = registry_wtf16le.key };
246 }
247};
238 const is_foreign = !is_32_bit and self.wow64 == .wow64_32;
239 switch (self.root) {
240 .local_machine => {
241 const path = if (is_foreign) L("\\Registry\\Machine\\Software\\WOW6432Node") else L("\\Registry\\Machine\\Software");
242 var key: Key = undefined;
243 const attr: windows.OBJECT.ATTRIBUTES = .{
244 .RootDirectory = null,
245 .Attributes = .{},
246 .ObjectName = @constCast(&windows.UNICODE_STRING.init(path)),
247 .SecurityDescriptor = null,
248 .SecurityQualityOfService = null,
249 };
250 const status = windows.ntdll.NtOpenKeyEx(
251 &key.handle,
252 .{ .MAXIMUM_ALLOWED = true },
253 &attr,
254 .{},
255 );
256 switch (status) {
257 .SUCCESS => {},
258 else => return error.KeyNotFound,
259 }
260 if (is_foreign) {
261 registry.cache.hklm_software_foreign = key.handle;
262 } else {
263 registry.cache.hklm_software_native = key.handle;
264 }
265 return key;
266 },
267 .current_user => {
268 const cu_handle: windows.HANDLE = registry.cache.hkcu orelse hkcu: {
269 var cu_handle: windows.HANDLE = undefined;
270 const status = windows.ntdll.RtlOpenCurrentUser(
271 .{ .MAXIMUM_ALLOWED = true },
272 &cu_handle,
273 );
274 switch (status) {
275 .SUCCESS => {},
276 else => return error.KeyNotFound,
277 }
278 registry.cache.hkcu = cu_handle;
279 break :hkcu cu_handle;
280 };
281 const cu_key: Registry.Key = .{ .handle = cu_handle };
282 const path = if (is_foreign) L("Software\\WOW6432Node") else L("Software");
283 const key = try cu_key.open(path);
284 if (is_foreign) {
285 registry.cache.hkcu_software_foreign = key.handle;
286 } else {
287 registry.cache.hkcu_software_native = key.handle;
288 }
289 return key;
290 },
291 }
292 }
293 };
248294
249const RegistryWtf16Le = struct {
250 key: windows.HKEY,
251
252 /// Includes root key (f.e. HKEY_LOCAL_MACHINE).
253 /// https://learn.microsoft.com/en-us/windows/win32/sysinfo/registry-element-size-limits
254 pub const key_name_max_len = 255;
255 /// In Unicode characters.
256 /// https://learn.microsoft.com/en-us/windows/win32/sysinfo/registry-element-size-limits
257 pub const value_name_max_len = 16_383;
258
259 /// Under HKEY_LOCAL_MACHINE with flags:
260 /// KEY_QUERY_VALUE, KEY_ENUMERATE_SUB_KEYS, optionally KEY_WOW64_32KEY.
261 /// After finishing work, call `closeKey`.
262 fn openKey(hkey: windows.HKEY, key_wtf16le: [:0]const u16, options: OpenOptions) error{KeyNotFound}!RegistryWtf16Le {
263 var key: windows.HKEY = undefined;
264 const return_code_int: windows.HRESULT = windows.advapi32.RegOpenKeyExW(
265 hkey,
266 key_wtf16le,
267 0,
268 .{ .SPECIFIC = .{ .KEY = .{
269 .QUERY_VALUE = true,
270 .ENUMERATE_SUB_KEYS = true,
271 .WOW64_32KEY = options.wow64_32,
272 } } },
273 &key,
274 );
275 const return_code: windows.Win32Error = @enumFromInt(return_code_int);
276 switch (return_code) {
277 .SUCCESS => {},
278 .FILE_NOT_FOUND => return error.KeyNotFound,
295 /// For 32-bit programs on a 64-bit operating system, the WOW64
296 /// version of ntdll.dll handles the WOW6432Node redirection before
297 /// calling into ntdll.dll proper, so no special handling is needed
298 /// and this setting is irrelevant in that case.
299 const Wow64 = enum {
300 /// Use 64-bit registry on 64-bit targets and 32-bit registry on
301 /// 32-bit targets.
302 native,
303 /// Go through WOW6432Node on both 32-bit and 64-bit targets,
304 /// if relevant (ignored for 32-bit programs executed on a 32-bit
305 /// OS).
306 wow64_32,
307 };
279308
280 else => return error.KeyNotFound,
309 fn tryOpenSoftwareKeyWithPrecedence(registry: *Registry, variants: []const Software, sub_path: []const u16) error{KeyNotFound}!Key {
310 for (variants) |variant| {
311 return registry.openSoftwareKey(variant, sub_path) catch continue;
281312 }
282 return .{ .key = key };
313 return error.KeyNotFound;
283314 }
284315
285 /// Closes key, after that usage is invalid
286 fn closeKey(reg: RegistryWtf16Le) void {
287 const return_code_int: windows.HRESULT = windows.advapi32.RegCloseKey(reg.key);
288 const return_code: windows.Win32Error = @enumFromInt(return_code_int);
289 switch (return_code) {
290 .SUCCESS => {},
291 else => {},
292 }
316 fn openSoftwareKey(registry: *Registry, software: Software, sub_path: []const u16) error{KeyNotFound}!Key {
317 const software_key = try software.getOrOpenKey(registry);
318 return software_key.open(sub_path);
293319 }
294320
295 /// Get string ([:0]const u16) from registry.
296 fn getString(reg: RegistryWtf16Le, gpa: Allocator, subkey_wtf16le: [:0]const u16, value_name_wtf16le: [:0]const u16) error{ OutOfMemory, ValueNameNotFound, NotAString, StringNotFound }![]const u16 {
297 var actual_type: windows.ULONG = undefined;
298
299 // Calculating length to allocate
300 var value_wtf16le_buf_size: u32 = 0; // in bytes, including any terminating NUL character or characters.
301 var return_code_int: windows.HRESULT = windows.advapi32.RegGetValueW(
302 reg.key,
303 subkey_wtf16le,
304 value_name_wtf16le,
305 RRF.RT_REG_SZ,
306 &actual_type,
307 null,
308 &value_wtf16le_buf_size,
309 );
321 const Key = struct {
322 handle: windows.HANDLE,
310323
311 // Check returned code and type
312 var return_code: windows.Win32Error = @enumFromInt(return_code_int);
313 switch (return_code) {
314 .SUCCESS => std.debug.assert(value_wtf16le_buf_size != 0),
315 .MORE_DATA => unreachable, // We are only reading length
316 .FILE_NOT_FOUND => return error.ValueNameNotFound,
317 .INVALID_PARAMETER => unreachable, // We didn't combine RRF.SUBKEY_WOW6464KEY and RRF.SUBKEY_WOW6432KEY
318 else => return error.StringNotFound,
324 fn close(self: Key) void {
325 windows.CloseHandle(self.handle);
319326 }
320 switch (actual_type) {
321 windows.REG.SZ => {},
322 else => return error.NotAString,
323 }
324
325 const value_wtf16le_buf: []u16 = try gpa.alloc(u16, std.math.divCeil(u32, value_wtf16le_buf_size, 2) catch unreachable);
326 errdefer gpa.free(value_wtf16le_buf);
327
328 return_code_int = windows.advapi32.RegGetValueW(
329 reg.key,
330 subkey_wtf16le,
331 value_name_wtf16le,
332 RRF.RT_REG_SZ,
333 &actual_type,
334 value_wtf16le_buf.ptr,
335 &value_wtf16le_buf_size,
336 );
337327
338 // Check returned code and (just in case) type again.
339 return_code = @enumFromInt(return_code_int);
340 switch (return_code) {
341 .SUCCESS => {},
342 .MORE_DATA => unreachable, // Calculated first time length should be enough, even overestimated
343 .FILE_NOT_FOUND => return error.ValueNameNotFound,
344 .INVALID_PARAMETER => unreachable, // We didn't combine RRF.SUBKEY_WOW6464KEY and RRF.SUBKEY_WOW6432KEY
345 else => return error.StringNotFound,
346 }
347 switch (actual_type) {
348 windows.REG.SZ => {},
349 else => return error.NotAString,
328 fn open(self: Key, sub_path: []const u16) error{KeyNotFound}!Key {
329 var key: Key = undefined;
330 const attr: windows.OBJECT.ATTRIBUTES = .{
331 .RootDirectory = self.handle,
332 .Attributes = .{},
333 .ObjectName = @constCast(&windows.UNICODE_STRING.init(sub_path)),
334 .SecurityDescriptor = null,
335 .SecurityQualityOfService = null,
336 };
337 const status = windows.ntdll.NtOpenKeyEx(
338 &key.handle,
339 .{ .SPECIFIC = .{
340 .KEY = .{
341 .QUERY_VALUE = true,
342 .ENUMERATE_SUB_KEYS = true,
343 },
344 } },
345 &attr,
346 .{},
347 );
348 switch (status) {
349 .SUCCESS => return key,
350 else => return error.KeyNotFound,
351 }
350352 }
351353
352 const value_wtf16le: []const u16 = value_wtf16le: {
353 // note(bratishkaerik): somehow returned value in `buf_len` is overestimated by Windows and contains extra space
354 // we will just search for zero termination and forget length
355 // Windows sure is strange
356 const value_wtf16le_overestimated: [*:0]const u16 = @ptrCast(value_wtf16le_buf.ptr);
357 break :value_wtf16le std.mem.span(value_wtf16le_overestimated);
354 const ValueEntry = union(enum) {
355 default: void,
356 name: []const u16,
358357 };
359358
360 _ = gpa.resize(value_wtf16le_buf, value_wtf16le.len);
361 return value_wtf16le;
362 }
359 fn getString(
360 key: Key,
361 gpa: Allocator,
362 entry: ValueEntry,
363 comptime result_encoding: enum { wtf16, wtf8 },
364 ) error{ OutOfMemory, ValueNameNotFound, NotAString, StringNotFound }!(switch (result_encoding) {
365 .wtf8 => []u8,
366 .wtf16 => []u16,
367 }) {
368 const num_data_bytes = windows.MAX_PATH * 2;
369 const stack_buf_len = @sizeOf(windows.KEY.VALUE.PARTIAL_INFORMATION) + num_data_bytes;
370 var stack_info_buf: [stack_buf_len]u8 align(@alignOf(windows.KEY.VALUE.PARTIAL_INFORMATION)) = undefined;
371 var result_len: windows.ULONG = undefined;
372 const rc = windows.ntdll.NtQueryValueKey(
373 key.handle,
374 switch (entry) {
375 .name => |name| @constCast(&windows.UNICODE_STRING.init(name)),
376 .default => @constCast(&windows.UNICODE_STRING.empty),
377 },
378 .Partial,
379 &stack_info_buf,
380 stack_buf_len,
381 &result_len,
382 );
383 var heap_info_buf: ?[]align(@alignOf(windows.KEY.VALUE.PARTIAL_INFORMATION)) u8 = null;
384 defer if (heap_info_buf) |buf| gpa.free(buf);
385
386 const info: *const windows.KEY.VALUE.PARTIAL_INFORMATION = switch (rc) {
387 .SUCCESS => @ptrCast(&stack_info_buf),
388 .BUFFER_OVERFLOW, .BUFFER_TOO_SMALL => heap_info: {
389 heap_info_buf = try gpa.alignedAlloc(u8, .of(windows.KEY.VALUE.PARTIAL_INFORMATION), result_len);
390 const heap_rc = windows.ntdll.NtQueryValueKey(
391 key.handle,
392 switch (entry) {
393 .name => |name| @constCast(&windows.UNICODE_STRING.init(name)),
394 .default => @constCast(&windows.UNICODE_STRING.empty),
395 },
396 .Partial,
397 heap_info_buf.?.ptr,
398 @intCast(heap_info_buf.?.len),
399 &result_len,
400 );
401 switch (heap_rc) {
402 .SUCCESS => break :heap_info @ptrCast(heap_info_buf.?.ptr),
403 .OBJECT_NAME_NOT_FOUND => return error.ValueNameNotFound,
404 else => return error.StringNotFound,
405 }
406 },
407 .OBJECT_NAME_NOT_FOUND => return error.ValueNameNotFound,
408 else => return error.StringNotFound,
409 };
363410
364 /// Get DWORD (u32) from registry.
365 fn getDword(reg: RegistryWtf16Le, subkey_wtf16le: [:0]const u16, value_name_wtf16le: [:0]const u16) error{ ValueNameNotFound, NotADword, DwordTooLong, DwordNotFound }!u32 {
366 var actual_type: windows.ULONG = undefined;
367 var reg_size: u32 = @sizeOf(u32);
368 var reg_value: u32 = 0;
369
370 const return_code_int: windows.HRESULT = windows.advapi32.RegGetValueW(
371 reg.key,
372 subkey_wtf16le,
373 value_name_wtf16le,
374 RRF.RT_REG_DWORD,
375 &actual_type,
376 &reg_value,
377 &reg_size,
378 );
379 const return_code: windows.Win32Error = @enumFromInt(return_code_int);
380 switch (return_code) {
381 .SUCCESS => {},
382 .MORE_DATA => return error.DwordTooLong,
383 .FILE_NOT_FOUND => return error.ValueNameNotFound,
384 .INVALID_PARAMETER => unreachable, // We didn't combine RRF.SUBKEY_WOW6464KEY and RRF.SUBKEY_WOW6432KEY
385 else => return error.DwordNotFound,
386 }
411 switch (info.Type) {
412 .SZ => {},
413 else => return error.NotAString,
414 }
387415
388 switch (actual_type) {
389 windows.REG.DWORD => {},
390 else => return error.NotADword,
416 const data_wtf16_with_nul = @as([*]const u16, @ptrCast(@alignCast(info.data())))[0..@divExact(info.DataLength, 2)];
417 const data_wtf16 = std.mem.trimEnd(u16, data_wtf16_with_nul, L("\x00"));
418 switch (result_encoding) {
419 .wtf16 => return gpa.dupe(u16, data_wtf16),
420 .wtf8 => return std.unicode.wtf16LeToWtf8Alloc(gpa, data_wtf16),
421 }
391422 }
392423
393 return reg_value;
394 }
424 fn getDword(key: Key, entry: ValueEntry) error{ ValueNameNotFound, NotADword, DwordNotFound }!windows.DWORD {
425 const num_data_bytes = @sizeOf(windows.DWORD);
426 const buf_len = @sizeOf(windows.KEY.VALUE.PARTIAL_INFORMATION) + num_data_bytes;
427 var info_buf: [buf_len]u8 align(@alignOf(windows.KEY.VALUE.PARTIAL_INFORMATION)) = undefined;
428 var result_len: windows.ULONG = undefined;
429 const rc = windows.ntdll.NtQueryValueKey(
430 key.handle,
431 switch (entry) {
432 .name => |name| @constCast(&windows.UNICODE_STRING.init(name)),
433 .default => @constCast(&windows.UNICODE_STRING.empty),
434 },
435 .Partial,
436 &info_buf,
437 buf_len,
438 &result_len,
439 );
440 switch (rc) {
441 .SUCCESS => {},
442 .OBJECT_NAME_NOT_FOUND => return error.ValueNameNotFound,
443 else => return error.DwordNotFound,
444 }
395445
396 /// Under private space with flags:
397 /// KEY_QUERY_VALUE and KEY_ENUMERATE_SUB_KEYS.
398 /// After finishing work, call `closeKey`.
399 fn loadFromPath(absolute_path_as_wtf16le: [:0]const u16) error{KeyNotFound}!RegistryWtf16Le {
400 var key: windows.HKEY = undefined;
401
402 const return_code_int: windows.HRESULT = std.os.windows.advapi32.RegLoadAppKeyW(
403 absolute_path_as_wtf16le,
404 &key,
405 .{ .SPECIFIC = .{ .KEY = .{
406 .QUERY_VALUE = true,
407 .ENUMERATE_SUB_KEYS = true,
408 } } },
409 0,
410 0,
411 );
412 const return_code: windows.Win32Error = @enumFromInt(return_code_int);
413 switch (return_code) {
414 .SUCCESS => {},
415 else => return error.KeyNotFound,
416 }
446 const info: *const windows.KEY.VALUE.PARTIAL_INFORMATION = @ptrCast(&info_buf);
417447
418 return .{ .key = key };
419 }
448 switch (info.Type) {
449 .DWORD => {},
450 else => return error.NotADword,
451 }
452
453 return std.mem.bytesToValue(windows.DWORD, info.data());
454 }
455 };
420456};
421457
422458pub const Installation = struct {
......@@ -428,20 +464,21 @@ pub const Installation = struct {
428464 fn find(
429465 gpa: Allocator,
430466 io: Io,
431 roots_key: RegistryWtf8,
432 roots_subkey: []const u8,
467 registry: *Registry,
468 roots_key: Registry.Key,
469 roots_subkey: []const u16,
433470 prefix: []const u8,
434 version_key_name: []const u8,
471 version_key_name: []const u16,
435472 ) error{ OutOfMemory, InstallationNotFound, PathTooLong, VersionTooLong }!Installation {
436473 roots: {
437474 const installation = findFromRoot(gpa, io, roots_key, roots_subkey, prefix) catch
438475 break :roots;
439 if (installation.isValidVersion()) return installation;
476 if (installation.isValidVersion(roots_key)) return installation;
440477 installation.free(gpa);
441478 }
442479 {
443 const installation = try findFromInstallationFolder(gpa, version_key_name);
444 if (installation.isValidVersion()) return installation;
480 const installation = try findFromInstallationFolder(gpa, registry, version_key_name);
481 if (installation.isValidVersion(roots_key)) return installation;
445482 installation.free(gpa);
446483 }
447484 return error.InstallationNotFound;
......@@ -450,29 +487,27 @@ pub const Installation = struct {
450487 fn findFromRoot(
451488 gpa: Allocator,
452489 io: Io,
453 roots_key: RegistryWtf8,
454 roots_subkey: []const u8,
490 roots_key: Registry.Key,
491 roots_subkey: []const u16,
455492 prefix: []const u8,
456493 ) error{ OutOfMemory, InstallationNotFound, PathTooLong, VersionTooLong }!Installation {
457494 const path = path: {
458 const path_maybe_with_trailing_slash = roots_key.getString(gpa, "", roots_subkey) catch |err| switch (err) {
459 error.NotAString => return error.InstallationNotFound,
460 error.ValueNameNotFound => return error.InstallationNotFound,
461 error.StringNotFound => return error.InstallationNotFound,
495 const path_w_maybe_with_trailing_slash = roots_key.getString(gpa, .{ .name = roots_subkey }, .wtf16) catch |err| switch (err) {
496 error.NotAString,
497 error.ValueNameNotFound,
498 error.StringNotFound,
499 => return error.InstallationNotFound,
462500
463501 error.OutOfMemory => return error.OutOfMemory,
464502 };
465 if (path_maybe_with_trailing_slash.len > Dir.max_path_bytes or !Dir.path.isAbsolute(path_maybe_with_trailing_slash)) {
466 gpa.free(path_maybe_with_trailing_slash);
467 return error.PathTooLong;
468 }
503 defer gpa.free(path_w_maybe_with_trailing_slash);
469504
470 var path = std.array_list.Managed(u8).fromOwnedSlice(gpa, path_maybe_with_trailing_slash);
471 errdefer path.deinit();
505 if (!std.fs.path.isAbsoluteWindowsWtf16(path_w_maybe_with_trailing_slash)) {
506 return error.InstallationNotFound;
507 }
472508
473 // String might contain trailing slash, so trim it here
474 if (path.items.len > "C:\\".len and path.getLast() == '\\') _ = path.pop();
475 break :path try path.toOwnedSlice();
509 const path_w = std.mem.trimEnd(u16, path_w_maybe_with_trailing_slash, L("\\/"));
510 break :path try std.unicode.wtf16LeToWtf8Alloc(gpa, path_w);
476511 };
477512 errdefer gpa.free(path);
478513
......@@ -508,70 +543,71 @@ pub const Installation = struct {
508543
509544 fn findFromInstallationFolder(
510545 gpa: Allocator,
511 version_key_name: []const u8,
546 registry: *Registry,
547 version_key_name: []const u16,
512548 ) error{ OutOfMemory, InstallationNotFound, PathTooLong, VersionTooLong }!Installation {
513 var key_name_buf: [RegistryWtf16Le.key_name_max_len]u8 = undefined;
514 const key_name = std.fmt.bufPrint(
515 &key_name_buf,
516 "SOFTWARE\\Microsoft\\Microsoft SDKs\\Windows\\{s}",
517 .{version_key_name},
518 ) catch unreachable;
519 const key = key: for ([_]bool{ true, false }) |wow6432node| {
520 for ([_]windows.HKEY{ windows.HKEY_LOCAL_MACHINE, windows.HKEY_CURRENT_USER }) |hkey| {
521 break :key RegistryWtf8.openKey(hkey, key_name, .{ .wow64_32 = wow6432node }) catch |err| switch (err) {
522 error.KeyNotFound => return error.InstallationNotFound,
523 };
524 }
525 } else return error.InstallationNotFound;
526 defer key.closeKey();
549 const key_name = try std.mem.concat(gpa, u16, &.{ L("Microsoft\\Microsoft SDKs\\Windows\\"), version_key_name });
550 defer gpa.free(key_name);
551
552 const key = registry.tryOpenSoftwareKeyWithPrecedence(switch (is_32_bit) {
553 true => &.{
554 .{ .root = .local_machine },
555 .{ .root = .current_user },
556 },
557 false => &.{
558 .{ .root = .local_machine, .wow64 = .wow64_32 },
559 .{ .root = .current_user, .wow64 = .wow64_32 },
560 .{ .root = .local_machine, .wow64 = .native },
561 .{ .root = .current_user, .wow64 = .native },
562 },
563 }, key_name) catch {
564 return error.InstallationNotFound;
565 };
566 defer key.close();
527567
528568 const path: []const u8 = path: {
529 const path_maybe_with_trailing_slash = key.getString(gpa, "", "InstallationFolder") catch |err| switch (err) {
530 error.NotAString => return error.InstallationNotFound,
531 error.ValueNameNotFound => return error.InstallationNotFound,
532 error.StringNotFound => return error.InstallationNotFound,
569 const path_w_maybe_with_trailing_slash = key.getString(gpa, .{ .name = L("InstallationFolder") }, .wtf16) catch |err| switch (err) {
570 error.NotAString,
571 error.ValueNameNotFound,
572 error.StringNotFound,
573 => return error.InstallationNotFound,
533574
534575 error.OutOfMemory => return error.OutOfMemory,
535576 };
577 defer gpa.free(path_w_maybe_with_trailing_slash);
536578
537 if (path_maybe_with_trailing_slash.len > Dir.max_path_bytes or !Dir.path.isAbsolute(path_maybe_with_trailing_slash)) {
538 gpa.free(path_maybe_with_trailing_slash);
539 return error.PathTooLong;
579 if (!std.fs.path.isAbsoluteWindowsWtf16(path_w_maybe_with_trailing_slash)) {
580 return error.InstallationNotFound;
540581 }
541582
542 var path = std.array_list.Managed(u8).fromOwnedSlice(gpa, path_maybe_with_trailing_slash);
543 errdefer path.deinit();
544
545 // String might contain trailing slash, so trim it here
546 if (path.items.len > "C:\\".len and path.getLast() == '\\') _ = path.pop();
547
548 const path_without_trailing_slash = try path.toOwnedSlice();
549 break :path path_without_trailing_slash;
583 const path_w = std.mem.trimEnd(u16, path_w_maybe_with_trailing_slash, L("\\/"));
584 break :path try std.unicode.wtf16LeToWtf8Alloc(gpa, path_w);
550585 };
551586 errdefer gpa.free(path);
552587
553588 const version: []const u8 = version: {
554
555 // note(dimenus): Microsoft doesn't include the .0 in the ProductVersion key....
556 const version_without_0 = key.getString(gpa, "", "ProductVersion") catch |err| switch (err) {
557 error.NotAString => return error.InstallationNotFound,
558 error.ValueNameNotFound => return error.InstallationNotFound,
559 error.StringNotFound => return error.InstallationNotFound,
589 // Microsoft doesn't include the .0 in the ProductVersion key
590 const version_without_0 = key.getString(gpa, .{ .name = L("ProductVersion") }, .wtf16) catch |err| switch (err) {
591 error.NotAString,
592 error.ValueNameNotFound,
593 error.StringNotFound,
594 => return error.InstallationNotFound,
560595
561596 error.OutOfMemory => return error.OutOfMemory,
562597 };
598 defer gpa.free(version_without_0);
599
563600 if (version_without_0.len + ".0".len > product_version_max_length) {
564 gpa.free(version_without_0);
565601 return error.VersionTooLong;
566602 }
567603
568 var version = std.array_list.Managed(u8).fromOwnedSlice(gpa, version_without_0);
604 var version: std.array_list.Managed(u8) = try .initCapacity(gpa, version_without_0.len + 2);
569605 errdefer version.deinit();
570606
607 try std.unicode.wtf16LeToWtf8ArrayList(&version, version_without_0);
571608 try version.appendSlice(".0");
572609
573 const version_with_0 = try version.toOwnedSlice();
574 break :version version_with_0;
610 break :version try version.toOwnedSlice();
575611 };
576612 errdefer gpa.free(version);
577613
......@@ -579,23 +615,22 @@ pub const Installation = struct {
579615 }
580616
581617 /// Check whether this version is enumerated in registry.
582 fn isValidVersion(installation: Installation) bool {
583 var buf: [Dir.max_path_bytes]u8 = undefined;
584 const reg_query_as_wtf8 = std.fmt.bufPrint(buf[0..], "{s}\\{s}\\Installed Options", .{
585 windows_kits_reg_key,
586 installation.version,
587 }) catch |err| switch (err) {
588 error.NoSpaceLeft => return false,
589 };
590
591 const options_key = RegistryWtf8.openKey(
592 windows.HKEY_LOCAL_MACHINE,
593 reg_query_as_wtf8,
594 .{ .wow64_32 = true },
595 ) catch |err| switch (err) {
618 fn isValidVersion(installation: Installation, roots_key: Registry.Key) bool {
619 var version_buf: [product_version_max_length]u16 = undefined;
620 const version_len = std.unicode.wtf8ToWtf16Le(&version_buf, installation.version) catch return false;
621 const version = version_buf[0..version_len];
622 const options_key_name = "Installed Options";
623 const buf_len = product_version_max_length + options_key_name.len + 2;
624 var buf: [buf_len]u16 = undefined;
625 var query: std.ArrayList(u16) = .initBuffer(&buf);
626 query.appendSliceAssumeCapacity(version);
627 query.appendAssumeCapacity('\\');
628 query.appendSliceAssumeCapacity(L(options_key_name));
629
630 const options_key = roots_key.open(query.items) catch |err| switch (err) {
596631 error.KeyNotFound => return false,
597632 };
598 defer options_key.closeKey();
633 defer options_key.close();
599634
600635 const option_name = comptime switch (builtin.target.cpu.arch) {
601636 .thumb => "OptionId.DesktopCPParm",
......@@ -605,7 +640,7 @@ pub const Installation = struct {
605640 else => |tag| @compileError("Windows SDK cannot be detected on architecture " ++ tag),
606641 };
607642
608 const reg_value = options_key.getDword("", option_name) catch return false;
643 const reg_value = options_key.getDword(.{ .name = L(option_name) }) catch return false;
609644 return (reg_value == 1);
610645 }
611646
......@@ -616,14 +651,14 @@ pub const Installation = struct {
616651};
617652
618653const MsvcLibDir = struct {
619 fn findInstancesDirViaSetup(gpa: Allocator, io: Io) error{ OutOfMemory, PathNotFound }!Dir {
620 const vs_setup_key_path = "SOFTWARE\\Microsoft\\VisualStudio\\Setup";
621 const vs_setup_key = RegistryWtf8.openKey(windows.HKEY_LOCAL_MACHINE, vs_setup_key_path, .{}) catch |err| switch (err) {
654 fn findInstancesDirViaSetup(gpa: Allocator, io: Io, registry: *Registry) error{ OutOfMemory, PathNotFound }!Dir {
655 const vs_setup_key_path = L("Microsoft\\VisualStudio\\Setup");
656 const vs_setup_key = registry.openSoftwareKey(.{ .root = .local_machine }, vs_setup_key_path) catch |err| switch (err) {
622657 error.KeyNotFound => return error.PathNotFound,
623658 };
624 defer vs_setup_key.closeKey();
659 defer vs_setup_key.close();
625660
626 const packages_path = vs_setup_key.getString(gpa, "", "CachePath") catch |err| switch (err) {
661 const packages_path = vs_setup_key.getString(gpa, .{ .name = L("CachePath") }, .wtf8) catch |err| switch (err) {
627662 error.NotAString,
628663 error.ValueNameNotFound,
629664 error.StringNotFound,
......@@ -633,22 +668,41 @@ const MsvcLibDir = struct {
633668 };
634669 defer gpa.free(packages_path);
635670
636 if (!Dir.path.isAbsolute(packages_path)) return error.PathNotFound;
671 if (!std.fs.path.isAbsolute(packages_path)) return error.PathNotFound;
637672
638 const instances_path = try Dir.path.join(gpa, &.{ packages_path, "_Instances" });
673 const instances_path = try std.fs.path.join(gpa, &.{ packages_path, "_Instances" });
639674 defer gpa.free(instances_path);
640675
641676 return Dir.openDirAbsolute(io, instances_path, .{ .iterate = true }) catch return error.PathNotFound;
642677 }
643678
644 fn findInstancesDirViaCLSID(gpa: Allocator, io: Io) error{ OutOfMemory, PathNotFound }!Dir {
679 fn findInstancesDirViaCLSID(gpa: Allocator, io: Io, registry: *Registry) error{ OutOfMemory, PathNotFound }!Dir {
645680 const setup_configuration_clsid = "{177f0c4a-1cd3-4de7-a32c-71dbbb9fa36d}";
646 const setup_config_key = RegistryWtf8.openKey(windows.HKEY_CLASSES_ROOT, "CLSID\\" ++ setup_configuration_clsid, .{}) catch |err| switch (err) {
681
682 // HKEY_CLASSES_ROOT is not a single key but instead a combination of
683 // HKCU\Software\Classes and HKLM\Software\Classes with HKCU taking precedent
684 // https://learn.microsoft.com/en-us/windows/win32/sysinfo/hkey-classes-root-key
685 //
686 // Instead of a CLASSES_ROOT abstraction, we emulate the behavior with a more
687 // general abstraction, which also means we need to include `Classes` in the path since
688 // we're starting from the `Software` keys instead of the "classes root".
689 //
690 // The advapi32 APIs with `HKEY_CLASSES_ROOT` go through `\REGISTRY\USER\<SID>_Classes`
691 // instead of `\REGISTRY\USER\<SID>\Software\Classes`, but we go through the latter
692 // because it allows us to take advantage of `RtlOpenCurrentUser` to avoid needing to implement
693 // the logic for getting the current user registry path, and it appears that the two keys are
694 // effectively equivalent. Further investigation of the relationship of these keys would probably
695 // be beneficial, though.
696 const setup_config_key = registry.tryOpenSoftwareKeyWithPrecedence(&.{
697 .{ .root = .current_user },
698 .{ .root = .local_machine },
699 }, L("Classes\\CLSID\\" ++ setup_configuration_clsid)) catch |err| switch (err) {
647700 error.KeyNotFound => return error.PathNotFound,
648701 };
649 defer setup_config_key.closeKey();
702 defer setup_config_key.close();
650703
651 const dll_path = setup_config_key.getString(gpa, "InprocServer32", "") catch |err| switch (err) {
704 const inproc_server = setup_config_key.open(L("InprocServer32")) catch return error.PathNotFound;
705 const dll_path = inproc_server.getString(gpa, .default, .wtf8) catch |err| switch (err) {
652706 error.NotAString,
653707 error.ValueNameNotFound,
654708 error.StringNotFound,
......@@ -658,9 +712,9 @@ const MsvcLibDir = struct {
658712 };
659713 defer gpa.free(dll_path);
660714
661 if (!Dir.path.isAbsolute(dll_path)) return error.PathNotFound;
715 if (!std.fs.path.isAbsolute(dll_path)) return error.PathNotFound;
662716
663 var path_it = Dir.path.componentIterator(dll_path);
717 var path_it = std.fs.path.componentIterator(dll_path);
664718 // the .dll filename
665719 _ = path_it.last();
666720 const root_path = while (path_it.previous()) |dir_component| {
......@@ -671,7 +725,7 @@ const MsvcLibDir = struct {
671725 return error.PathNotFound;
672726 };
673727
674 const instances_path = try Dir.path.join(gpa, &.{ root_path, "Packages", "_Instances" });
728 const instances_path = try std.fs.path.join(gpa, &.{ root_path, "Packages", "_Instances" });
675729 defer gpa.free(instances_path);
676730
677731 return Dir.openDirAbsolute(io, instances_path, .{ .iterate = true }) catch return error.PathNotFound;
......@@ -680,12 +734,13 @@ const MsvcLibDir = struct {
680734 fn findInstancesDir(
681735 gpa: Allocator,
682736 io: Io,
737 registry: *Registry,
683738 environ_map: *const Environ.Map,
684739 ) error{ OutOfMemory, PathNotFound }!Dir {
685740 // First, try getting the packages cache path from the registry.
686741 // This only seems to exist when the path is different from the default.
687742 method1: {
688 return findInstancesDirViaSetup(gpa, io) catch |err| switch (err) {
743 return findInstancesDirViaSetup(gpa, io, registry) catch |err| switch (err) {
689744 error.OutOfMemory => |e| return e,
690745 error.PathNotFound => break :method1,
691746 };
......@@ -693,7 +748,7 @@ const MsvcLibDir = struct {
693748 // Otherwise, try to get the path from the .dll that would have been
694749 // loaded via COM for SetupConfiguration.
695750 method2: {
696 return findInstancesDirViaCLSID(gpa, io) catch |err| switch (err) {
751 return findInstancesDirViaCLSID(gpa, io, registry) catch |err| switch (err) {
697752 error.OutOfMemory => |e| return e,
698753 error.PathNotFound => break :method2,
699754 };
......@@ -703,7 +758,7 @@ const MsvcLibDir = struct {
703758 method3: {
704759 const program_data = std.zig.EnvVar.PROGRAMDATA.get(environ_map) orelse break :method3;
705760
706 if (!Dir.path.isAbsolute(program_data)) break :method3;
761 if (!std.fs.path.isAbsolute(program_data)) break :method3;
707762
708763 const instances_path = try Dir.path.join(gpa, &.{
709764 program_data, "Microsoft", "VisualStudio", "Packages", "_Instances",
......@@ -764,6 +819,7 @@ const MsvcLibDir = struct {
764819 fn findViaCOM(
765820 gpa: Allocator,
766821 io: Io,
822 registry: *Registry,
767823 arch: std.Target.Cpu.Arch,
768824 environ_map: *const Environ.Map,
769825 ) error{ OutOfMemory, PathNotFound }![]const u8 {
......@@ -771,7 +827,7 @@ const MsvcLibDir = struct {
771827 // This will contain directories with names of instance IDs like 80a758ca,
772828 // which will contain `state.json` files that have the version and
773829 // installation directory.
774 var instances_dir = try findInstancesDir(gpa, io, environ_map);
830 var instances_dir = try findInstancesDir(gpa, io, registry, environ_map);
775831 defer instances_dir.close(io);
776832
777833 var state_subpath_buf: [Dir.max_name_bytes + 32]u8 = undefined;
......@@ -874,7 +930,6 @@ const MsvcLibDir = struct {
874930 arch: std.Target.Cpu.Arch,
875931 environ_map: *const Environ.Map,
876932 ) error{ OutOfMemory, PathNotFound }![]const u8 {
877
878933 // %localappdata%\Microsoft\VisualStudio\
879934 // %appdata%\Local\Microsoft\VisualStudio\
880935 const local_app_data_path = std.zig.EnvVar.LOCALAPPDATA.get(environ_map) orelse return error.PathNotFound;
......@@ -883,15 +938,18 @@ const MsvcLibDir = struct {
883938 });
884939 defer gpa.free(visualstudio_folder_path);
885940
941 if (!Dir.path.isAbsolute(visualstudio_folder_path)) return error.PathNotFound;
942 // To make things easier later on, we open the VisualStudio directory here which
943 // allows us to pass relative paths to NtLoadKeyEx in order to avoid dealing with
944 // conversion to NT namespace paths.
945 var visualstudio_folder = Dir.openDirAbsolute(io, visualstudio_folder_path, .{
946 .iterate = true,
947 }) catch return error.PathNotFound;
948 defer visualstudio_folder.close(io);
949
886950 const vs_versions: []const []const u8 = vs_versions: {
887 if (!Dir.path.isAbsolute(visualstudio_folder_path)) return error.PathNotFound;
888951 // enumerate folders that contain `privateregistry.bin`, looking for all versions
889952 // f.i. %localappdata%\Microsoft\VisualStudio\17.0_9e9cbb98\
890 var visualstudio_folder = Dir.openDirAbsolute(io, visualstudio_folder_path, .{
891 .iterate = true,
892 }) catch return error.PathNotFound;
893 defer visualstudio_folder.close(io);
894
895953 var iterator = visualstudio_folder.iterate();
896954 break :vs_versions try iterateAndFilterByVersion(&iterator, gpa, io, "");
897955 };
......@@ -899,25 +957,94 @@ const MsvcLibDir = struct {
899957 for (vs_versions) |vs_version| gpa.free(vs_version);
900958 gpa.free(vs_versions);
901959 }
902 var config_subkey_buf: [RegistryWtf16Le.key_name_max_len * 2]u8 = undefined;
960 var key_path_buf: [windows.NAME_MAX * 2]u16 = undefined;
961 var sub_path_buf: [windows.NAME_MAX * 2]u16 = undefined;
903962 const source_directories: []const u8 = source_directories: for (vs_versions) |vs_version| {
904 const privateregistry_absolute_path = Dir.path.join(gpa, &.{ visualstudio_folder_path, vs_version, "privateregistry.bin" }) catch continue;
905 defer gpa.free(privateregistry_absolute_path);
906 if (!Dir.path.isAbsolute(privateregistry_absolute_path)) continue;
963 const sub_path = blk: {
964 var buf: std.ArrayList(u16) = .initBuffer(&sub_path_buf);
965 buf.items.len += std.unicode.wtf8ToWtf16Le(buf.unusedCapacitySlice(), vs_version) catch unreachable;
966 buf.appendSliceAssumeCapacity(L("\\privateregistry.bin"));
967 break :blk buf.items;
968 };
907969
908 const visualstudio_registry = RegistryWtf8.loadFromPath(privateregistry_absolute_path) catch continue;
909 defer visualstudio_registry.closeKey();
970 // The goal is to emulate advapi32.RegLoadAppKeyW with a direct call
971 // to NtLoadKeyEx instead.
972 //
973 // RegLoadAppKeyW loads the hive into a registry key of the format:
974 // \REGISTRY\A\{fdb2baa5-8ca8-ef03-78d0-3b1f868fd2a9}
975 // where `\REGISTRY\A` is a special unenumerable location intended for
976 // per-app hives, and the GUID is randomly generated (in testing, it
977 // was different for each run of the program).
978 //
979 // The OS is responsible for cleaning up `\REGISTRY\A` whenever all handles
980 // to one of its keys are closed, so we don't have to do anything special
981 // with regards to that.
982
983 const temp_key_path = blk: {
984 var guid: windows.GUID = undefined;
985 io.random(std.mem.asBytes(&guid));
986
987 var guid_buf: [38]u8 = undefined;
988 const guid_str = std.fmt.bufPrint(&guid_buf, "{f}", .{guid}) catch unreachable;
989
990 var buf: std.ArrayList(u16) = .initBuffer(&key_path_buf);
991 buf.appendSliceAssumeCapacity(L("\\REGISTRY\\A\\"));
992 buf.items.len += std.unicode.wtf8ToWtf16Le(buf.unusedCapacitySlice(), guid_str) catch unreachable;
993 break :blk buf.items;
994 };
910995
911 const config_subkey = std.fmt.bufPrint(config_subkey_buf[0..], "Software\\Microsoft\\VisualStudio\\{s}_Config", .{vs_version}) catch unreachable;
996 const target_key: windows.OBJECT.ATTRIBUTES = .{
997 .RootDirectory = null,
998 .Attributes = .{},
999 .ObjectName = @constCast(&windows.UNICODE_STRING.init(temp_key_path)),
1000 .SecurityDescriptor = null,
1001 };
1002 const source_file: windows.OBJECT.ATTRIBUTES = .{
1003 .RootDirectory = visualstudio_folder.handle,
1004 .Attributes = .{},
1005 .ObjectName = @constCast(&windows.UNICODE_STRING.init(sub_path)),
1006 .SecurityDescriptor = null,
1007 };
1008 var root_key: Registry.Key = undefined;
1009 const rc = windows.ntdll.NtLoadKeyEx(
1010 &target_key,
1011 &source_file,
1012 .{
1013 .APP_HIVE = true,
1014 // This wasn't set by RegLoadAppKeyW, but it seems relevant
1015 // since we aren't intending to do any modifcation of the hive.
1016 .OPEN_READ_ONLY = true,
1017 },
1018 null,
1019 null,
1020 .{ .SPECIFIC = .{
1021 .KEY = .{
1022 .QUERY_VALUE = true,
1023 .ENUMERATE_SUB_KEYS = true,
1024 },
1025 } },
1026 &root_key.handle,
1027 null,
1028 );
1029 switch (rc) {
1030 .SUCCESS => {},
1031 else => continue,
1032 }
1033 defer root_key.close();
1034
1035 const config_path = blk: {
1036 var buf: std.ArrayList(u16) = .initBuffer(&key_path_buf);
1037 buf.appendSliceAssumeCapacity(L("Software\\Microsoft\\VisualStudio\\"));
1038 buf.items.len += std.unicode.wtf8ToWtf16Le(buf.unusedCapacitySlice(), vs_version) catch unreachable;
1039 buf.appendSliceAssumeCapacity(L("_Config"));
1040 break :blk buf.items;
1041 };
1042 const config_key = root_key.open(config_path) catch continue;
9121043
913 const source_directories_value = visualstudio_registry.getString(gpa, config_subkey, "Source Directories") catch |err| switch (err) {
1044 const source_directories_value = config_key.getString(gpa, .{ .name = L("Source Directories") }, .wtf8) catch |err| switch (err) {
9141045 error.OutOfMemory => return error.OutOfMemory,
9151046 else => continue,
9161047 };
917 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
918 gpa.free(source_directories_value);
919 continue;
920 }
9211048
9221049 break :source_directories source_directories_value;
9231050 } else return error.PathNotFound;
......@@ -967,6 +1094,7 @@ const MsvcLibDir = struct {
9671094 fn findViaVs7Key(
9681095 gpa: Allocator,
9691096 io: Io,
1097 registry: *Registry,
9701098 arch: std.Target.Cpu.Arch,
9711099 environ_map: *const Environ.Map,
9721100 ) error{ OutOfMemory, PathNotFound }![]const u8 {
......@@ -986,10 +1114,10 @@ const MsvcLibDir = struct {
9861114 }
9871115 }
9881116
989 const vs7_key = RegistryWtf8.openKey(windows.HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\VisualStudio\\SxS\\VS7", .{ .wow64_32 = true }) catch return error.PathNotFound;
990 defer vs7_key.closeKey();
1117 const vs7_key = registry.openSoftwareKey(.{ .root = .local_machine, .wow64 = .wow64_32 }, L("Microsoft\\VisualStudio\\SxS\\VS7")) catch return error.PathNotFound;
1118 defer vs7_key.close();
9911119 try_vs7_key: {
992 const path_maybe_with_trailing_slash = vs7_key.getString(gpa, "", "14.0") catch |err| switch (err) {
1120 const path_maybe_with_trailing_slash = vs7_key.getString(gpa, .{ .name = L("14.0") }, .wtf8) catch |err| switch (err) {
9931121 error.OutOfMemory => return error.OutOfMemory,
9941122 else => break :try_vs7_key,
9951123 };
......@@ -1045,14 +1173,15 @@ const MsvcLibDir = struct {
10451173 pub fn find(
10461174 gpa: Allocator,
10471175 io: Io,
1176 registry: *Registry,
10481177 arch: std.Target.Cpu.Arch,
10491178 environ_map: *const Environ.Map,
10501179 ) error{ OutOfMemory, MsvcLibDirNotFound }![]const u8 {
1051 const full_path = MsvcLibDir.findViaCOM(gpa, io, arch, environ_map) catch |err1| switch (err1) {
1180 const full_path = MsvcLibDir.findViaCOM(gpa, io, registry, arch, environ_map) catch |err1| switch (err1) {
10521181 error.OutOfMemory => return error.OutOfMemory,
10531182 error.PathNotFound => MsvcLibDir.findViaRegistry(gpa, io, arch, environ_map) catch |err2| switch (err2) {
10541183 error.OutOfMemory => return error.OutOfMemory,
1055 error.PathNotFound => MsvcLibDir.findViaVs7Key(gpa, io, arch, environ_map) catch |err3| switch (err3) {
1184 error.PathNotFound => MsvcLibDir.findViaVs7Key(gpa, io, registry, arch, environ_map) catch |err3| switch (err3) {
10561185 error.OutOfMemory => return error.OutOfMemory,
10571186 error.PathNotFound => return error.MsvcLibDirNotFound,
10581187 },
lib/std/zig/system/windows.zig+29-29
......@@ -85,7 +85,7 @@ fn getCpuInfoFromRegistry(core: usize, args: anytype) !void {
8585 .Flags = std.os.windows.RTL_QUERY_REGISTRY_SUBKEY | std.os.windows.RTL_QUERY_REGISTRY_REQUIRED,
8686 .Name = subkey[0..subkey_len :0],
8787 .EntryContext = null,
88 .DefaultType = REG.NONE,
88 .DefaultType = .NONE,
8989 .DefaultData = null,
9090 .DefaultLength = 0,
9191 };
......@@ -95,9 +95,9 @@ fn getCpuInfoFromRegistry(core: usize, args: anytype) !void {
9595 inline for (fields_info, 0..) |field, i| {
9696 const ctx: *anyopaque = blk: {
9797 switch (@field(args, field.name).value_type) {
98 REG.SZ,
99 REG.EXPAND_SZ,
100 REG.MULTI_SZ,
98 .SZ,
99 .EXPAND_SZ,
100 .MULTI_SZ,
101101 => {
102102 comptime assert(@sizeOf(std.os.windows.UNICODE_STRING) % 2 == 0);
103103 const unicode: *std.os.windows.UNICODE_STRING = @ptrCast(&tmp_bufs[i]);
......@@ -109,9 +109,9 @@ fn getCpuInfoFromRegistry(core: usize, args: anytype) !void {
109109 break :blk unicode;
110110 },
111111
112 REG.DWORD,
113 REG.DWORD_BIG_ENDIAN,
114 REG.QWORD,
112 .DWORD,
113 .DWORD_BIG_ENDIAN,
114 .QWORD,
115115 => break :blk &tmp_bufs[i],
116116
117117 else => unreachable,
......@@ -127,7 +127,7 @@ fn getCpuInfoFromRegistry(core: usize, args: anytype) !void {
127127 .Flags = std.os.windows.RTL_QUERY_REGISTRY_DIRECT | std.os.windows.RTL_QUERY_REGISTRY_REQUIRED,
128128 .Name = key_buf[0..key_len :0],
129129 .EntryContext = ctx,
130 .DefaultType = REG.NONE,
130 .DefaultType = .NONE,
131131 .DefaultData = null,
132132 .DefaultLength = 0,
133133 };
......@@ -139,7 +139,7 @@ fn getCpuInfoFromRegistry(core: usize, args: anytype) !void {
139139 .Flags = 0,
140140 .Name = null,
141141 .EntryContext = null,
142 .DefaultType = 0,
142 .DefaultType = .NONE,
143143 .DefaultData = null,
144144 .DefaultLength = 0,
145145 };
......@@ -154,9 +154,9 @@ fn getCpuInfoFromRegistry(core: usize, args: anytype) !void {
154154 switch (res) {
155155 .SUCCESS => {
156156 inline for (fields_info, 0..) |field, i| switch (@field(args, field.name).value_type) {
157 REG.SZ,
158 REG.EXPAND_SZ,
159 REG.MULTI_SZ,
157 .SZ,
158 .EXPAND_SZ,
159 .MULTI_SZ,
160160 => {
161161 var buf = @field(args, field.name).value_buf;
162162 const entry: *const std.os.windows.UNICODE_STRING = @ptrCast(table[i + 1].EntryContext);
......@@ -164,16 +164,16 @@ fn getCpuInfoFromRegistry(core: usize, args: anytype) !void {
164164 buf[len] = 0;
165165 },
166166
167 REG.DWORD,
168 REG.DWORD_BIG_ENDIAN,
169 REG.QWORD,
167 .DWORD,
168 .DWORD_BIG_ENDIAN,
169 .QWORD,
170170 => {
171171 const entry: [*]const u8 = @ptrCast(table[i + 1].EntryContext);
172172 switch (@field(args, field.name).value_type) {
173 REG.DWORD, REG.DWORD_BIG_ENDIAN => {
173 .DWORD, .DWORD_BIG_ENDIAN => {
174174 @memcpy(@field(args, field.name).value_buf[0..4], entry[0..4]);
175175 },
176 REG.QWORD => {
176 .QWORD => {
177177 @memcpy(@field(args, field.name).value_buf[0..8], entry[0..8]);
178178 },
179179 else => unreachable,
......@@ -254,18 +254,18 @@ pub fn detectNativeCpuAndFeatures() ?Target.Cpu {
254254 // CP 4039 -> ID_AA64MMFR1_EL1
255255 // CP 403A -> ID_AA64MMFR2_EL1
256256 getCpuInfoFromRegistry(i, .{
257 .{ .key = "CP 4000", .value_type = REG.QWORD, .value_buf = @as(*[8]u8, @ptrCast(&registers[0])) },
258 .{ .key = "CP 4020", .value_type = REG.QWORD, .value_buf = @as(*[8]u8, @ptrCast(&registers[1])) },
259 .{ .key = "CP 4021", .value_type = REG.QWORD, .value_buf = @as(*[8]u8, @ptrCast(&registers[2])) },
260 .{ .key = "CP 4028", .value_type = REG.QWORD, .value_buf = @as(*[8]u8, @ptrCast(&registers[3])) },
261 .{ .key = "CP 4029", .value_type = REG.QWORD, .value_buf = @as(*[8]u8, @ptrCast(&registers[4])) },
262 .{ .key = "CP 402C", .value_type = REG.QWORD, .value_buf = @as(*[8]u8, @ptrCast(&registers[5])) },
263 .{ .key = "CP 402D", .value_type = REG.QWORD, .value_buf = @as(*[8]u8, @ptrCast(&registers[6])) },
264 .{ .key = "CP 4030", .value_type = REG.QWORD, .value_buf = @as(*[8]u8, @ptrCast(&registers[7])) },
265 .{ .key = "CP 4031", .value_type = REG.QWORD, .value_buf = @as(*[8]u8, @ptrCast(&registers[8])) },
266 .{ .key = "CP 4038", .value_type = REG.QWORD, .value_buf = @as(*[8]u8, @ptrCast(&registers[9])) },
267 .{ .key = "CP 4039", .value_type = REG.QWORD, .value_buf = @as(*[8]u8, @ptrCast(&registers[10])) },
268 .{ .key = "CP 403A", .value_type = REG.QWORD, .value_buf = @as(*[8]u8, @ptrCast(&registers[11])) },
257 .{ .key = "CP 4000", .value_type = REG.ValueType.QWORD, .value_buf = @as(*[8]u8, @ptrCast(&registers[0])) },
258 .{ .key = "CP 4020", .value_type = REG.ValueType.QWORD, .value_buf = @as(*[8]u8, @ptrCast(&registers[1])) },
259 .{ .key = "CP 4021", .value_type = REG.ValueType.QWORD, .value_buf = @as(*[8]u8, @ptrCast(&registers[2])) },
260 .{ .key = "CP 4028", .value_type = REG.ValueType.QWORD, .value_buf = @as(*[8]u8, @ptrCast(&registers[3])) },
261 .{ .key = "CP 4029", .value_type = REG.ValueType.QWORD, .value_buf = @as(*[8]u8, @ptrCast(&registers[4])) },
262 .{ .key = "CP 402C", .value_type = REG.ValueType.QWORD, .value_buf = @as(*[8]u8, @ptrCast(&registers[5])) },
263 .{ .key = "CP 402D", .value_type = REG.ValueType.QWORD, .value_buf = @as(*[8]u8, @ptrCast(&registers[6])) },
264 .{ .key = "CP 4030", .value_type = REG.ValueType.QWORD, .value_buf = @as(*[8]u8, @ptrCast(&registers[7])) },
265 .{ .key = "CP 4031", .value_type = REG.ValueType.QWORD, .value_buf = @as(*[8]u8, @ptrCast(&registers[8])) },
266 .{ .key = "CP 4038", .value_type = REG.ValueType.QWORD, .value_buf = @as(*[8]u8, @ptrCast(&registers[9])) },
267 .{ .key = "CP 4039", .value_type = REG.ValueType.QWORD, .value_buf = @as(*[8]u8, @ptrCast(&registers[10])) },
268 .{ .key = "CP 403A", .value_type = REG.ValueType.QWORD, .value_buf = @as(*[8]u8, @ptrCast(&registers[11])) },
269269 }) catch break :blk null;
270270
271271 cores[i] = @import("arm.zig").aarch64.detectNativeCpuAndFeatures(current_arch, registers) orelse