| ... | ... | @@ -7,19 +7,20 @@ const Dir = std.Io.Dir; |
| 7 | 7 | const Writer = std.Io.Writer; |
| 8 | 8 | const Allocator = std.mem.Allocator; |
| 9 | 9 | const Environ = std.process.Environ; |
| 10 | const L = std.unicode.wtf8ToWtf16LeStringLiteral; |
| 11 | const is_32_bit = @bitSizeOf(usize) == 32; |
| 10 | 12 | |
| 11 | 13 | windows10sdk: ?Installation, |
| 12 | 14 | windows81sdk: ?Installation, |
| 13 | 15 | msvc_lib_dir: ?[]const u8, |
| 14 | 16 | |
| 15 | 17 | const windows = std.os.windows; |
| 16 | | const RRF = windows.advapi32.RRF; |
| 17 | 18 | |
| 18 | | const windows_kits_reg_key = "SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots"; |
| 19 | const windows_kits_reg_key = "Microsoft\\Windows Kits\\Installed Roots"; |
| 19 | 20 | |
| 20 | 21 | // https://learn.microsoft.com/en-us/windows/win32/msi/productversion |
| 21 | 22 | const 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 |
| 23 | 24 | const product_version_max_length = version_major_minor_max_length + ".65535".len; |
| 24 | 25 | |
| 25 | 26 | /// 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( |
| 33 | 34 | ) error{ OutOfMemory, NotFound, PathTooLong }!WindowsSdk { |
| 34 | 35 | if (builtin.os.tag != .windows) return error.NotFound; |
| 35 | 36 | |
| 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) { |
| 38 | 42 | error.KeyNotFound => return error.NotFound, |
| 39 | 43 | }; |
| 40 | | defer roots_key.closeKey(); |
| 44 | defer roots_key.close(); |
| 41 | 45 | |
| 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) { |
| 43 | 47 | error.InstallationNotFound => null, |
| 44 | 48 | error.PathTooLong => null, |
| 45 | 49 | error.VersionTooLong => null, |
| ... | ... | @@ -47,7 +51,7 @@ pub fn find( |
| 47 | 51 | }; |
| 48 | 52 | errdefer if (windows10sdk) |*w| w.free(gpa); |
| 49 | 53 | |
| 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) { |
| 51 | 55 | error.InstallationNotFound => null, |
| 52 | 56 | error.PathTooLong => null, |
| 53 | 57 | error.VersionTooLong => null, |
| ... | ... | @@ -55,7 +59,7 @@ pub fn find( |
| 55 | 59 | }; |
| 56 | 60 | errdefer if (windows81sdk) |*w| w.free(gpa); |
| 57 | 61 | |
| 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) { |
| 59 | 63 | error.MsvcLibDirNotFound => null, |
| 60 | 64 | error.OutOfMemory => return error.OutOfMemory, |
| 61 | 65 | }; |
| ... | ... | @@ -149,274 +153,306 @@ fn iterateAndFilterByVersion( |
| 149 | 153 | return dirs.toOwnedSlice(); |
| 150 | 154 | } |
| 151 | 155 | |
| 152 | | const 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 | | |
| 158 | | const 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. |
| 184 | const 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); |
| 183 | 191 | } |
| 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); |
| 184 | 195 | } |
| 185 | 196 | |
| 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 | }; |
| 221 | 217 | |
| 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, |
| 227 | 231 | }; |
| 228 | 232 | |
| 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 | } |
| 243 | 237 | |
| 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 | }; |
| 248 | 294 | |
| 249 | | const 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 | }; |
| 279 | 308 | |
| 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; |
| 281 | 312 | } |
| 282 | | return .{ .key = key }; |
| 313 | return error.KeyNotFound; |
| 283 | 314 | } |
| 284 | 315 | |
| 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); |
| 293 | 319 | } |
| 294 | 320 | |
| 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, |
| 310 | 323 | |
| 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); |
| 319 | 326 | } |
| 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 | | ); |
| 337 | 327 | |
| 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 | } |
| 350 | 352 | } |
| 351 | 353 | |
| 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, |
| 358 | 357 | }; |
| 359 | 358 | |
| 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 | }; |
| 363 | 410 | |
| 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 | } |
| 387 | 415 | |
| 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 | } |
| 391 | 422 | } |
| 392 | 423 | |
| 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 | } |
| 395 | 445 | |
| 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); |
| 417 | 447 | |
| 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 | }; |
| 420 | 456 | }; |
| 421 | 457 | |
| 422 | 458 | pub const Installation = struct { |
| ... | ... | @@ -428,20 +464,21 @@ pub const Installation = struct { |
| 428 | 464 | fn find( |
| 429 | 465 | gpa: Allocator, |
| 430 | 466 | io: Io, |
| 431 | | roots_key: RegistryWtf8, |
| 432 | | roots_subkey: []const u8, |
| 467 | registry: *Registry, |
| 468 | roots_key: Registry.Key, |
| 469 | roots_subkey: []const u16, |
| 433 | 470 | prefix: []const u8, |
| 434 | | version_key_name: []const u8, |
| 471 | version_key_name: []const u16, |
| 435 | 472 | ) error{ OutOfMemory, InstallationNotFound, PathTooLong, VersionTooLong }!Installation { |
| 436 | 473 | roots: { |
| 437 | 474 | const installation = findFromRoot(gpa, io, roots_key, roots_subkey, prefix) catch |
| 438 | 475 | break :roots; |
| 439 | | if (installation.isValidVersion()) return installation; |
| 476 | if (installation.isValidVersion(roots_key)) return installation; |
| 440 | 477 | installation.free(gpa); |
| 441 | 478 | } |
| 442 | 479 | { |
| 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; |
| 445 | 482 | installation.free(gpa); |
| 446 | 483 | } |
| 447 | 484 | return error.InstallationNotFound; |
| ... | ... | @@ -450,29 +487,27 @@ pub const Installation = struct { |
| 450 | 487 | fn findFromRoot( |
| 451 | 488 | gpa: Allocator, |
| 452 | 489 | io: Io, |
| 453 | | roots_key: RegistryWtf8, |
| 454 | | roots_subkey: []const u8, |
| 490 | roots_key: Registry.Key, |
| 491 | roots_subkey: []const u16, |
| 455 | 492 | prefix: []const u8, |
| 456 | 493 | ) error{ OutOfMemory, InstallationNotFound, PathTooLong, VersionTooLong }!Installation { |
| 457 | 494 | 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, |
| 462 | 500 | |
| 463 | 501 | error.OutOfMemory => return error.OutOfMemory, |
| 464 | 502 | }; |
| 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); |
| 469 | 504 | |
| 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 | } |
| 472 | 508 | |
| 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); |
| 476 | 511 | }; |
| 477 | 512 | errdefer gpa.free(path); |
| 478 | 513 | |
| ... | ... | @@ -508,70 +543,71 @@ pub const Installation = struct { |
| 508 | 543 | |
| 509 | 544 | fn findFromInstallationFolder( |
| 510 | 545 | gpa: Allocator, |
| 511 | | version_key_name: []const u8, |
| 546 | registry: *Registry, |
| 547 | version_key_name: []const u16, |
| 512 | 548 | ) 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(); |
| 527 | 567 | |
| 528 | 568 | 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, |
| 533 | 574 | |
| 534 | 575 | error.OutOfMemory => return error.OutOfMemory, |
| 535 | 576 | }; |
| 577 | defer gpa.free(path_w_maybe_with_trailing_slash); |
| 536 | 578 | |
| 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; |
| 540 | 581 | } |
| 541 | 582 | |
| 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); |
| 550 | 585 | }; |
| 551 | 586 | errdefer gpa.free(path); |
| 552 | 587 | |
| 553 | 588 | 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, |
| 560 | 595 | |
| 561 | 596 | error.OutOfMemory => return error.OutOfMemory, |
| 562 | 597 | }; |
| 598 | defer gpa.free(version_without_0); |
| 599 | |
| 563 | 600 | if (version_without_0.len + ".0".len > product_version_max_length) { |
| 564 | | gpa.free(version_without_0); |
| 565 | 601 | return error.VersionTooLong; |
| 566 | 602 | } |
| 567 | 603 | |
| 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); |
| 569 | 605 | errdefer version.deinit(); |
| 570 | 606 | |
| 607 | try std.unicode.wtf16LeToWtf8ArrayList(&version, version_without_0); |
| 571 | 608 | try version.appendSlice(".0"); |
| 572 | 609 | |
| 573 | | const version_with_0 = try version.toOwnedSlice(); |
| 574 | | break :version version_with_0; |
| 610 | break :version try version.toOwnedSlice(); |
| 575 | 611 | }; |
| 576 | 612 | errdefer gpa.free(version); |
| 577 | 613 | |
| ... | ... | @@ -579,23 +615,22 @@ pub const Installation = struct { |
| 579 | 615 | } |
| 580 | 616 | |
| 581 | 617 | /// 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) { |
| 596 | 631 | error.KeyNotFound => return false, |
| 597 | 632 | }; |
| 598 | | defer options_key.closeKey(); |
| 633 | defer options_key.close(); |
| 599 | 634 | |
| 600 | 635 | const option_name = comptime switch (builtin.target.cpu.arch) { |
| 601 | 636 | .thumb => "OptionId.DesktopCPParm", |
| ... | ... | @@ -605,7 +640,7 @@ pub const Installation = struct { |
| 605 | 640 | else => |tag| @compileError("Windows SDK cannot be detected on architecture " ++ tag), |
| 606 | 641 | }; |
| 607 | 642 | |
| 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; |
| 609 | 644 | return (reg_value == 1); |
| 610 | 645 | } |
| 611 | 646 | |
| ... | ... | @@ -616,14 +651,14 @@ pub const Installation = struct { |
| 616 | 651 | }; |
| 617 | 652 | |
| 618 | 653 | const 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) { |
| 622 | 657 | error.KeyNotFound => return error.PathNotFound, |
| 623 | 658 | }; |
| 624 | | defer vs_setup_key.closeKey(); |
| 659 | defer vs_setup_key.close(); |
| 625 | 660 | |
| 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) { |
| 627 | 662 | error.NotAString, |
| 628 | 663 | error.ValueNameNotFound, |
| 629 | 664 | error.StringNotFound, |
| ... | ... | @@ -633,22 +668,41 @@ const MsvcLibDir = struct { |
| 633 | 668 | }; |
| 634 | 669 | defer gpa.free(packages_path); |
| 635 | 670 | |
| 636 | | if (!Dir.path.isAbsolute(packages_path)) return error.PathNotFound; |
| 671 | if (!std.fs.path.isAbsolute(packages_path)) return error.PathNotFound; |
| 637 | 672 | |
| 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" }); |
| 639 | 674 | defer gpa.free(instances_path); |
| 640 | 675 | |
| 641 | 676 | return Dir.openDirAbsolute(io, instances_path, .{ .iterate = true }) catch return error.PathNotFound; |
| 642 | 677 | } |
| 643 | 678 | |
| 644 | | fn findInstancesDirViaCLSID(gpa: Allocator, io: Io) error{ OutOfMemory, PathNotFound }!Dir { |
| 679 | fn findInstancesDirViaCLSID(gpa: Allocator, io: Io, registry: *Registry) error{ OutOfMemory, PathNotFound }!Dir { |
| 645 | 680 | 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) { |
| 647 | 700 | error.KeyNotFound => return error.PathNotFound, |
| 648 | 701 | }; |
| 649 | | defer setup_config_key.closeKey(); |
| 702 | defer setup_config_key.close(); |
| 650 | 703 | |
| 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) { |
| 652 | 706 | error.NotAString, |
| 653 | 707 | error.ValueNameNotFound, |
| 654 | 708 | error.StringNotFound, |
| ... | ... | @@ -658,9 +712,9 @@ const MsvcLibDir = struct { |
| 658 | 712 | }; |
| 659 | 713 | defer gpa.free(dll_path); |
| 660 | 714 | |
| 661 | | if (!Dir.path.isAbsolute(dll_path)) return error.PathNotFound; |
| 715 | if (!std.fs.path.isAbsolute(dll_path)) return error.PathNotFound; |
| 662 | 716 | |
| 663 | | var path_it = Dir.path.componentIterator(dll_path); |
| 717 | var path_it = std.fs.path.componentIterator(dll_path); |
| 664 | 718 | // the .dll filename |
| 665 | 719 | _ = path_it.last(); |
| 666 | 720 | const root_path = while (path_it.previous()) |dir_component| { |
| ... | ... | @@ -671,7 +725,7 @@ const MsvcLibDir = struct { |
| 671 | 725 | return error.PathNotFound; |
| 672 | 726 | }; |
| 673 | 727 | |
| 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" }); |
| 675 | 729 | defer gpa.free(instances_path); |
| 676 | 730 | |
| 677 | 731 | return Dir.openDirAbsolute(io, instances_path, .{ .iterate = true }) catch return error.PathNotFound; |
| ... | ... | @@ -680,12 +734,13 @@ const MsvcLibDir = struct { |
| 680 | 734 | fn findInstancesDir( |
| 681 | 735 | gpa: Allocator, |
| 682 | 736 | io: Io, |
| 737 | registry: *Registry, |
| 683 | 738 | environ_map: *const Environ.Map, |
| 684 | 739 | ) error{ OutOfMemory, PathNotFound }!Dir { |
| 685 | 740 | // First, try getting the packages cache path from the registry. |
| 686 | 741 | // This only seems to exist when the path is different from the default. |
| 687 | 742 | method1: { |
| 688 | | return findInstancesDirViaSetup(gpa, io) catch |err| switch (err) { |
| 743 | return findInstancesDirViaSetup(gpa, io, registry) catch |err| switch (err) { |
| 689 | 744 | error.OutOfMemory => |e| return e, |
| 690 | 745 | error.PathNotFound => break :method1, |
| 691 | 746 | }; |
| ... | ... | @@ -693,7 +748,7 @@ const MsvcLibDir = struct { |
| 693 | 748 | // Otherwise, try to get the path from the .dll that would have been |
| 694 | 749 | // loaded via COM for SetupConfiguration. |
| 695 | 750 | method2: { |
| 696 | | return findInstancesDirViaCLSID(gpa, io) catch |err| switch (err) { |
| 751 | return findInstancesDirViaCLSID(gpa, io, registry) catch |err| switch (err) { |
| 697 | 752 | error.OutOfMemory => |e| return e, |
| 698 | 753 | error.PathNotFound => break :method2, |
| 699 | 754 | }; |
| ... | ... | @@ -703,7 +758,7 @@ const MsvcLibDir = struct { |
| 703 | 758 | method3: { |
| 704 | 759 | const program_data = std.zig.EnvVar.PROGRAMDATA.get(environ_map) orelse break :method3; |
| 705 | 760 | |
| 706 | | if (!Dir.path.isAbsolute(program_data)) break :method3; |
| 761 | if (!std.fs.path.isAbsolute(program_data)) break :method3; |
| 707 | 762 | |
| 708 | 763 | const instances_path = try Dir.path.join(gpa, &.{ |
| 709 | 764 | program_data, "Microsoft", "VisualStudio", "Packages", "_Instances", |
| ... | ... | @@ -764,6 +819,7 @@ const MsvcLibDir = struct { |
| 764 | 819 | fn findViaCOM( |
| 765 | 820 | gpa: Allocator, |
| 766 | 821 | io: Io, |
| 822 | registry: *Registry, |
| 767 | 823 | arch: std.Target.Cpu.Arch, |
| 768 | 824 | environ_map: *const Environ.Map, |
| 769 | 825 | ) error{ OutOfMemory, PathNotFound }![]const u8 { |
| ... | ... | @@ -771,7 +827,7 @@ const MsvcLibDir = struct { |
| 771 | 827 | // This will contain directories with names of instance IDs like 80a758ca, |
| 772 | 828 | // which will contain `state.json` files that have the version and |
| 773 | 829 | // installation directory. |
| 774 | | var instances_dir = try findInstancesDir(gpa, io, environ_map); |
| 830 | var instances_dir = try findInstancesDir(gpa, io, registry, environ_map); |
| 775 | 831 | defer instances_dir.close(io); |
| 776 | 832 | |
| 777 | 833 | var state_subpath_buf: [Dir.max_name_bytes + 32]u8 = undefined; |
| ... | ... | @@ -874,7 +930,6 @@ const MsvcLibDir = struct { |
| 874 | 930 | arch: std.Target.Cpu.Arch, |
| 875 | 931 | environ_map: *const Environ.Map, |
| 876 | 932 | ) error{ OutOfMemory, PathNotFound }![]const u8 { |
| 877 | | |
| 878 | 933 | // %localappdata%\Microsoft\VisualStudio\ |
| 879 | 934 | // %appdata%\Local\Microsoft\VisualStudio\ |
| 880 | 935 | const local_app_data_path = std.zig.EnvVar.LOCALAPPDATA.get(environ_map) orelse return error.PathNotFound; |
| ... | ... | @@ -883,15 +938,18 @@ const MsvcLibDir = struct { |
| 883 | 938 | }); |
| 884 | 939 | defer gpa.free(visualstudio_folder_path); |
| 885 | 940 | |
| 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 | |
| 886 | 950 | const vs_versions: []const []const u8 = vs_versions: { |
| 887 | | if (!Dir.path.isAbsolute(visualstudio_folder_path)) return error.PathNotFound; |
| 888 | 951 | // enumerate folders that contain `privateregistry.bin`, looking for all versions |
| 889 | 952 | // 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 | | |
| 895 | 953 | var iterator = visualstudio_folder.iterate(); |
| 896 | 954 | break :vs_versions try iterateAndFilterByVersion(&iterator, gpa, io, ""); |
| 897 | 955 | }; |
| ... | ... | @@ -899,25 +957,94 @@ const MsvcLibDir = struct { |
| 899 | 957 | for (vs_versions) |vs_version| gpa.free(vs_version); |
| 900 | 958 | gpa.free(vs_versions); |
| 901 | 959 | } |
| 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; |
| 903 | 962 | 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 | }; |
| 907 | 969 | |
| 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 | }; |
| 910 | 995 | |
| 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; |
| 912 | 1043 | |
| 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) { |
| 914 | 1045 | error.OutOfMemory => return error.OutOfMemory, |
| 915 | 1046 | else => continue, |
| 916 | 1047 | }; |
| 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 | | } |
| 921 | 1048 | |
| 922 | 1049 | break :source_directories source_directories_value; |
| 923 | 1050 | } else return error.PathNotFound; |
| ... | ... | @@ -967,6 +1094,7 @@ const MsvcLibDir = struct { |
| 967 | 1094 | fn findViaVs7Key( |
| 968 | 1095 | gpa: Allocator, |
| 969 | 1096 | io: Io, |
| 1097 | registry: *Registry, |
| 970 | 1098 | arch: std.Target.Cpu.Arch, |
| 971 | 1099 | environ_map: *const Environ.Map, |
| 972 | 1100 | ) error{ OutOfMemory, PathNotFound }![]const u8 { |
| ... | ... | @@ -986,10 +1114,10 @@ const MsvcLibDir = struct { |
| 986 | 1114 | } |
| 987 | 1115 | } |
| 988 | 1116 | |
| 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(); |
| 991 | 1119 | 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) { |
| 993 | 1121 | error.OutOfMemory => return error.OutOfMemory, |
| 994 | 1122 | else => break :try_vs7_key, |
| 995 | 1123 | }; |
| ... | ... | @@ -1045,14 +1173,15 @@ const MsvcLibDir = struct { |
| 1045 | 1173 | pub fn find( |
| 1046 | 1174 | gpa: Allocator, |
| 1047 | 1175 | io: Io, |
| 1176 | registry: *Registry, |
| 1048 | 1177 | arch: std.Target.Cpu.Arch, |
| 1049 | 1178 | environ_map: *const Environ.Map, |
| 1050 | 1179 | ) 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) { |
| 1052 | 1181 | error.OutOfMemory => return error.OutOfMemory, |
| 1053 | 1182 | error.PathNotFound => MsvcLibDir.findViaRegistry(gpa, io, arch, environ_map) catch |err2| switch (err2) { |
| 1054 | 1183 | 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) { |
| 1056 | 1185 | error.OutOfMemory => return error.OutOfMemory, |
| 1057 | 1186 | error.PathNotFound => return error.MsvcLibDirNotFound, |
| 1058 | 1187 | }, |