fn findViaRegistry(
gpa: Allocator,
io: Io,
arch: std.Target.Cpu.Arch,
environ_map: *const Environ.Map,
) error{ OutOfMemory, PathNotFound }![]const u8 {
// %localappdata%\Microsoft\VisualStudio\
// %appdata%\Local\Microsoft\VisualStudio\
const local_app_data_path = std.zig.EnvVar.LOCALAPPDATA.get(environ_map) orelse return error.PathNotFound;
const visualstudio_folder_path = try Dir.path.join(gpa, &.{
local_app_data_path, "Microsoft\\VisualStudio\\",
});
defer gpa.free(visualstudio_folder_path);
if (!Dir.path.isAbsolute(visualstudio_folder_path)) return error.PathNotFound;
// To make things easier later on, we open the VisualStudio directory here which
// allows us to pass relative paths to NtLoadKeyEx in order to avoid dealing with
// conversion to NT namespace paths.
var visualstudio_folder = Dir.openDirAbsolute(io, visualstudio_folder_path, .{
.iterate = true,
}) catch return error.PathNotFound;
defer visualstudio_folder.close(io);
const vs_versions: []const []const u8 = vs_versions: {
// enumerate folders that contain `privateregistry.bin`, looking for all versions
// f.i. %localappdata%\Microsoft\VisualStudio\17.0_9e9cbb98\
var iterator = visualstudio_folder.iterate();
break :vs_versions try iterateAndFilterByVersion(&iterator, gpa, io, "");
};
defer {
for (vs_versions) |vs_version| gpa.free(vs_version);
gpa.free(vs_versions);
}
var key_path_buf: [windows.NAME_MAX * 2]u16 = undefined;
var sub_path_buf: [windows.NAME_MAX * 2]u16 = undefined;
const source_directories: []const u8 = source_directories: for (vs_versions) |vs_version| {
const sub_path = blk: {
var buf: std.ArrayList(u16) = .initBuffer(&sub_path_buf);
buf.items.len += std.unicode.wtf8ToWtf16Le(buf.unusedCapacitySlice(), vs_version) catch unreachable;
buf.appendSliceAssumeCapacity(L("\\privateregistry.bin"));
break :blk buf.items;
};
// The goal is to emulate advapi32.RegLoadAppKeyW with a direct call
// to NtLoadKeyEx instead.
//
// RegLoadAppKeyW loads the hive into a registry key of the format:
// \REGISTRY\A\{fdb2baa5-8ca8-ef03-78d0-3b1f868fd2a9}
// where `\REGISTRY\A` is a special unenumerable location intended for
// per-app hives, and the GUID is randomly generated (in testing, it
// was different for each run of the program).
//
// The OS is responsible for cleaning up `\REGISTRY\A` whenever all handles
// to one of its keys are closed, so we don't have to do anything special
// with regards to that.
const temp_key_path = blk: {
var guid: windows.GUID = undefined;
io.random(std.mem.asBytes(&guid));
var guid_buf: [38]u8 = undefined;
const guid_str = std.mem.print(&guid_buf, "{f}", .{guid}) catch unreachable;
var buf: std.ArrayList(u16) = .initBuffer(&key_path_buf);
buf.appendSliceAssumeCapacity(L("\\REGISTRY\\A\\"));
buf.items.len += std.unicode.wtf8ToWtf16Le(buf.unusedCapacitySlice(), guid_str) catch unreachable;
break :blk buf.items;
};
const target_key: windows.OBJECT.ATTRIBUTES = .{
.RootDirectory = null,
.Attributes = .{},
.ObjectName = @constCast(&windows.UNICODE_STRING.init(temp_key_path)),
.SecurityDescriptor = null,
};
const source_file: windows.OBJECT.ATTRIBUTES = .{
.RootDirectory = visualstudio_folder.handle,
.Attributes = .{},
.ObjectName = @constCast(&windows.UNICODE_STRING.init(sub_path)),
.SecurityDescriptor = null,
};
var root_key: Registry.Key = undefined;
const rc = windows.ntdll.NtLoadKeyEx(
&target_key,
&source_file,
.{
.APP_HIVE = true,
// This wasn't set by RegLoadAppKeyW, but it seems relevant
// since we aren't intending to do any modifcation of the hive.
.OPEN_READ_ONLY = true,
},
null,
null,
.{ .SPECIFIC = .{
.KEY = .{
.QUERY_VALUE = true,
.ENUMERATE_SUB_KEYS = true,
},
} },
&root_key.handle,
null,
);
switch (rc) {
.SUCCESS => {},
else => continue,
}
defer root_key.close();
const config_path = blk: {
var buf: std.ArrayList(u16) = .initBuffer(&key_path_buf);
buf.appendSliceAssumeCapacity(L("Software\\Microsoft\\VisualStudio\\"));
buf.items.len += std.unicode.wtf8ToWtf16Le(buf.unusedCapacitySlice(), vs_version) catch unreachable;
buf.appendSliceAssumeCapacity(L("_Config"));
break :blk buf.items;
};
const config_key = root_key.open(config_path) catch continue;
const source_directories_value = config_key.getString(gpa, .{ .name = L("Source Directories") }, .wtf8) catch |err| switch (err) {
error.OutOfMemory => |e| return e,
else => continue,
};
break :source_directories source_directories_value;
} else return error.PathNotFound;
defer gpa.free(source_directories);
var source_directories_split = std.mem.splitScalar(u8, source_directories, ';');
const msvc_dir: []const u8 = msvc_dir: {
const msvc_include_dir_maybe_with_trailing_slash = try gpa.dupe(u8, source_directories_split.first());
if (msvc_include_dir_maybe_with_trailing_slash.len > Dir.max_path_bytes or !Dir.path.isAbsolute(msvc_include_dir_maybe_with_trailing_slash)) {
gpa.free(msvc_include_dir_maybe_with_trailing_slash);
return error.PathNotFound;
}
var msvc_dir = std.array_list.Managed(u8).fromOwnedSlice(gpa, msvc_include_dir_maybe_with_trailing_slash);
errdefer msvc_dir.deinit();
// String might contain trailing slash, so trim it here
if (msvc_dir.items.len > "C:\\".len and msvc_dir.last().? == '\\') _ = msvc_dir.pop();
// Remove `\include` at the end of path
if (std.mem.endsWith(u8, msvc_dir.items, "\\include")) {
msvc_dir.shrinkRetainingCapacity(msvc_dir.items.len - "\\include".len);
}
try msvc_dir.appendSlice("\\Lib\\");
try msvc_dir.appendSlice(switch (arch) {
.thumb => "arm",
.aarch64 => "arm64",
.x86 => "x86",
.x86_64 => "x64",
else => unreachable,
});
const msvc_dir_with_arch = try msvc_dir.toOwnedSlice();
break :msvc_dir msvc_dir_with_arch;
};
errdefer gpa.free(msvc_dir);
if (!verifyLibDir(io, msvc_dir)) {
return error.PathNotFound;
}
return msvc_dir;
}
https://learn.microsoft.com/en-us/visualstudio/install/tools-for-managing-visual-studio-instances?view=vs-2022#editing-the-registry-for-a-visual-studio-instance