| ... | @@ -7,19 +7,20 @@ const Dir = std.Io.Dir; | ... | @@ -7,19 +7,20 @@ const Dir = std.Io.Dir; |
| 7 | const Writer = std.Io.Writer; | 7 | const Writer = std.Io.Writer; |
| 8 | const Allocator = std.mem.Allocator; | 8 | const Allocator = std.mem.Allocator; |
| 9 | const Environ = std.process.Environ; | 9 | const Environ = std.process.Environ; |
| | 10 | const L = std.unicode.wtf8ToWtf16LeStringLiteral; |
| | 11 | const is_32_bit = @bitSizeOf(usize) == 32; |
| 10 | | 12 | |
| 11 | windows10sdk: ?Installation, | 13 | windows10sdk: ?Installation, |
| 12 | windows81sdk: ?Installation, | 14 | windows81sdk: ?Installation, |
| 13 | msvc_lib_dir: ?[]const u8, | 15 | msvc_lib_dir: ?[]const u8, |
| 14 | | 16 | |
| 15 | const windows = std.os.windows; | 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 | // https://learn.microsoft.com/en-us/windows/win32/msi/productversion | 21 | // https://learn.microsoft.com/en-us/windows/win32/msi/productversion |
| 21 | const version_major_minor_max_length = "255.255".len; | 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 | const product_version_max_length = version_major_minor_max_length + ".65535".len; | 24 | const product_version_max_length = version_major_minor_max_length + ".65535".len; |
| 24 | | 25 | |
| 25 | /// Find path and version of Windows 10 SDK and Windows 8.1 SDK, and find path to MSVC's `lib/` directory. | 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,13 +34,16 @@ pub fn find( |
| 33 | ) error{ OutOfMemory, NotFound, PathTooLong }!WindowsSdk { | 34 | ) error{ OutOfMemory, NotFound, PathTooLong }!WindowsSdk { |
| 34 | if (builtin.os.tag != .windows) return error.NotFound; | 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 | var registry: Registry = .{}; |
| 37 | const roots_key = RegistryWtf8.openKey(windows.HKEY_LOCAL_MACHINE, windows_kits_reg_key, .{ .wow64_32 = true }) catch |err| switch (err) { | 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 | error.KeyNotFound => return error.NotFound, | 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 | error.InstallationNotFound => null, | 47 | error.InstallationNotFound => null, |
| 44 | error.PathTooLong => null, | 48 | error.PathTooLong => null, |
| 45 | error.VersionTooLong => null, | 49 | error.VersionTooLong => null, |
| ... | @@ -47,7 +51,7 @@ pub fn find( | ... | @@ -47,7 +51,7 @@ pub fn find( |
| 47 | }; | 51 | }; |
| 48 | errdefer if (windows10sdk) |*w| w.free(gpa); | 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 | error.InstallationNotFound => null, | 55 | error.InstallationNotFound => null, |
| 52 | error.PathTooLong => null, | 56 | error.PathTooLong => null, |
| 53 | error.VersionTooLong => null, | 57 | error.VersionTooLong => null, |
| ... | @@ -55,7 +59,7 @@ pub fn find( | ... | @@ -55,7 +59,7 @@ pub fn find( |
| 55 | }; | 59 | }; |
| 56 | errdefer if (windows81sdk) |*w| w.free(gpa); | 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 | error.MsvcLibDirNotFound => null, | 63 | error.MsvcLibDirNotFound => null, |
| 60 | error.OutOfMemory => return error.OutOfMemory, | 64 | error.OutOfMemory => return error.OutOfMemory, |
| 61 | }; | 65 | }; |
| ... | @@ -149,274 +153,306 @@ fn iterateAndFilterByVersion( | ... | @@ -149,274 +153,306 @@ fn iterateAndFilterByVersion( |
| 149 | return dirs.toOwnedSlice(); | 153 | return dirs.toOwnedSlice(); |
| 150 | } | 154 | } |
| 151 | | 155 | |
| 152 | const OpenOptions = struct { | 156 | /// Not a general purpose implementation of an ntdll-based Registry API. |
| 153 | /// Sets the KEY_WOW64_32KEY access flag. | 157 | /// Only intended to support the particular calls necessary for the purposes of finding |
| 154 | /// https://learn.microsoft.com/en-us/windows/win32/winprog64/accessing-an-alternate-registry-view | 158 | /// the SDK/MSVC installation paths. |
| 155 | wow64_32: bool = false, | 159 | /// |
| 156 | }; | 160 | /// The advapi32 APIs internally open and cache `\Registry\Machine` and the current user |
| 157 | | 161 | /// key when HKEY_LOCAL_MACHINE (HKLM) and HKEY_CURRENT_USER (HKCU) are passed, and also |
| 158 | const RegistryWtf8 = struct { | 162 | /// rewrite key path values to go through WOW6432Node when appropriate. |
| 159 | key: windows.HKEY, | 163 | /// |
| 160 | | 164 | /// For example, when opening `Software\Foo` relative to `HKEY_LOCAL_MACHINE` with |
| 161 | /// Assert that `key` is valid WTF-8 string | 165 | /// the WOW64_32KEY option set, that will end up as a call to NtLoadKeyEx with the path |
| 162 | pub fn openKey(hkey: windows.HKEY, key: []const u8, options: OpenOptions) error{KeyNotFound}!RegistryWtf8 { | 166 | /// rewritten to `Software\WOW6432Node\Foo` relative to a cached `\REGISTRY\Machine` |
| 163 | const key_wtf16le: [:0]const u16 = key_wtf16le: { | 167 | /// key. |
| 164 | var key_wtf16le_buf: [RegistryWtf16Le.key_name_max_len]u16 = undefined; | 168 | /// |
| 165 | const key_wtf16le_len: usize = std.unicode.wtf8ToWtf16Le(key_wtf16le_buf[0..], key) catch |err| switch (err) { | 169 | /// For our purposes, we really only care about 4 potential variations of the `Software` key: |
| 166 | error.InvalidWtf8 => unreachable, | 170 | /// - Relative to HKLM, no redirection through WOW6432Node |
| 167 | }; | 171 | /// - Relative to HKLM, redirected through WOW6432Node |
| 168 | key_wtf16le_buf[key_wtf16le_len] = 0; | 172 | /// - Relative to HKCU, no redirection through WOW6432Node |
| 169 | break :key_wtf16le key_wtf16le_buf[0..key_wtf16le_len :0]; | 173 | /// - Relative to HKCU, redirected through WOW6432Node |
| 170 | }; | 174 | /// (e.g. all the values we care about are within one of those `Software` keys) |
| 171 | | 175 | /// |
| 172 | const registry_wtf16le = try RegistryWtf16Le.openKey(hkey, key_wtf16le, options); | 176 | /// So, we cache those variants of the Software keys instead of HKLM/HKCU and treat them |
| 173 | return .{ .key = registry_wtf16le.key }; | 177 | /// as the "root" keys that the user can specify, which in turn (1) allows all provided key |
| 174 | } | 178 | /// paths to be agnostic to WOW6432Node, (2) avoids the need for internal path rewriting, |
| 175 | | 179 | /// and (3) works correctly on 32-bit targets without any special support. |
| 176 | /// Closes key, after that usage is invalid | 180 | /// |
| 177 | pub fn closeKey(reg: RegistryWtf8) void { | 181 | /// For example, instead of an advapi32 call with `Software\Foo` relative to |
| 178 | const return_code_int: windows.HRESULT = windows.advapi32.RegCloseKey(reg.key); | 182 | /// `HKEY_LOCAL_MACHINE` which may get rewritten to `Software\WOW6432Node\Foo`, |
| 179 | const return_code: windows.Win32Error = @enumFromInt(return_code_int); | 183 | /// the equivalent is now a call to open `Foo` relative to some Software key variant. |
| 180 | switch (return_code) { | 184 | const Registry = struct { |
| 181 | .SUCCESS => {}, | 185 | cache: Cache = .{}, |
| 182 | else => {}, | 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. | 197 | const Cache = struct { |
| 187 | /// Caller owns result. | 198 | hklm_software_foreign: if (is_32_bit) void else ?windows.HANDLE = if (is_32_bit) {} else null, |
| 188 | pub fn getString(reg: RegistryWtf8, gpa: Allocator, subkey: []const u8, value_name: []const u8) error{ OutOfMemory, ValueNameNotFound, NotAString, StringNotFound }![]u8 { | 199 | hkcu_software_foreign: if (is_32_bit) void else ?windows.HANDLE = if (is_32_bit) {} else null, |
| 189 | const subkey_wtf16le: [:0]const u16 = subkey_wtf16le: { | 200 | hklm_software_native: ?windows.HANDLE = null, |
| 190 | var subkey_wtf16le_buf: [RegistryWtf16Le.key_name_max_len]u16 = undefined; | 201 | hkcu_software_native: ?windows.HANDLE = null, |
| 191 | const subkey_wtf16le_len: usize = std.unicode.wtf8ToWtf16Le(subkey_wtf16le_buf[0..], subkey) catch unreachable; | 202 | hkcu: ?windows.HANDLE = null, |
| 192 | subkey_wtf16le_buf[subkey_wtf16le_len] = 0; | 203 | |
| 193 | break :subkey_wtf16le subkey_wtf16le_buf[0..subkey_wtf16le_len :0]; | 204 | fn getSoftware(cache: *const Cache, variant: Software) ?windows.HANDLE { |
| 194 | }; | 205 | if (!is_32_bit and variant.wow64 == .wow64_32) { |
| 195 | | 206 | return switch (variant.root) { |
| 196 | const value_name_wtf16le: [:0]const u16 = value_name_wtf16le: { | 207 | .local_machine => cache.hklm_software_foreign, |
| 197 | var value_name_wtf16le_buf: [RegistryWtf16Le.value_name_max_len]u16 = undefined; | 208 | .current_user => cache.hkcu_software_foreign, |
| 198 | const value_name_wtf16le_len: usize = std.unicode.wtf8ToWtf16Le(value_name_wtf16le_buf[0..], value_name) catch unreachable; | 209 | }; |
| 199 | value_name_wtf16le_buf[value_name_wtf16le_len] = 0; | 210 | } |
| 200 | break :value_name_wtf16le value_name_wtf16le_buf[0..value_name_wtf16le_len :0]; | 211 | return switch (variant.root) { |
| 201 | }; | 212 | .local_machine => cache.hklm_software_native, |
| 202 | | 213 | .current_user => cache.hkcu_software_native, |
| 203 | const registry_wtf16le: RegistryWtf16Le = .{ .key = reg.key }; | 214 | }; |
| 204 | const value_wtf16le = try registry_wtf16le.getString(gpa, subkey_wtf16le, value_name_wtf16le); | 215 | } |
| 205 | defer gpa.free(value_wtf16le); | 216 | }; |
| 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 | }; | | |
| 221 | | 217 | |
| 222 | const value_name_wtf16le: [:0]const u16 = value_name_wtf16le: { | 218 | // This does not correspond to HKEY_LOCAL_MACHINE/HKEY_CURRENT_USER |
| 223 | var value_name_wtf16le_buf: [RegistryWtf16Le.value_name_max_len]u16 = undefined; | 219 | // since WOW64 redirection is applicable to e.g. `HKLM\Software` instead of |
| 224 | const value_name_wtf16le_len: usize = std.unicode.wtf8ToWtf16Le(value_name_wtf16le_buf[0..], value_name) catch unreachable; | 220 | // HKLM/HKCU directly. Since we are only ever interested in the |
| 225 | value_name_wtf16le_buf[value_name_wtf16le_len] = 0; | 221 | // `Software` key, it makes more sense to treat `Software` as the "root" |
| 226 | break :value_name_wtf16le value_name_wtf16le_buf[0..value_name_wtf16le_len :0]; | 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 }; | 233 | fn getOrOpenKey(self: Software, registry: *Registry) !Key { |
| 230 | return registry_wtf16le.getDword(subkey_wtf16le, value_name_wtf16le); | 234 | if (registry.cache.getSoftware(self)) |handle| { |
| 231 | } | 235 | return .{ .handle = handle }; |
| 232 | | 236 | } |
| 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 | }; | | |
| 243 | | 237 | |
| 244 | const registry_wtf16le = try RegistryWtf16Le.loadFromPath(absolute_path_wtf16le); | 238 | const is_foreign = !is_32_bit and self.wow64 == .wow64_32; |
| 245 | return .{ .key = registry_wtf16le.key }; | 239 | switch (self.root) { |
| 246 | } | 240 | .local_machine => { |
| 247 | }; | 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 { | 295 | /// For 32-bit programs on a 64-bit operating system, the WOW64 |
| 250 | key: windows.HKEY, | 296 | /// version of ntdll.dll handles the WOW6432Node redirection before |
| 251 | | 297 | /// calling into ntdll.dll proper, so no special handling is needed |
| 252 | /// Includes root key (f.e. HKEY_LOCAL_MACHINE). | 298 | /// and this setting is irrelevant in that case. |
| 253 | /// https://learn.microsoft.com/en-us/windows/win32/sysinfo/registry-element-size-limits | 299 | const Wow64 = enum { |
| 254 | pub const key_name_max_len = 255; | 300 | /// Use 64-bit registry on 64-bit targets and 32-bit registry on |
| 255 | /// In Unicode characters. | 301 | /// 32-bit targets. |
| 256 | /// https://learn.microsoft.com/en-us/windows/win32/sysinfo/registry-element-size-limits | 302 | native, |
| 257 | pub const value_name_max_len = 16_383; | 303 | /// Go through WOW6432Node on both 32-bit and 64-bit targets, |
| 258 | | 304 | /// if relevant (ignored for 32-bit programs executed on a 32-bit |
| 259 | /// Under HKEY_LOCAL_MACHINE with flags: | 305 | /// OS). |
| 260 | /// KEY_QUERY_VALUE, KEY_ENUMERATE_SUB_KEYS, optionally KEY_WOW64_32KEY. | 306 | wow64_32, |
| 261 | /// After finishing work, call `closeKey`. | 307 | }; |
| 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, | | |
| 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 | 316 | fn openSoftwareKey(registry: *Registry, software: Software, sub_path: []const u16) error{KeyNotFound}!Key { |
| 286 | fn closeKey(reg: RegistryWtf16Le) void { | 317 | const software_key = try software.getOrOpenKey(registry); |
| 287 | const return_code_int: windows.HRESULT = windows.advapi32.RegCloseKey(reg.key); | 318 | return software_key.open(sub_path); |
| 288 | const return_code: windows.Win32Error = @enumFromInt(return_code_int); | | |
| 289 | switch (return_code) { | | |
| 290 | .SUCCESS => {}, | | |
| 291 | else => {}, | | |
| 292 | } | | |
| 293 | } | 319 | } |
| 294 | | 320 | |
| 295 | /// Get string ([:0]const u16) from registry. | 321 | const Key = struct { |
| 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 { | 322 | handle: windows.HANDLE, |
| 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 | ); | | |
| 310 | | 323 | |
| 311 | // Check returned code and type | 324 | fn close(self: Key) void { |
| 312 | var return_code: windows.Win32Error = @enumFromInt(return_code_int); | 325 | windows.CloseHandle(self.handle); |
| 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, | | |
| 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. | 328 | fn open(self: Key, sub_path: []const u16) error{KeyNotFound}!Key { |
| 339 | return_code = @enumFromInt(return_code_int); | 329 | var key: Key = undefined; |
| 340 | switch (return_code) { | 330 | const attr: windows.OBJECT.ATTRIBUTES = .{ |
| 341 | .SUCCESS => {}, | 331 | .RootDirectory = self.handle, |
| 342 | .MORE_DATA => unreachable, // Calculated first time length should be enough, even overestimated | 332 | .Attributes = .{}, |
| 343 | .FILE_NOT_FOUND => return error.ValueNameNotFound, | 333 | .ObjectName = @constCast(&windows.UNICODE_STRING.init(sub_path)), |
| 344 | .INVALID_PARAMETER => unreachable, // We didn't combine RRF.SUBKEY_WOW6464KEY and RRF.SUBKEY_WOW6432KEY | 334 | .SecurityDescriptor = null, |
| 345 | else => return error.StringNotFound, | 335 | .SecurityQualityOfService = null, |
| 346 | } | 336 | }; |
| 347 | switch (actual_type) { | 337 | const status = windows.ntdll.NtOpenKeyEx( |
| 348 | windows.REG.SZ => {}, | 338 | &key.handle, |
| 349 | else => return error.NotAString, | 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: { | 354 | const ValueEntry = union(enum) { |
| 353 | // note(bratishkaerik): somehow returned value in `buf_len` is overestimated by Windows and contains extra space | 355 | default: void, |
| 354 | // we will just search for zero termination and forget length | 356 | name: []const u16, |
| 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); | | |
| 358 | }; | 357 | }; |
| 359 | | 358 | |
| 360 | _ = gpa.resize(value_wtf16le_buf, value_wtf16le.len); | 359 | fn getString( |
| 361 | return value_wtf16le; | 360 | key: Key, |
| 362 | } | 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. | 411 | switch (info.Type) { |
| 365 | fn getDword(reg: RegistryWtf16Le, subkey_wtf16le: [:0]const u16, value_name_wtf16le: [:0]const u16) error{ ValueNameNotFound, NotADword, DwordTooLong, DwordNotFound }!u32 { | 412 | .SZ => {}, |
| 366 | var actual_type: windows.ULONG = undefined; | 413 | else => return error.NotAString, |
| 367 | var reg_size: u32 = @sizeOf(u32); | 414 | } |
| 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 | } | | |
| 387 | | 415 | |
| 388 | switch (actual_type) { | 416 | const data_wtf16_with_nul = @as([*]const u16, @ptrCast(@alignCast(info.data())))[0..@divExact(info.DataLength, 2)]; |
| 389 | windows.REG.DWORD => {}, | 417 | const data_wtf16 = std.mem.trimEnd(u16, data_wtf16_with_nul, L("\x00")); |
| 390 | else => return error.NotADword, | 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; | 424 | fn getDword(key: Key, entry: ValueEntry) error{ ValueNameNotFound, NotADword, DwordNotFound }!windows.DWORD { |
| 394 | } | 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: | 446 | const info: *const windows.KEY.VALUE.PARTIAL_INFORMATION = @ptrCast(&info_buf); |
| 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 | } | | |
| 417 | | 447 | |
| 418 | return .{ .key = key }; | 448 | switch (info.Type) { |
| 419 | } | 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 | pub const Installation = struct { | 458 | pub const Installation = struct { |
| ... | @@ -428,20 +464,21 @@ pub const Installation = struct { | ... | @@ -428,20 +464,21 @@ pub const Installation = struct { |
| 428 | fn find( | 464 | fn find( |
| 429 | gpa: Allocator, | 465 | gpa: Allocator, |
| 430 | io: Io, | 466 | io: Io, |
| 431 | roots_key: RegistryWtf8, | 467 | registry: *Registry, |
| 432 | roots_subkey: []const u8, | 468 | roots_key: Registry.Key, |
| | 469 | roots_subkey: []const u16, |
| 433 | prefix: []const u8, | 470 | prefix: []const u8, |
| 434 | version_key_name: []const u8, | 471 | version_key_name: []const u16, |
| 435 | ) error{ OutOfMemory, InstallationNotFound, PathTooLong, VersionTooLong }!Installation { | 472 | ) error{ OutOfMemory, InstallationNotFound, PathTooLong, VersionTooLong }!Installation { |
| 436 | roots: { | 473 | roots: { |
| 437 | const installation = findFromRoot(gpa, io, roots_key, roots_subkey, prefix) catch | 474 | const installation = findFromRoot(gpa, io, roots_key, roots_subkey, prefix) catch |
| 438 | break :roots; | 475 | break :roots; |
| 439 | if (installation.isValidVersion()) return installation; | 476 | if (installation.isValidVersion(roots_key)) return installation; |
| 440 | installation.free(gpa); | 477 | installation.free(gpa); |
| 441 | } | 478 | } |
| 442 | { | 479 | { |
| 443 | const installation = try findFromInstallationFolder(gpa, version_key_name); | 480 | const installation = try findFromInstallationFolder(gpa, registry, version_key_name); |
| 444 | if (installation.isValidVersion()) return installation; | 481 | if (installation.isValidVersion(roots_key)) return installation; |
| 445 | installation.free(gpa); | 482 | installation.free(gpa); |
| 446 | } | 483 | } |
| 447 | return error.InstallationNotFound; | 484 | return error.InstallationNotFound; |
| ... | @@ -450,29 +487,27 @@ pub const Installation = struct { | ... | @@ -450,29 +487,27 @@ pub const Installation = struct { |
| 450 | fn findFromRoot( | 487 | fn findFromRoot( |
| 451 | gpa: Allocator, | 488 | gpa: Allocator, |
| 452 | io: Io, | 489 | io: Io, |
| 453 | roots_key: RegistryWtf8, | 490 | roots_key: Registry.Key, |
| 454 | roots_subkey: []const u8, | 491 | roots_subkey: []const u16, |
| 455 | prefix: []const u8, | 492 | prefix: []const u8, |
| 456 | ) error{ OutOfMemory, InstallationNotFound, PathTooLong, VersionTooLong }!Installation { | 493 | ) error{ OutOfMemory, InstallationNotFound, PathTooLong, VersionTooLong }!Installation { |
| 457 | const path = path: { | 494 | const path = path: { |
| 458 | const path_maybe_with_trailing_slash = roots_key.getString(gpa, "", roots_subkey) catch |err| switch (err) { | 495 | const path_w_maybe_with_trailing_slash = roots_key.getString(gpa, .{ .name = roots_subkey }, .wtf16) catch |err| switch (err) { |
| 459 | error.NotAString => return error.InstallationNotFound, | 496 | error.NotAString, |
| 460 | error.ValueNameNotFound => return error.InstallationNotFound, | 497 | error.ValueNameNotFound, |
| 461 | error.StringNotFound => return error.InstallationNotFound, | 498 | error.StringNotFound, |
| | 499 | => return error.InstallationNotFound, |
| 462 | | 500 | |
| 463 | error.OutOfMemory => return error.OutOfMemory, | 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)) { | 503 | defer gpa.free(path_w_maybe_with_trailing_slash); |
| 466 | gpa.free(path_maybe_with_trailing_slash); | | |
| 467 | return error.PathTooLong; | | |
| 468 | } | | |
| 469 | | 504 | |
| 470 | var path = std.array_list.Managed(u8).fromOwnedSlice(gpa, path_maybe_with_trailing_slash); | 505 | if (!std.fs.path.isAbsoluteWindowsWtf16(path_w_maybe_with_trailing_slash)) { |
| 471 | errdefer path.deinit(); | 506 | return error.InstallationNotFound; |
| | 507 | } |
| 472 | | 508 | |
| 473 | // String might contain trailing slash, so trim it here | 509 | const path_w = std.mem.trimEnd(u16, path_w_maybe_with_trailing_slash, L("\\/")); |
| 474 | if (path.items.len > "C:\\".len and path.getLast() == '\\') _ = path.pop(); | 510 | break :path try std.unicode.wtf16LeToWtf8Alloc(gpa, path_w); |
| 475 | break :path try path.toOwnedSlice(); | | |
| 476 | }; | 511 | }; |
| 477 | errdefer gpa.free(path); | 512 | errdefer gpa.free(path); |
| 478 | | 513 | |
| ... | @@ -508,70 +543,71 @@ pub const Installation = struct { | ... | @@ -508,70 +543,71 @@ pub const Installation = struct { |
| 508 | | 543 | |
| 509 | fn findFromInstallationFolder( | 544 | fn findFromInstallationFolder( |
| 510 | gpa: Allocator, | 545 | gpa: Allocator, |
| 511 | version_key_name: []const u8, | 546 | registry: *Registry, |
| | 547 | version_key_name: []const u16, |
| 512 | ) error{ OutOfMemory, InstallationNotFound, PathTooLong, VersionTooLong }!Installation { | 548 | ) error{ OutOfMemory, InstallationNotFound, PathTooLong, VersionTooLong }!Installation { |
| 513 | var key_name_buf: [RegistryWtf16Le.key_name_max_len]u8 = undefined; | 549 | const key_name = try std.mem.concat(gpa, u16, &.{ L("Microsoft\\Microsoft SDKs\\Windows\\"), version_key_name }); |
| 514 | const key_name = std.fmt.bufPrint( | 550 | defer gpa.free(key_name); |
| 515 | &key_name_buf, | 551 | |
| 516 | "SOFTWARE\\Microsoft\\Microsoft SDKs\\Windows\\{s}", | 552 | const key = registry.tryOpenSoftwareKeyWithPrecedence(switch (is_32_bit) { |
| 517 | .{version_key_name}, | 553 | true => &.{ |
| 518 | ) catch unreachable; | 554 | .{ .root = .local_machine }, |
| 519 | const key = key: for ([_]bool{ true, false }) |wow6432node| { | 555 | .{ .root = .current_user }, |
| 520 | for ([_]windows.HKEY{ windows.HKEY_LOCAL_MACHINE, windows.HKEY_CURRENT_USER }) |hkey| { | 556 | }, |
| 521 | break :key RegistryWtf8.openKey(hkey, key_name, .{ .wow64_32 = wow6432node }) catch |err| switch (err) { | 557 | false => &.{ |
| 522 | error.KeyNotFound => return error.InstallationNotFound, | 558 | .{ .root = .local_machine, .wow64 = .wow64_32 }, |
| 523 | }; | 559 | .{ .root = .current_user, .wow64 = .wow64_32 }, |
| 524 | } | 560 | .{ .root = .local_machine, .wow64 = .native }, |
| 525 | } else return error.InstallationNotFound; | 561 | .{ .root = .current_user, .wow64 = .native }, |
| 526 | defer key.closeKey(); | 562 | }, |
| | 563 | }, key_name) catch { |
| | 564 | return error.InstallationNotFound; |
| | 565 | }; |
| | 566 | defer key.close(); |
| 527 | | 567 | |
| 528 | const path: []const u8 = path: { | 568 | const path: []const u8 = path: { |
| 529 | const path_maybe_with_trailing_slash = key.getString(gpa, "", "InstallationFolder") catch |err| switch (err) { | 569 | const path_w_maybe_with_trailing_slash = key.getString(gpa, .{ .name = L("InstallationFolder") }, .wtf16) catch |err| switch (err) { |
| 530 | error.NotAString => return error.InstallationNotFound, | 570 | error.NotAString, |
| 531 | error.ValueNameNotFound => return error.InstallationNotFound, | 571 | error.ValueNameNotFound, |
| 532 | error.StringNotFound => return error.InstallationNotFound, | 572 | error.StringNotFound, |
| | 573 | => return error.InstallationNotFound, |
| 533 | | 574 | |
| 534 | error.OutOfMemory => return error.OutOfMemory, | 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)) { | 579 | if (!std.fs.path.isAbsoluteWindowsWtf16(path_w_maybe_with_trailing_slash)) { |
| 538 | gpa.free(path_maybe_with_trailing_slash); | 580 | return error.InstallationNotFound; |
| 539 | return error.PathTooLong; | | |
| 540 | } | 581 | } |
| 541 | | 582 | |
| 542 | var path = std.array_list.Managed(u8).fromOwnedSlice(gpa, path_maybe_with_trailing_slash); | 583 | const path_w = std.mem.trimEnd(u16, path_w_maybe_with_trailing_slash, L("\\/")); |
| 543 | errdefer path.deinit(); | 584 | break :path try std.unicode.wtf16LeToWtf8Alloc(gpa, path_w); |
| 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; | | |
| 550 | }; | 585 | }; |
| 551 | errdefer gpa.free(path); | 586 | errdefer gpa.free(path); |
| 552 | | 587 | |
| 553 | const version: []const u8 = version: { | 588 | const version: []const u8 = version: { |
| 554 | | 589 | // Microsoft doesn't include the .0 in the ProductVersion key |
| 555 | // note(dimenus): 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) { |
| 556 | const version_without_0 = key.getString(gpa, "", "ProductVersion") catch |err| switch (err) { | 591 | error.NotAString, |
| 557 | error.NotAString => return error.InstallationNotFound, | 592 | error.ValueNameNotFound, |
| 558 | error.ValueNameNotFound => return error.InstallationNotFound, | 593 | error.StringNotFound, |
| 559 | error.StringNotFound => return error.InstallationNotFound, | 594 | => return error.InstallationNotFound, |
| 560 | | 595 | |
| 561 | error.OutOfMemory => return error.OutOfMemory, | 596 | error.OutOfMemory => return error.OutOfMemory, |
| 562 | }; | 597 | }; |
| | 598 | defer gpa.free(version_without_0); |
| | 599 | |
| 563 | if (version_without_0.len + ".0".len > product_version_max_length) { | 600 | if (version_without_0.len + ".0".len > product_version_max_length) { |
| 564 | gpa.free(version_without_0); | | |
| 565 | return error.VersionTooLong; | 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 | errdefer version.deinit(); | 605 | errdefer version.deinit(); |
| 570 | | 606 | |
| | 607 | try std.unicode.wtf16LeToWtf8ArrayList(&version, version_without_0); |
| 571 | try version.appendSlice(".0"); | 608 | try version.appendSlice(".0"); |
| 572 | | 609 | |
| 573 | const version_with_0 = try version.toOwnedSlice(); | 610 | break :version try version.toOwnedSlice(); |
| 574 | break :version version_with_0; | | |
| 575 | }; | 611 | }; |
| 576 | errdefer gpa.free(version); | 612 | errdefer gpa.free(version); |
| 577 | | 613 | |
| ... | @@ -579,23 +615,22 @@ pub const Installation = struct { | ... | @@ -579,23 +615,22 @@ pub const Installation = struct { |
| 579 | } | 615 | } |
| 580 | | 616 | |
| 581 | /// Check whether this version is enumerated in registry. | 617 | /// Check whether this version is enumerated in registry. |
| 582 | fn isValidVersion(installation: Installation) bool { | 618 | fn isValidVersion(installation: Installation, roots_key: Registry.Key) bool { |
| 583 | var buf: [Dir.max_path_bytes]u8 = undefined; | 619 | var version_buf: [product_version_max_length]u16 = undefined; |
| 584 | const reg_query_as_wtf8 = std.fmt.bufPrint(buf[0..], "{s}\\{s}\\Installed Options", .{ | 620 | const version_len = std.unicode.wtf8ToWtf16Le(&version_buf, installation.version) catch return false; |
| 585 | windows_kits_reg_key, | 621 | const version = version_buf[0..version_len]; |
| 586 | installation.version, | 622 | const options_key_name = "Installed Options"; |
| 587 | }) catch |err| switch (err) { | 623 | const buf_len = product_version_max_length + options_key_name.len + 2; |
| 588 | error.NoSpaceLeft => return false, | 624 | var buf: [buf_len]u16 = undefined; |
| 589 | }; | 625 | var query: std.ArrayList(u16) = .initBuffer(&buf); |
| 590 | | 626 | query.appendSliceAssumeCapacity(version); |
| 591 | const options_key = RegistryWtf8.openKey( | 627 | query.appendAssumeCapacity('\\'); |
| 592 | windows.HKEY_LOCAL_MACHINE, | 628 | query.appendSliceAssumeCapacity(L(options_key_name)); |
| 593 | reg_query_as_wtf8, | 629 | |
| 594 | .{ .wow64_32 = true }, | 630 | const options_key = roots_key.open(query.items) catch |err| switch (err) { |
| 595 | ) catch |err| switch (err) { | | |
| 596 | error.KeyNotFound => return false, | 631 | error.KeyNotFound => return false, |
| 597 | }; | 632 | }; |
| 598 | defer options_key.closeKey(); | 633 | defer options_key.close(); |
| 599 | | 634 | |
| 600 | const option_name = comptime switch (builtin.target.cpu.arch) { | 635 | const option_name = comptime switch (builtin.target.cpu.arch) { |
| 601 | .thumb => "OptionId.DesktopCPParm", | 636 | .thumb => "OptionId.DesktopCPParm", |
| ... | @@ -605,7 +640,7 @@ pub const Installation = struct { | ... | @@ -605,7 +640,7 @@ pub const Installation = struct { |
| 605 | else => |tag| @compileError("Windows SDK cannot be detected on architecture " ++ tag), | 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 | return (reg_value == 1); | 644 | return (reg_value == 1); |
| 610 | } | 645 | } |
| 611 | | 646 | |
| ... | @@ -616,14 +651,14 @@ pub const Installation = struct { | ... | @@ -616,14 +651,14 @@ pub const Installation = struct { |
| 616 | }; | 651 | }; |
| 617 | | 652 | |
| 618 | const MsvcLibDir = struct { | 653 | const MsvcLibDir = struct { |
| 619 | fn findInstancesDirViaSetup(gpa: Allocator, io: Io) error{ OutOfMemory, PathNotFound }!Dir { | 654 | fn findInstancesDirViaSetup(gpa: Allocator, io: Io, registry: *Registry) error{ OutOfMemory, PathNotFound }!Dir { |
| 620 | const vs_setup_key_path = "SOFTWARE\\Microsoft\\VisualStudio\\Setup"; | 655 | const vs_setup_key_path = L("Microsoft\\VisualStudio\\Setup"); |
| 621 | const vs_setup_key = RegistryWtf8.openKey(windows.HKEY_LOCAL_MACHINE, vs_setup_key_path, .{}) catch |err| switch (err) { | 656 | const vs_setup_key = registry.openSoftwareKey(.{ .root = .local_machine }, vs_setup_key_path) catch |err| switch (err) { |
| 622 | error.KeyNotFound => return error.PathNotFound, | 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 | error.NotAString, | 662 | error.NotAString, |
| 628 | error.ValueNameNotFound, | 663 | error.ValueNameNotFound, |
| 629 | error.StringNotFound, | 664 | error.StringNotFound, |
| ... | @@ -633,22 +668,41 @@ const MsvcLibDir = struct { | ... | @@ -633,22 +668,41 @@ const MsvcLibDir = struct { |
| 633 | }; | 668 | }; |
| 634 | defer gpa.free(packages_path); | 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 | defer gpa.free(instances_path); | 674 | defer gpa.free(instances_path); |
| 640 | | 675 | |
| 641 | return Dir.openDirAbsolute(io, instances_path, .{ .iterate = true }) catch return error.PathNotFound; | 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 | const setup_configuration_clsid = "{177f0c4a-1cd3-4de7-a32c-71dbbb9fa36d}"; | 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 | error.KeyNotFound => return error.PathNotFound, | 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 | error.NotAString, | 706 | error.NotAString, |
| 653 | error.ValueNameNotFound, | 707 | error.ValueNameNotFound, |
| 654 | error.StringNotFound, | 708 | error.StringNotFound, |
| ... | @@ -658,9 +712,9 @@ const MsvcLibDir = struct { | ... | @@ -658,9 +712,9 @@ const MsvcLibDir = struct { |
| 658 | }; | 712 | }; |
| 659 | defer gpa.free(dll_path); | 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 | // the .dll filename | 718 | // the .dll filename |
| 665 | _ = path_it.last(); | 719 | _ = path_it.last(); |
| 666 | const root_path = while (path_it.previous()) |dir_component| { | 720 | const root_path = while (path_it.previous()) |dir_component| { |
| ... | @@ -671,7 +725,7 @@ const MsvcLibDir = struct { | ... | @@ -671,7 +725,7 @@ const MsvcLibDir = struct { |
| 671 | return error.PathNotFound; | 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 | defer gpa.free(instances_path); | 729 | defer gpa.free(instances_path); |
| 676 | | 730 | |
| 677 | return Dir.openDirAbsolute(io, instances_path, .{ .iterate = true }) catch return error.PathNotFound; | 731 | return Dir.openDirAbsolute(io, instances_path, .{ .iterate = true }) catch return error.PathNotFound; |
| ... | @@ -680,12 +734,13 @@ const MsvcLibDir = struct { | ... | @@ -680,12 +734,13 @@ const MsvcLibDir = struct { |
| 680 | fn findInstancesDir( | 734 | fn findInstancesDir( |
| 681 | gpa: Allocator, | 735 | gpa: Allocator, |
| 682 | io: Io, | 736 | io: Io, |
| | 737 | registry: *Registry, |
| 683 | environ_map: *const Environ.Map, | 738 | environ_map: *const Environ.Map, |
| 684 | ) error{ OutOfMemory, PathNotFound }!Dir { | 739 | ) error{ OutOfMemory, PathNotFound }!Dir { |
| 685 | // First, try getting the packages cache path from the registry. | 740 | // First, try getting the packages cache path from the registry. |
| 686 | // This only seems to exist when the path is different from the default. | 741 | // This only seems to exist when the path is different from the default. |
| 687 | method1: { | 742 | method1: { |
| 688 | return findInstancesDirViaSetup(gpa, io) catch |err| switch (err) { | 743 | return findInstancesDirViaSetup(gpa, io, registry) catch |err| switch (err) { |
| 689 | error.OutOfMemory => |e| return e, | 744 | error.OutOfMemory => |e| return e, |
| 690 | error.PathNotFound => break :method1, | 745 | error.PathNotFound => break :method1, |
| 691 | }; | 746 | }; |
| ... | @@ -693,7 +748,7 @@ const MsvcLibDir = struct { | ... | @@ -693,7 +748,7 @@ const MsvcLibDir = struct { |
| 693 | // Otherwise, try to get the path from the .dll that would have been | 748 | // Otherwise, try to get the path from the .dll that would have been |
| 694 | // loaded via COM for SetupConfiguration. | 749 | // loaded via COM for SetupConfiguration. |
| 695 | method2: { | 750 | method2: { |
| 696 | return findInstancesDirViaCLSID(gpa, io) catch |err| switch (err) { | 751 | return findInstancesDirViaCLSID(gpa, io, registry) catch |err| switch (err) { |
| 697 | error.OutOfMemory => |e| return e, | 752 | error.OutOfMemory => |e| return e, |
| 698 | error.PathNotFound => break :method2, | 753 | error.PathNotFound => break :method2, |
| 699 | }; | 754 | }; |
| ... | @@ -703,7 +758,7 @@ const MsvcLibDir = struct { | ... | @@ -703,7 +758,7 @@ const MsvcLibDir = struct { |
| 703 | method3: { | 758 | method3: { |
| 704 | const program_data = std.zig.EnvVar.PROGRAMDATA.get(environ_map) orelse break :method3; | 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 | const instances_path = try Dir.path.join(gpa, &.{ | 763 | const instances_path = try Dir.path.join(gpa, &.{ |
| 709 | program_data, "Microsoft", "VisualStudio", "Packages", "_Instances", | 764 | program_data, "Microsoft", "VisualStudio", "Packages", "_Instances", |
| ... | @@ -764,6 +819,7 @@ const MsvcLibDir = struct { | ... | @@ -764,6 +819,7 @@ const MsvcLibDir = struct { |
| 764 | fn findViaCOM( | 819 | fn findViaCOM( |
| 765 | gpa: Allocator, | 820 | gpa: Allocator, |
| 766 | io: Io, | 821 | io: Io, |
| | 822 | registry: *Registry, |
| 767 | arch: std.Target.Cpu.Arch, | 823 | arch: std.Target.Cpu.Arch, |
| 768 | environ_map: *const Environ.Map, | 824 | environ_map: *const Environ.Map, |
| 769 | ) error{ OutOfMemory, PathNotFound }![]const u8 { | 825 | ) error{ OutOfMemory, PathNotFound }![]const u8 { |
| ... | @@ -771,7 +827,7 @@ const MsvcLibDir = struct { | ... | @@ -771,7 +827,7 @@ const MsvcLibDir = struct { |
| 771 | // This will contain directories with names of instance IDs like 80a758ca, | 827 | // This will contain directories with names of instance IDs like 80a758ca, |
| 772 | // which will contain `state.json` files that have the version and | 828 | // which will contain `state.json` files that have the version and |
| 773 | // installation directory. | 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 | defer instances_dir.close(io); | 831 | defer instances_dir.close(io); |
| 776 | | 832 | |
| 777 | var state_subpath_buf: [Dir.max_name_bytes + 32]u8 = undefined; | 833 | var state_subpath_buf: [Dir.max_name_bytes + 32]u8 = undefined; |
| ... | @@ -874,7 +930,6 @@ const MsvcLibDir = struct { | ... | @@ -874,7 +930,6 @@ const MsvcLibDir = struct { |
| 874 | arch: std.Target.Cpu.Arch, | 930 | arch: std.Target.Cpu.Arch, |
| 875 | environ_map: *const Environ.Map, | 931 | environ_map: *const Environ.Map, |
| 876 | ) error{ OutOfMemory, PathNotFound }![]const u8 { | 932 | ) error{ OutOfMemory, PathNotFound }![]const u8 { |
| 877 | | | |
| 878 | // %localappdata%\Microsoft\VisualStudio\ | 933 | // %localappdata%\Microsoft\VisualStudio\ |
| 879 | // %appdata%\Local\Microsoft\VisualStudio\ | 934 | // %appdata%\Local\Microsoft\VisualStudio\ |
| 880 | const local_app_data_path = std.zig.EnvVar.LOCALAPPDATA.get(environ_map) orelse return error.PathNotFound; | 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,15 +938,18 @@ const MsvcLibDir = struct { |
| 883 | }); | 938 | }); |
| 884 | defer gpa.free(visualstudio_folder_path); | 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 | const vs_versions: []const []const u8 = vs_versions: { | 950 | const vs_versions: []const []const u8 = vs_versions: { |
| 887 | if (!Dir.path.isAbsolute(visualstudio_folder_path)) return error.PathNotFound; | | |
| 888 | // enumerate folders that contain `privateregistry.bin`, looking for all versions | 951 | // enumerate folders that contain `privateregistry.bin`, looking for all versions |
| 889 | // f.i. %localappdata%\Microsoft\VisualStudio\17.0_9e9cbb98\ | 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 | var iterator = visualstudio_folder.iterate(); | 953 | var iterator = visualstudio_folder.iterate(); |
| 896 | break :vs_versions try iterateAndFilterByVersion(&iterator, gpa, io, ""); | 954 | break :vs_versions try iterateAndFilterByVersion(&iterator, gpa, io, ""); |
| 897 | }; | 955 | }; |
| ... | @@ -899,25 +957,94 @@ const MsvcLibDir = struct { | ... | @@ -899,25 +957,94 @@ const MsvcLibDir = struct { |
| 899 | for (vs_versions) |vs_version| gpa.free(vs_version); | 957 | for (vs_versions) |vs_version| gpa.free(vs_version); |
| 900 | gpa.free(vs_versions); | 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 | const source_directories: []const u8 = source_directories: for (vs_versions) |vs_version| { | 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; | 963 | const sub_path = blk: { |
| 905 | defer gpa.free(privateregistry_absolute_path); | 964 | var buf: std.ArrayList(u16) = .initBuffer(&sub_path_buf); |
| 906 | if (!Dir.path.isAbsolute(privateregistry_absolute_path)) continue; | 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; | 970 | // The goal is to emulate advapi32.RegLoadAppKeyW with a direct call |
| 909 | defer visualstudio_registry.closeKey(); | 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 | error.OutOfMemory => return error.OutOfMemory, | 1045 | error.OutOfMemory => return error.OutOfMemory, |
| 915 | else => continue, | 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 | break :source_directories source_directories_value; | 1049 | break :source_directories source_directories_value; |
| 923 | } else return error.PathNotFound; | 1050 | } else return error.PathNotFound; |
| ... | @@ -967,6 +1094,7 @@ const MsvcLibDir = struct { | ... | @@ -967,6 +1094,7 @@ const MsvcLibDir = struct { |
| 967 | fn findViaVs7Key( | 1094 | fn findViaVs7Key( |
| 968 | gpa: Allocator, | 1095 | gpa: Allocator, |
| 969 | io: Io, | 1096 | io: Io, |
| | 1097 | registry: *Registry, |
| 970 | arch: std.Target.Cpu.Arch, | 1098 | arch: std.Target.Cpu.Arch, |
| 971 | environ_map: *const Environ.Map, | 1099 | environ_map: *const Environ.Map, |
| 972 | ) error{ OutOfMemory, PathNotFound }![]const u8 { | 1100 | ) error{ OutOfMemory, PathNotFound }![]const u8 { |
| ... | @@ -986,10 +1114,10 @@ const MsvcLibDir = struct { | ... | @@ -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; | 1117 | const vs7_key = registry.openSoftwareKey(.{ .root = .local_machine, .wow64 = .wow64_32 }, L("Microsoft\\VisualStudio\\SxS\\VS7")) catch return error.PathNotFound; |
| 990 | defer vs7_key.closeKey(); | 1118 | defer vs7_key.close(); |
| 991 | try_vs7_key: { | 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 | error.OutOfMemory => return error.OutOfMemory, | 1121 | error.OutOfMemory => return error.OutOfMemory, |
| 994 | else => break :try_vs7_key, | 1122 | else => break :try_vs7_key, |
| 995 | }; | 1123 | }; |
| ... | @@ -1045,14 +1173,15 @@ const MsvcLibDir = struct { | ... | @@ -1045,14 +1173,15 @@ const MsvcLibDir = struct { |
| 1045 | pub fn find( | 1173 | pub fn find( |
| 1046 | gpa: Allocator, | 1174 | gpa: Allocator, |
| 1047 | io: Io, | 1175 | io: Io, |
| | 1176 | registry: *Registry, |
| 1048 | arch: std.Target.Cpu.Arch, | 1177 | arch: std.Target.Cpu.Arch, |
| 1049 | environ_map: *const Environ.Map, | 1178 | environ_map: *const Environ.Map, |
| 1050 | ) error{ OutOfMemory, MsvcLibDirNotFound }![]const u8 { | 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 | error.OutOfMemory => return error.OutOfMemory, | 1181 | error.OutOfMemory => return error.OutOfMemory, |
| 1053 | error.PathNotFound => MsvcLibDir.findViaRegistry(gpa, io, arch, environ_map) catch |err2| switch (err2) { | 1182 | error.PathNotFound => MsvcLibDir.findViaRegistry(gpa, io, arch, environ_map) catch |err2| switch (err2) { |
| 1054 | error.OutOfMemory => return error.OutOfMemory, | 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 | error.OutOfMemory => return error.OutOfMemory, | 1185 | error.OutOfMemory => return error.OutOfMemory, |
| 1057 | error.PathNotFound => return error.MsvcLibDirNotFound, | 1186 | error.PathNotFound => return error.MsvcLibDirNotFound, |
| 1058 | }, | 1187 | }, |