authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-03-03 18:26:12+01:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-03-03 18:26:12+01:00
log2d88a5a10334bddf3bd0b8bc98744ea6f239ce3a
tree72f6bcddc59a83c3bed1f135ab2c677583a59ba4
parent0a412853aae9815eb663a88a8a2d37b91c614317
parentfdde8e6394c1dd07eb477e66c79f1fa7333bd7de

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>

5 files changed, 696 insertions(+), 494 deletions(-)

lib/std/os/windows.zig+134-29
......@@ -15,7 +15,6 @@ const math = std.math;
1515const maxInt = std.math.maxInt;
1616const UnexpectedError = std.posix.UnexpectedError;
1717
18pub const advapi32 = @import("windows/advapi32.zig");
1918pub const kernel32 = @import("windows/kernel32.zig");
2019pub const ntdll = @import("windows/ntdll.zig");
2120pub const ws2_32 = @import("windows/ws2_32.zig");
......@@ -3506,6 +3505,16 @@ pub const GUID = extern struct {
35063505 }
35073506 return @as(GUID, @bitCast(bytes));
35083507 }
3508
3509 pub fn format(self: GUID, w: *std.Io.Writer) std.Io.Writer.Error!void {
3510 return w.print("{{{x:0>8}-{x:0>4}-{x:0>4}-{x}-{x}}}", .{
3511 self.Data1,
3512 self.Data2,
3513 self.Data3,
3514 self.Data4[0..2],
3515 self.Data4[2..8],
3516 });
3517 }
35093518};
35103519
35113520test GUID {
......@@ -3518,6 +3527,16 @@ test GUID {
35183527 },
35193528 GUID.parse("{01234567-89AB-EF10-3254-7698badcfe91}"),
35203529 );
3530 try std.testing.expectFmt(
3531 "{01234567-89ab-ef10-3254-7698badcfe91}",
3532 "{f}",
3533 .{GUID.parse("{01234567-89AB-EF10-3254-7698badcfe91}")},
3534 );
3535 try std.testing.expectFmt(
3536 "{00000001-0001-0001-0001-000000000001}",
3537 "{f}",
3538 .{GUID{ .Data1 = 1, .Data2 = 1, .Data3 = 1, .Data4 = [_]u8{ 0, 1, 0, 0, 0, 0, 0, 1 } }},
3539 );
35213540}
35223541
35233542pub const COORD = extern struct {
......@@ -3560,7 +3579,7 @@ pub const RTL_QUERY_REGISTRY_TABLE = extern struct {
35603579 Flags: ULONG,
35613580 Name: ?PWSTR,
35623581 EntryContext: ?*anyopaque,
3563 DefaultType: ULONG,
3582 DefaultType: REG.ValueType,
35643583 DefaultData: ?*anyopaque,
35653584 DefaultLength: ULONG,
35663585};
......@@ -3625,34 +3644,120 @@ pub const RTL_QUERY_REGISTRY_DELETE = 0x00000040;
36253644/// If the types do not match, the call fails.
36263645pub const RTL_QUERY_REGISTRY_TYPECHECK = 0x00000100;
36273646
3647/// REG_ is a crowded namespace with a lot of overlapping and unrelated
3648/// defines in the Windows headers, so instead of strictly following the
3649/// Windows headers names, extra namespaces are added here for clarity.
36283650pub 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;
3651 pub const ValueType = enum(ULONG) {
3652 /// No value type
3653 NONE = 0,
3654 /// Unicode nul terminated string
3655 SZ = 1,
3656 /// Unicode nul terminated string (with environment variable references)
3657 EXPAND_SZ = 2,
3658 /// Free form binary
3659 BINARY = 3,
3660 /// 32-bit number
3661 DWORD = 4,
3662 /// 32-bit number
3663 DWORD_BIG_ENDIAN = 5,
3664 /// Symbolic Link (unicode)
3665 LINK = 6,
3666 /// Multiple Unicode strings
3667 MULTI_SZ = 7,
3668 /// Resource list in the resource map
3669 RESOURCE_LIST = 8,
3670 /// Resource list in the hardware description
3671 FULL_RESOURCE_DESCRIPTOR = 9,
3672 RESOURCE_REQUIREMENTS_LIST = 10,
3673 /// 64-bit number
3674 QWORD = 11,
3675 _,
3676
3677 /// 32-bit number (same as REG_DWORD)
3678 pub const DWORD_LITTLE_ENDIAN: ValueType = .DWORD;
3679 /// 64-bit number (same as REG_QWORD)
3680 pub const QWORD_LITTLE_ENDIAN: ValueType = .QWORD;
3681 };
3682
3683 /// Used with NtOpenKeyEx, maybe others
3684 pub const OpenOptions = packed struct(ULONG) {
3685 Reserved0: u2 = 0,
3686 /// Open for backup or restore
3687 /// special access rules privilege required
3688 BACKUP_RESTORE: bool = false,
3689 /// Open symbolic link
3690 OPEN_LINK: bool = false,
3691 Reserved3: u28 = 0,
3692 };
3693
3694 /// Used with NtLoadKeyEx, maybe others
3695 pub const LoadOptions = packed struct(ULONG) {
3696 /// Restore whole hive volatile
3697 WHOLE_HIVE_VOLATILE: bool = false,
3698 /// Unwind changes to last flush
3699 REFRESH_HIVE: bool = false,
3700 /// Never lazy flush this hive
3701 NO_LAZY_FLUSH: bool = false,
3702 /// Force the restore process even when we have open handles on subkeys
3703 FORCE_RESTORE: bool = false,
3704 /// Loads the hive visible to the calling process
3705 APP_HIVE: bool = false,
3706 /// Hive cannot be mounted by any other process while in use
3707 PROCESS_PRIVATE: bool = false,
3708 /// Starts Hive Journal
3709 START_JOURNAL: bool = false,
3710 /// Grow hive file in exact 4k increments
3711 HIVE_EXACT_FILE_GROWTH: bool = false,
3712 /// No RM is started for this hive (no transactions)
3713 HIVE_NO_RM: bool = false,
3714 /// Legacy single logging is used for this hive
3715 HIVE_SINGLE_LOG: bool = false,
3716 /// This hive might be used by the OS loader
3717 BOOT_HIVE: bool = false,
3718 /// Load the hive and return a handle to its root kcb
3719 LOAD_HIVE_OPEN_HANDLE: bool = false,
3720 /// Flush changes to primary hive file size as part of all flushes
3721 FLUSH_HIVE_FILE_GROWTH: bool = false,
3722 /// Open a hive's files in read-only mode
3723 /// The same flag is used for REG_APP_HIVE_OPEN_READ_ONLY:
3724 /// Open an app hive's files in read-only mode (if the hive was not previously loaded).
3725 OPEN_READ_ONLY: bool = false,
3726 /// Load the hive, but don't allow any modification of it
3727 IMMUTABLE: bool = false,
3728 /// Do not fall back to impersonating the caller if hive file access fails
3729 NO_IMPERSONATION_FALLBACK: bool = false,
3730 Reserved16: u16 = 0,
3731 };
3732};
3733
3734pub const KEY = struct {
3735 pub const VALUE = struct {
3736 /// https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/ne-wdm-_key_value_information_class
3737 pub const INFORMATION_CLASS = enum(c_int) {
3738 Basic = 0,
3739 Full = 1,
3740 Partial = 2,
3741 FullAlign64 = 3,
3742 PartialAlign64 = 4,
3743 Layer = 5,
3744 _,
3745
3746 pub const Max: @typeInfo(@This()).@"enum".tag_type = @typeInfo(@This()).@"enum".fields.len;
3747 };
3748
3749 pub const PARTIAL_INFORMATION = extern struct {
3750 TitleIndex: ULONG,
3751 Type: REG.ValueType,
3752 DataLength: ULONG,
3753 Data: [0]UCHAR,
3754
3755 pub fn data(info: *const PARTIAL_INFORMATION) []const UCHAR {
3756 const ptr: [*]const UCHAR = @ptrCast(&info.Data);
3757 return ptr[0..info.DataLength];
3758 }
3759 };
3760 };
36563761};
36573762
36583763pub const ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x4;
lib/std/os/windows/advapi32.zig deleted-67
......@@ -1,67 +0,0 @@
1const std = @import("../../std.zig");
2const windows = std.os.windows;
3const BOOL = windows.BOOL;
4const DWORD = windows.DWORD;
5const HKEY = windows.HKEY;
6const BYTE = windows.BYTE;
7const LPCWSTR = windows.LPCWSTR;
8const LSTATUS = windows.LSTATUS;
9const REGSAM = windows.REGSAM;
10const ULONG = windows.ULONG;
11
12pub extern "advapi32" fn RegOpenKeyExW(
13 hKey: HKEY,
14 lpSubKey: LPCWSTR,
15 ulOptions: DWORD,
16 samDesired: REGSAM,
17 phkResult: *HKEY,
18) callconv(.winapi) LSTATUS;
19
20pub extern "advapi32" fn RegQueryValueExW(
21 hKey: HKEY,
22 lpValueName: LPCWSTR,
23 lpReserved: ?*DWORD,
24 lpType: ?*DWORD,
25 lpData: ?*BYTE,
26 lpcbData: ?*DWORD,
27) callconv(.winapi) LSTATUS;
28
29pub extern "advapi32" fn RegCloseKey(hKey: HKEY) callconv(.winapi) LSTATUS;
30
31pub const RRF = struct {
32 pub const RT_ANY: DWORD = 0x0000ffff;
33
34 pub const RT_DWORD: DWORD = 0x00000018;
35 pub const RT_QWORD: DWORD = 0x00000048;
36
37 pub const RT_REG_BINARY: DWORD = 0x00000008;
38 pub const RT_REG_DWORD: DWORD = 0x00000010;
39 pub const RT_REG_EXPAND_SZ: DWORD = 0x00000004;
40 pub const RT_REG_MULTI_SZ: DWORD = 0x00000020;
41 pub const RT_REG_NONE: DWORD = 0x00000001;
42 pub const RT_REG_QWORD: DWORD = 0x00000040;
43 pub const RT_REG_SZ: DWORD = 0x00000002;
44
45 pub const NOEXPAND: DWORD = 0x10000000;
46 pub const ZEROONFAILURE: DWORD = 0x20000000;
47 pub const SUBKEY_WOW6464KEY: DWORD = 0x00010000;
48 pub const SUBKEY_WOW6432KEY: DWORD = 0x00020000;
49};
50
51pub extern "advapi32" fn RegGetValueW(
52 hkey: HKEY,
53 lpSubKey: LPCWSTR,
54 lpValue: LPCWSTR,
55 dwFlags: DWORD,
56 pdwType: ?*DWORD,
57 pvData: ?*anyopaque,
58 pcbData: ?*DWORD,
59) callconv(.winapi) LSTATUS;
60
61pub extern "advapi32" fn RegLoadAppKeyW(
62 lpFile: LPCWSTR,
63 phkResult: *HKEY,
64 samDesired: REGSAM,
65 dwOptions: DWORD,
66 reserved: DWORD,
67) callconv(.winapi) LSTATUS;
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