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 }