github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/install/winversion.go (about)

     1  // Copyright 2018 Keybase Inc. All rights reserved.
     2  // Use of this source code is governed by a BSD
     3  // license that can be found in the LICENSE file.
     4  // Adapted mainly from github.com/gonutz/w32
     5  
     6  //go:build windows
     7  // +build windows
     8  
     9  package install
    10  
    11  import (
    12  	"errors"
    13  	"syscall"
    14  	"unsafe"
    15  
    16  	"golang.org/x/sys/windows"
    17  )
    18  
    19  var (
    20  	version                = windows.NewLazySystemDLL("version.dll")
    21  	getFileVersionInfoSize = version.NewProc("GetFileVersionInfoSizeW")
    22  	getFileVersionInfo     = version.NewProc("GetFileVersionInfoW")
    23  	verQueryValue          = version.NewProc("VerQueryValueW")
    24  )
    25  
    26  type VS_FIXEDFILEINFO struct {
    27  	Signature        uint32
    28  	StrucVersion     uint32
    29  	FileVersionMS    uint32
    30  	FileVersionLS    uint32
    31  	ProductVersionMS uint32
    32  	ProductVersionLS uint32
    33  	FileFlagsMask    uint32
    34  	FileFlags        uint32
    35  	FileOS           uint32
    36  	FileType         uint32
    37  	FileSubtype      uint32
    38  	FileDateMS       uint32
    39  	FileDateLS       uint32
    40  }
    41  
    42  type WinVersion struct {
    43  	Major uint32
    44  	Minor uint32
    45  	Patch uint32
    46  	Build uint32
    47  }
    48  
    49  // FileVersion concatenates FileVersionMS and FileVersionLS to a uint64 value.
    50  func (fi VS_FIXEDFILEINFO) FileVersion() uint64 {
    51  	return uint64(fi.FileVersionMS)<<32 | uint64(fi.FileVersionLS)
    52  }
    53  
    54  func GetFileVersionInfoSize(path string) uint32 {
    55  	ret, _, _ := getFileVersionInfoSize.Call(
    56  		uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(path))),
    57  		0,
    58  	)
    59  	return uint32(ret)
    60  }
    61  
    62  func GetFileVersionInfo(path string, data []byte) bool {
    63  	ret, _, _ := getFileVersionInfo.Call(
    64  		uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(path))),
    65  		0,
    66  		uintptr(len(data)),
    67  		uintptr(unsafe.Pointer(&data[0])),
    68  	)
    69  	return ret != 0
    70  }
    71  
    72  // VerQueryValueRoot calls VerQueryValue
    73  // (https://msdn.microsoft.com/en-us/library/windows/desktop/ms647464(v=vs.85).aspx)
    74  // with `\` (root) to retieve the VS_FIXEDFILEINFO.
    75  func VerQueryValueRoot(block []byte) (VS_FIXEDFILEINFO, error) {
    76  	var offset uintptr
    77  	var length uint
    78  	blockStart := unsafe.Pointer(&block[0])
    79  	ret, _, _ := verQueryValue.Call(
    80  		uintptr(blockStart),
    81  		uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(`\`))),
    82  		uintptr(unsafe.Pointer(&offset)),
    83  		uintptr(unsafe.Pointer(&length)),
    84  	)
    85  	if ret == 0 {
    86  		return VS_FIXEDFILEINFO{}, errors.New("VerQueryValueRoot: verQueryValue failed")
    87  	}
    88  	start := int(offset) - int(uintptr(blockStart))
    89  	end := start + int(length)
    90  	if start < 0 || start >= len(block) || end < start || end > len(block) {
    91  		return VS_FIXEDFILEINFO{}, errors.New("VerQueryValueRoot: find failed")
    92  	}
    93  	data := block[start:end]
    94  	info := *((*VS_FIXEDFILEINFO)(unsafe.Pointer(&data[0])))
    95  	return info, nil
    96  }
    97  
    98  func GetFileVersion(path string) (WinVersion, error) {
    99  	var result WinVersion
   100  	size := GetFileVersionInfoSize(path)
   101  	if size <= 0 {
   102  		return result, errors.New("GetFileVersionInfoSize failed")
   103  	}
   104  
   105  	info := make([]byte, size)
   106  	ok := GetFileVersionInfo(path, info)
   107  	if !ok {
   108  		return result, errors.New("GetFileVersionInfo failed")
   109  	}
   110  
   111  	fixed, err := VerQueryValueRoot(info)
   112  	if err != nil {
   113  		return result, err
   114  	}
   115  	version := fixed.FileVersion()
   116  
   117  	result.Major = uint32(version & 0xFFFF000000000000 >> 48)
   118  	result.Minor = uint32(version & 0x0000FFFF00000000 >> 32)
   119  	result.Patch = uint32(version & 0x00000000FFFF0000 >> 16)
   120  	result.Build = uint32(version & 0x000000000000FFFF)
   121  
   122  	return result, nil
   123  }