mirror of
https://github.com/Cheviiot/Vintner.git
synced 2026-08-03 15:57:24 +00:00
download --with-wdk fetches the WDK headers/libs/host-tools NuGet packages (nuget.org has no vsman-manifest entry for this content) and lays them out where the DriverKit.BuildTools PlatformToolset expects them. msbuildEnv wires WDKContentRoot/WDKBuildFolder through DisableRegistryUse the same way the SDK/toolset paths already are. Two WDK-package fixups were needed for a real driver to actually build under Wine: the bundled build-task assembly is versioned for an older VisualStudioVersion than ours, and the package ships no x86 host-tools directory at all (only x64/arm64), which StampInf hardcodes a path to. Also force TZ=UTC for msbuild invocations: StampInf stamps DriverVer using the local wall-clock date while Inf2Cat validates it against UTC "now", so any timezone east of UTC sees a "postdated DriverVer" failure for most of the day. Verified against a real KMDF sample driver (microsoft/Windows-driver- samples' echo_2): compiles, links, INF stamps and passes Inf2Cat's signability check with SignMode=off.
265 lines
8.8 KiB
Go
265 lines
8.8 KiB
Go
package download
|
|
|
|
import (
|
|
"archive/zip"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/fs"
|
|
"net/url"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
// The actual driver headers/libs (ntddk.h, wdf, km/um import libs) aren't
|
|
// part of the VS installer manifest at all - Component.Microsoft.Windows.
|
|
// DriverKit.BuildTools (see select.go) only registers the
|
|
// WindowsKernelModeDriver10.0/WindowsUserModeDriver10.0 PlatformToolsets in
|
|
// the MSBuild tree. The content those toolsets actually need ships as a
|
|
// separate per-architecture NuGet package, independently of the vsman
|
|
// pipeline: https://www.nuget.org/packages/Microsoft.Windows.WDK.x64 (and
|
|
// .ARM64). This file fetches and unpacks that package directly through the
|
|
// NuGet v3 flat-container API, without needing nuget.exe or a project
|
|
// restore.
|
|
|
|
const nugetFlatContainer = "https://api.nuget.org/v3-flatcontainer"
|
|
|
|
// WDKNuGetID returns the nuget.org package id providing WDK content for
|
|
// arch ("x86"/"x64" both use the x64 package - there's no 32-bit WDK
|
|
// package - "arm64" uses the ARM64 one).
|
|
func WDKNuGetID(arch string) string {
|
|
if arch == "arm64" {
|
|
return "Microsoft.Windows.WDK.ARM64"
|
|
}
|
|
return "Microsoft.Windows.WDK.x64"
|
|
}
|
|
|
|
// nugetVersionIndex is the "versions" list nuget.org's flat-container index
|
|
// returns, oldest first.
|
|
type nugetVersionIndex struct {
|
|
Versions []string `json:"versions"`
|
|
}
|
|
|
|
var reNuGetPrerelease = regexp.MustCompile(`-`)
|
|
|
|
// FetchLatestWDKVersion returns the newest stable (non-prerelease) version
|
|
// of arch's WDK NuGet package, preferring one whose version starts with
|
|
// sdkBuild (e.g. "10.0.26100", to match an already-selected Windows SDK) if
|
|
// any such version exists; otherwise it returns the newest stable version
|
|
// overall.
|
|
func FetchLatestWDKVersion(arch, sdkBuild string) (string, error) {
|
|
id := strings.ToLower(WDKNuGetID(arch))
|
|
data, err := httpGet(nugetFlatContainer + "/" + id + "/index.json")
|
|
if err != nil {
|
|
return "", fmt.Errorf("listing %s versions: %w", WDKNuGetID(arch), err)
|
|
}
|
|
var idx nugetVersionIndex
|
|
if err := json.Unmarshal(data, &idx); err != nil {
|
|
return "", fmt.Errorf("parsing %s version index: %w", WDKNuGetID(arch), err)
|
|
}
|
|
|
|
var latestMatching, latestAny string
|
|
for _, v := range idx.Versions {
|
|
if reNuGetPrerelease.MatchString(v) {
|
|
continue
|
|
}
|
|
latestAny = v
|
|
if sdkBuild != "" && strings.HasPrefix(v, sdkBuild) {
|
|
latestMatching = v
|
|
}
|
|
}
|
|
if latestMatching != "" {
|
|
return latestMatching, nil
|
|
}
|
|
if latestAny != "" {
|
|
return latestAny, nil
|
|
}
|
|
return "", fmt.Errorf("no stable release of %s found", WDKNuGetID(arch))
|
|
}
|
|
|
|
// SDKBuildPrefix extracts the "10.0.26100"-style build prefix from a
|
|
// selected Win10SDK/Win11SDK package's version (e.g. "10.0.26100.1742" ->
|
|
// "10.0.26100"), used to pick a matching WDK NuGet version. Returns "" if
|
|
// selected contains no such package.
|
|
func SDKBuildPrefix(selected []*Package) string {
|
|
for _, p := range selected {
|
|
id := strings.ToLower(p.ID)
|
|
if strings.HasPrefix(id, "win10sdk") || strings.HasPrefix(id, "win11sdk") {
|
|
parts := strings.SplitN(p.Version, ".", 4)
|
|
if len(parts) >= 3 {
|
|
return strings.Join(parts[:3], ".")
|
|
}
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// DownloadWDK fetches (or reuses a cached copy of) arch's WDK NuGet package
|
|
// at version, then unpacks its "c" directory - headers, libs, and the
|
|
// per-SDK-build MSBuild props/targets the DriverKit.BuildTools PlatformToolset
|
|
// registration imports via $(WDKContentRoot) - into destDir/wdk/<arch>/c.
|
|
// Kept as its own self-contained tree rather than merged into the regular
|
|
// SDK dirs, matching how $(WDKContentRoot) is meant to be used.
|
|
//
|
|
// vsVersion is the installed MSBuild's own $(VisualStudioVersion) (e.g.
|
|
// "18.0"): the package's WindowsDriver.common.targets loads its custom
|
|
// build tasks from an assembly named for that property
|
|
// (Microsoft.DriverKit.Build.Tasks.$(VisualStudioVersion).dll), but the
|
|
// package only ships one built for whatever (older) VS generation it
|
|
// targeted - see duplicateVersionedBuildTaskAssemblies.
|
|
func DownloadWDK(arch, version, cacheDir, destDir, vsVersion string) (string, error) {
|
|
id := WDKNuGetID(arch)
|
|
idLower := strings.ToLower(id)
|
|
nupkgURL := fmt.Sprintf("%s/%s/%s/%s.%s.nupkg", nugetFlatContainer, idLower, version, idLower, version)
|
|
|
|
cacheFile := filepath.Join(cacheDir, fmt.Sprintf("%s-%s.nupkg", idLower, version))
|
|
if !isFile(cacheFile) {
|
|
fmt.Printf("Downloading %s %s\n", id, version)
|
|
if err := httpDownloadFile(nupkgURL, cacheFile); err != nil {
|
|
return "", fmt.Errorf("downloading %s %s: %w", id, version, err)
|
|
}
|
|
} else {
|
|
fmt.Printf("Using existing file %s\n", filepath.Base(cacheFile))
|
|
}
|
|
|
|
wdkDir := filepath.Join(destDir, "wdk", arch)
|
|
cDir := filepath.Join(wdkDir, "c")
|
|
if err := extractNuGetPackageDir(cacheFile, "c/", cDir); err != nil {
|
|
return "", fmt.Errorf("unpacking %s %s: %w", id, version, err)
|
|
}
|
|
if err := duplicateVersionedBuildTaskAssemblies(cDir, vsVersion); err != nil {
|
|
return "", fmt.Errorf("adapting %s %s to VisualStudioVersion %s: %w", id, version, vsVersion, err)
|
|
}
|
|
if err := fillMissingHostTools(cDir); err != nil {
|
|
return "", fmt.Errorf("filling in missing x86 host tools for %s %s: %w", id, version, err)
|
|
}
|
|
return wdkDir, nil
|
|
}
|
|
|
|
// fillMissingHostTools copies every file from each cDir/bin/<sdkver>/x64
|
|
// directory into its sibling .../x86 directory, adding whatever's missing
|
|
// without overwriting anything already there. The WDK NuGet package's x86
|
|
// host-tool directory only ships a handful of genuinely x86-specific files
|
|
// (Inf2Cat.exe among them); most driver build tools (stampinf.exe included)
|
|
// only ship as x64 binaries, but WindowsDriver.Common.targets hardcodes an
|
|
// x86 tool path (WDKBinRoot_x86) with no fallback. This isn't "faking" an
|
|
// x86 binary - it's just running the real x64 PE binaries from a directory
|
|
// MSBuild happens to call "x86"; Wine doesn't care what the folder is
|
|
// named, only the PE header when it execs the file, and we're always on an
|
|
// x64 host/Wine setup here.
|
|
func fillMissingHostTools(cDir string) error {
|
|
binDir := filepath.Join(cDir, "bin")
|
|
entries, err := os.ReadDir(binDir)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
for _, e := range entries {
|
|
if !e.IsDir() {
|
|
continue
|
|
}
|
|
x64Dir := filepath.Join(binDir, e.Name(), "x64")
|
|
if !isDir(x64Dir) {
|
|
continue
|
|
}
|
|
x86Dir := filepath.Join(binDir, e.Name(), "x86")
|
|
if err := os.MkdirAll(x86Dir, 0o755); err != nil {
|
|
return err
|
|
}
|
|
files, err := os.ReadDir(x64Dir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, f := range files {
|
|
if f.IsDir() {
|
|
continue
|
|
}
|
|
dst := filepath.Join(x86Dir, f.Name())
|
|
if isFile(dst) {
|
|
continue
|
|
}
|
|
if err := copyFile(filepath.Join(x64Dir, f.Name()), dst, 0o755); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
var reVersionedDLL = regexp.MustCompile(`^(.*)\.\d+\.\d+\.dll$`)
|
|
|
|
// duplicateVersionedBuildTaskAssemblies copies every "*.<oldver>.dll" file
|
|
// under cDir/build (the driver build task assemblies, e.g.
|
|
// Microsoft.DriverKit.Build.Tasks.17.0.dll) to a sibling
|
|
// "*.<targetVSVersion>.dll" when one doesn't already exist. The WDK NuGet
|
|
// package bundles these named for whatever VS generation it was built
|
|
// against; our installed MSBuild may be newer and looks them up by its own
|
|
// $(VisualStudioVersion). The assemblies aren't VS-version-specific in
|
|
// behavior - only the file name encodes an expected caller - so serving the
|
|
// same bytes under the new name is safe.
|
|
func duplicateVersionedBuildTaskAssemblies(cDir, targetVSVersion string) error {
|
|
buildDir := filepath.Join(cDir, "build")
|
|
if !isDir(buildDir) {
|
|
return nil
|
|
}
|
|
return filepath.WalkDir(buildDir, func(path string, d fs.DirEntry, err error) error {
|
|
if err != nil || d.IsDir() {
|
|
return err
|
|
}
|
|
m := reVersionedDLL.FindStringSubmatch(d.Name())
|
|
if m == nil || strings.HasSuffix(d.Name(), "."+targetVSVersion+".dll") {
|
|
return nil
|
|
}
|
|
target := filepath.Join(filepath.Dir(path), m[1]+"."+targetVSVersion+".dll")
|
|
if isFile(target) {
|
|
return nil
|
|
}
|
|
return copyFile(path, target, 0o644)
|
|
})
|
|
}
|
|
|
|
// extractNuGetPackageDir extracts every entry of nupkgFile whose name starts
|
|
// with prefix into dest, stripping prefix from each resulting path.
|
|
func extractNuGetPackageDir(nupkgFile, prefix, dest string) error {
|
|
r, err := zip.OpenReader(nupkgFile)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer r.Close()
|
|
|
|
if err := os.MkdirAll(dest, 0o755); err != nil {
|
|
return err
|
|
}
|
|
for _, f := range r.File {
|
|
name, err := url.PathUnescape(f.Name)
|
|
if err != nil {
|
|
name = f.Name
|
|
}
|
|
name = strings.ReplaceAll(name, `\`, "/")
|
|
if !strings.HasPrefix(name, prefix) {
|
|
continue
|
|
}
|
|
rel := strings.TrimPrefix(name, prefix)
|
|
if rel == "" {
|
|
continue
|
|
}
|
|
target := filepath.Join(dest, rel)
|
|
if f.FileInfo().IsDir() {
|
|
if err := os.MkdirAll(target, 0o755); err != nil {
|
|
return err
|
|
}
|
|
continue
|
|
}
|
|
if err := os.MkdirAll(filepath.Dir(target), 0o755); err != nil {
|
|
return err
|
|
}
|
|
if err := extractZipEntry(f, target); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|