├── .gitignore ├── cmd └── get-priv-data-gfe │ ├── main.go │ ├── manifest.go │ ├── disasm.go │ └── download.go ├── go.mod ├── LICENSE ├── .goreleaser.yml ├── README.md └── go.sum /.gitignore: -------------------------------------------------------------------------------- 1 | _nvspcaps64.dll-notes.txt 2 | dist/ 3 | -------------------------------------------------------------------------------- /cmd/get-priv-data-gfe/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func main() { 8 | data := getPrivData() 9 | var validStr = "valid" 10 | if valid := checkValidData(data); !valid { 11 | validStr = "not valid" 12 | } 13 | fmt.Printf("privateData is: %x (%s)\n", data, validStr) 14 | } 15 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/trevor403/get-priv-data-gfe 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/gen2brain/go-unarr v0.1.1 7 | github.com/kr/text v0.2.0 // indirect 8 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect 9 | github.com/pkg/errors v0.9.1 10 | golang.org/x/arch v0.0.0-20200511175325-f7c78586839d 11 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect 12 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b 13 | ) 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Trevor Rudolph 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | project_name: get-priv-data-gfe 2 | env: 3 | - GO111MODULE=on 4 | before: 5 | hooks: 6 | - go mod tidy 7 | builds: 8 | - id: linux-glibc 9 | main: ./cmd/get-priv-data-gfe 10 | env: 11 | - CGO_ENABLED=1 12 | goos: 13 | - linux 14 | goarch: 15 | - amd64 16 | - id: win 17 | main: ./cmd/get-priv-data-gfe 18 | env: 19 | - CC=/usr/bin/x86_64-w64-mingw32-gcc 20 | - CGO_ENABLED=1 21 | goos: 22 | - windows 23 | goarch: 24 | - amd64 25 | archives: 26 | - id: linux-glibc 27 | builds: 28 | - linux-glibc 29 | name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}" 30 | format: zip 31 | files: 32 | - LICENSE 33 | - README.md 34 | replacements: 35 | amd64: x86_64 36 | - id: win 37 | builds: 38 | - win 39 | name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}" 40 | format: zip 41 | files: 42 | - LICENSE 43 | - README.md 44 | replacements: 45 | amd64: x86_64 46 | checksum: 47 | name_template: 'checksums.txt' 48 | snapshot: 49 | name_template: "{{ .Tag }}-next" 50 | changelog: 51 | sort: asc 52 | filters: 53 | exclude: 54 | - '^docs:' 55 | - '^test:' 56 | -------------------------------------------------------------------------------- /cmd/get-priv-data-gfe/manifest.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | type GitHubRepoContents struct { 4 | Name string `json:"name"` 5 | Path string `json:"path"` 6 | Sha string `json:"sha"` 7 | Size int `json:"size"` 8 | URL string `json:"url"` 9 | HTMLURL string `json:"html_url"` 10 | GitURL string `json:"git_url"` 11 | DownloadURL string `json:"download_url"` 12 | Type string `json:"type"` 13 | } 14 | 15 | type WinGetPkg struct { 16 | ID string `yaml:"Id"` 17 | Publisher string `yaml:"Publisher"` 18 | Name string `yaml:"Name"` 19 | Author string `yaml:"Author"` 20 | Description string `yaml:"Description"` 21 | AppMoniker string `yaml:"AppMoniker"` 22 | Tags []string `yaml:"Tags"` 23 | Homepage string `yaml:"Homepage"` 24 | License string `yaml:"License"` 25 | LicenseURL string `yaml:"LicenseUrl"` 26 | MinOSVersion string `yaml:"MinOSVersion"` 27 | Version string `yaml:"Version"` 28 | Installers []struct { 29 | Architecture string `yaml:"Architecture"` 30 | InstallerURL string `yaml:"InstallerUrl"` 31 | InstallerSha256 string `yaml:"InstallerSha256"` 32 | Scope string `yaml:"Scope"` 33 | } `yaml:"Installers"` 34 | } 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # get-priv-data-gfe 2 | Retrieve NvFBC Private Data (UUID) from a GeForce Experience installation 3 | 4 | ## Method for finding the UUID constant 5 | 6 | The C code might look something like this 7 | ``` 8 | NvFBCCreateParams createParams; 9 | memset(&createParams, 0, sizeof(createParams)); 10 | 11 | ((&createParams.pPrivateData) + 0x0) = 0x00000000; 12 | ((&createParams.pPrivateData) + 0x4) = 0x00000000; 13 | ((&createParams.pPrivateData) + 0x8) = 0x00000000; 14 | ((&createParams.pPrivateData) + 0x10) = 0x00000000; 15 | 16 | createParams.dwPrivateDataSize = 16; 17 | ``` 18 | 19 | So we want to find an assignment (MOV dword ptr) of value 16 (10h) 20 | 21 | proceeded by 4x evenly spaced 4 byte assignments. 22 | 23 | ## Usage 24 | 25 | There are releases precompiled for you! 26 | You can find them in the [Releases tab](https://github.com/trevor403/get-priv-data-gfe/releases) 27 | 28 | You can get the executable via `go get` as well 29 | ``` 30 | go get github.com/trevor403/get-priv-data-gfe/cmd/... 31 | ``` 32 | 33 | ## Disclaimer 34 | Executing this program may put you in violation of NVIDIA's EULA 35 | 36 | I do not provide any legal guarantees around this software or it's usage. However it is my opinion that the Reverse Engineering effort that went into developing it is covered by the DMCA as it promotes interoperability. 37 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 2 | github.com/gen2brain/go-unarr v0.1.1 h1:wZl53oYzEN1PEIA/dPa/FjBq9rRqPmS/Gzul8BdKYK4= 3 | github.com/gen2brain/go-unarr v0.1.1/go.mod h1:P05CsEe8jVEXhxqXqp9mFKUKFV0BKpFmtgNWf8Mcoos= 4 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 5 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 6 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 7 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 8 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= 9 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= 10 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 11 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 12 | golang.org/x/arch v0.0.0-20200511175325-f7c78586839d h1:YvwchuJby5xEAPdBGmdAVSiVME50C+RJfJJwJJsGEV8= 13 | golang.org/x/arch v0.0.0-20200511175325-f7c78586839d/go.mod h1:flIaEI6LNU6xOCD5PaJvn9wGP0agmIOqjrtsKGRguv4= 14 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 15 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= 16 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 17 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= 18 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 19 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= 20 | -------------------------------------------------------------------------------- /cmd/get-priv-data-gfe/disasm.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "container/list" 6 | "debug/pe" 7 | "encoding/binary" 8 | "fmt" 9 | "hash/crc32" 10 | "io/ioutil" 11 | "log" 12 | "os" 13 | 14 | "github.com/pkg/errors" 15 | "golang.org/x/arch/x86/x86asm" 16 | ) 17 | 18 | const streamPath = "C:\\Program Files\\NVIDIA Corporation\\ShadowPlay\\NVSPCAPS\\_nvspcaps64.dll" 19 | 20 | const steamChecksum uint32 = 0x85ac72fb 21 | const nvidiaChecksum uint32 = 0x3806c005 22 | 23 | var validChecksums = []uint32{steamChecksum, nvidiaChecksum} 24 | 25 | func getTextSection(buf []byte) (*pe.Section, int, uint64) { 26 | reader := bytes.NewReader(buf) 27 | 28 | file, err := pe.NewFile(reader) 29 | if err != nil { 30 | log.Fatal(err) 31 | } 32 | 33 | var arch int = 64 34 | if file.FileHeader.Machine != pe.IMAGE_FILE_MACHINE_AMD64 { 35 | log.Fatalf("support for machine architecture %v not yet implemented", file.FileHeader.Machine) 36 | } 37 | 38 | //BaseOfData - Size - Offset 39 | 40 | var sectionOffset uint64 41 | opt, ok := file.OptionalHeader.(*pe.OptionalHeader64) 42 | if !ok { 43 | panic(fmt.Errorf("support for optional header type %T not yet implemented", file.OptionalHeader)) 44 | } 45 | 46 | sectionOffset += uint64(opt.ImageBase) 47 | sectionOffset += uint64(opt.BaseOfCode) 48 | 49 | var section *pe.Section 50 | for _, sec := range file.Sections { 51 | if sec.Name == ".text" { 52 | section = sec 53 | } 54 | } 55 | if section == nil { 56 | log.Fatal("could not find text section") 57 | } 58 | 59 | // fmt.Printf("HEADER\t opt.ImageBase = %x\n", uint64(opt.ImageBase)) 60 | // fmt.Printf("HEADER\t opt.BaseOfCode = %x\n", uint64(opt.BaseOfCode)) 61 | // fmt.Printf("HEADER\t section.Size = %x\n", uint64(section.Size)) 62 | // fmt.Printf("HEADER\t section.Offset = %x\n", uint64(section.Offset)) 63 | 64 | // fmt.Printf("%# v\n", pretty.Formatter(section)) 65 | 66 | return section, arch, sectionOffset 67 | } 68 | 69 | func checkSizeAssigment(inst *x86asm.Inst) bool { 70 | // fmt.Println(inst.Opcode) 71 | // fmt.Printf("%# v\n", pretty.Formatter(inst)) 72 | 73 | mem, typeOk := inst.Args[0].(x86asm.Mem) 74 | if !typeOk { 75 | return false 76 | } 77 | imm, typeOk := inst.Args[1].(x86asm.Imm) 78 | if !typeOk { 79 | return false 80 | } 81 | 82 | // the C code looks something like this 83 | // uint8 privData[16]; 84 | // ... 85 | // NvFBCCreateParams createParams; 86 | // memset(&createParams, 0, sizeof(createParams)); 87 | // ... 88 | // ((&createParams.pPrivateData) + 0x0) = 0xAABBCCDD; 89 | // ((&createParams.pPrivateData) + 0x4) = 0xEEFF0011; 90 | // ((&createParams.pPrivateData) + 0x8) = 0x22334455; 91 | // ((&createParams.pPrivateData) + 0x10) = 0x66778899; 92 | // createParams.dwPrivateDataSize = 16; 93 | 94 | // so we want to find an asignment assignment (MOV dword ptr) of value 16 (10h) 95 | // proceeded by 4x evenly spaces 4 byte assignments 96 | 97 | if inst.Op == x86asm.MOV && inst.Opcode>>16 == 0xc744 && imm == 0x10 && mem.Scale == 0x1 { 98 | // fmt.Println("got ya ========================") 99 | // fmt.Printf("%# v\n", pretty.Formatter(prev)) 100 | // fmt.Printf("%# v\n", pretty.Formatter(inst)) 101 | return true 102 | } 103 | 104 | return false 105 | } 106 | 107 | func extractFromInstList(instList *list.List) []byte { 108 | data := make([]byte, 16) 109 | 110 | partCount := 0 111 | var structAddr int64 = -1 112 | for e := instList.Front(); e != nil; e = e.Next() { 113 | inst := e.Value.(x86asm.Inst) 114 | 115 | mem, typeOk := inst.Args[0].(x86asm.Mem) 116 | if !typeOk { 117 | // log.Fatal("bad type mem") 118 | continue 119 | } 120 | imm, typeOk := inst.Args[1].(x86asm.Imm) 121 | if !typeOk { 122 | // log.Fatal("bad type imm") 123 | continue 124 | } 125 | 126 | if (inst.Op == x86asm.MOV && inst.Opcode>>16 == 0xc785) && (structAddr < 0 || mem.Disp-structAddr == 4) { 127 | // fmt.Printf("%# v\n", pretty.Formatter(inst)) 128 | binary.BigEndian.PutUint32(data[(partCount*4):], uint32(imm)) 129 | 130 | structAddr = mem.Disp 131 | partCount++ 132 | 133 | if partCount == 4 { 134 | break 135 | } 136 | } 137 | } 138 | 139 | return data 140 | } 141 | 142 | func _getPrivData(buf []byte) []byte { 143 | sec, arch, base := getTextSection(buf) 144 | 145 | _ = base 146 | 147 | raw, err := sec.Data() 148 | if err != nil { 149 | log.Fatal(errors.WithStack(err)) 150 | } 151 | data := raw 152 | fileSize := len(raw) 153 | memSize := int(sec.VirtualSize) 154 | if fileSize > memSize { 155 | // Ignore section alignment padding. 156 | data = raw[:memSize] 157 | } 158 | 159 | code := data 160 | start := uint64(0) 161 | end := uint64(len(code)) 162 | 163 | instList := list.New() 164 | for pc := start; pc < end; { 165 | addr := pc 166 | 167 | inst, err := x86asm.Decode(code[addr:], arch) 168 | if err != nil { 169 | pc++ 170 | continue 171 | } 172 | size := inst.Len 173 | 174 | instList.PushBack(inst) 175 | if instList.Len() > 20 { 176 | instList.Remove(instList.Front()) 177 | } 178 | 179 | if ok := checkSizeAssigment(&inst); ok { 180 | // fmt.Printf("%# v\n", pretty.Formatter(inst)) 181 | // fmt.Printf("%016x\t%s\n", base+pc, x86asm.IntelSyntax(inst, pc, nil)) 182 | // Iterate through list and print its contents. 183 | break 184 | } 185 | 186 | pc += uint64(size) 187 | } 188 | 189 | if data := extractFromInstList(instList); data != nil { 190 | return data 191 | } 192 | 193 | fmt.Println("this is so bad") 194 | return nil 195 | } 196 | 197 | func getDllPath() (string, error) { 198 | if _, err := os.Stat(streamPath); err == nil { 199 | return streamPath, nil 200 | } 201 | 202 | return getDownloadDllPath() 203 | } 204 | 205 | func getPrivData() []byte { 206 | dllPath, err := getDllPath() 207 | if err != nil { 208 | log.Fatal(err) 209 | } 210 | 211 | buf, err := ioutil.ReadFile(dllPath) 212 | if err != nil { 213 | log.Fatal(err) 214 | } 215 | 216 | data := _getPrivData(buf) 217 | return data 218 | } 219 | 220 | func checkValidData(priv []byte) bool { 221 | sum := crc32.ChecksumIEEE(priv) 222 | for _, validChecksum := range validChecksums { 223 | if sum == validChecksum { 224 | return true 225 | } 226 | } 227 | return false 228 | } 229 | -------------------------------------------------------------------------------- /cmd/get-priv-data-gfe/download.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "encoding/json" 7 | "fmt" 8 | "io/ioutil" 9 | "net/http" 10 | "path" 11 | "sort" 12 | 13 | "io" 14 | 15 | "github.com/gen2brain/go-unarr" 16 | "gopkg.in/yaml.v3" 17 | 18 | "log" 19 | "os" 20 | "path/filepath" 21 | ) 22 | 23 | const dllname = "_nvspcaps64.dll" 24 | 25 | var szfinder = []byte("!@InstallEnd@!") 26 | var szmagic = []byte("7z") 27 | 28 | func scanFile(f io.ReadSeeker, search []byte, count int) int64 { 29 | ix := 0 30 | r := bufio.NewReader(f) 31 | offset := int64(0) 32 | for nth := 0; nth < count; nth++ { 33 | for ix < len(search) { 34 | b, err := r.ReadByte() 35 | if err != nil { 36 | return -1 37 | } 38 | if search[ix] == b { 39 | ix++ 40 | } else { 41 | ix = 0 42 | } 43 | offset++ 44 | } 45 | ix = 0 46 | } 47 | f.Seek(offset, os.SEEK_SET) 48 | return offset 49 | } 50 | 51 | func seekTo7z(file io.ReadSeeker) error { 52 | offset := scanFile(file, szfinder, 2) 53 | 54 | buf := make([]byte, 2) 55 | var skip int64 56 | for skip = 0; skip < 0x10; skip += 0x2 { 57 | _, err := file.Read(buf) 58 | if err != nil { 59 | log.Fatal(err) 60 | } else if bytes.Equal(buf, szmagic) { 61 | break 62 | } 63 | } 64 | offset += skip 65 | file.Seek(offset, os.SEEK_SET) 66 | 67 | return nil 68 | } 69 | 70 | func createCacheDir() (string, error) { 71 | cacheDir, err := os.UserCacheDir() 72 | if err != nil { 73 | return "", fmt.Errorf("get os.UserCacheDir failed: %s", err) 74 | } 75 | 76 | execName := filepath.Base(os.Args[0]) 77 | cacheDir = filepath.Join(cacheDir, execName) 78 | err = os.MkdirAll(cacheDir, 0755) 79 | if err != nil { 80 | return "", fmt.Errorf("create dir in os.UserCacheDir failed: %s", err) 81 | } 82 | 83 | return cacheDir, nil 84 | } 85 | 86 | func pickVersion(repoContents []GitHubRepoContents) string { 87 | fileNames := make([]string, len(repoContents)) 88 | for i, contents := range repoContents { 89 | fileNames[i] = contents.Name 90 | } 91 | 92 | sort.Strings(fileNames) 93 | newest := fileNames[len(fileNames)-1] 94 | 95 | return newest 96 | } 97 | 98 | func pickDownloadURL(repoContents []GitHubRepoContents) string { 99 | return repoContents[0].DownloadURL 100 | } 101 | 102 | func getInstaller(gfePath string) error { 103 | const wingetURL = "https://api.github.com/repos/microsoft/winget-pkgs/contents/manifests/n/Nvidia/GeForceExperience" 104 | 105 | // get github folder contents 106 | resp, err := http.Get(wingetURL) 107 | if err != nil { 108 | return err 109 | } 110 | b, err := ioutil.ReadAll(resp.Body) 111 | if err != nil { 112 | return err 113 | } 114 | resp.Body.Close() 115 | 116 | // unmarshal json 117 | var repoContents []GitHubRepoContents 118 | err = json.Unmarshal(b, &repoContents) 119 | if err != nil { 120 | return err 121 | } else if len(repoContents) == 0 { 122 | return fmt.Errorf("too few Contents entries") 123 | } 124 | 125 | version := pickVersion(repoContents) 126 | 127 | // get github folder contents 128 | resp, err = http.Get(fmt.Sprintf("%s/%s", wingetURL, version)) 129 | if err != nil { 130 | return err 131 | } 132 | b, err = ioutil.ReadAll(resp.Body) 133 | if err != nil { 134 | return err 135 | } 136 | resp.Body.Close() 137 | 138 | // unmarshal json 139 | err = json.Unmarshal(b, &repoContents) 140 | if err != nil { 141 | return err 142 | } else if len(repoContents) == 0 { 143 | return fmt.Errorf("too few Contents entries") 144 | } 145 | 146 | downloadURL := pickDownloadURL(repoContents) 147 | 148 | // get winpkg yaml 149 | resp, err = http.Get(downloadURL) 150 | if err != nil { 151 | return err 152 | } 153 | b, err = ioutil.ReadAll(resp.Body) 154 | if err != nil { 155 | return err 156 | } 157 | resp.Body.Close() 158 | 159 | // unmarshal yaml 160 | var winget WinGetPkg 161 | err = yaml.Unmarshal(b, &winget) 162 | if err != nil { 163 | return err 164 | } else if len(winget.Installers) == 0 { 165 | return fmt.Errorf("too few Intaller entries") 166 | } 167 | 168 | // get GeForceNow exe 169 | resp, err = http.Get(winget.Installers[0].InstallerURL) 170 | if err != nil { 171 | return err 172 | } 173 | defer resp.Body.Close() 174 | 175 | out, err := os.Create(gfePath) 176 | if err != nil { 177 | return err 178 | } 179 | defer out.Close() 180 | 181 | _, err = io.Copy(out, resp.Body) 182 | if err != nil { 183 | return err 184 | } 185 | 186 | return nil 187 | } 188 | 189 | func getArchive(gfePath, szPath string) error { 190 | file, err := os.Open(gfePath) 191 | if err != nil { 192 | return err 193 | } 194 | defer file.Close() 195 | 196 | err = seekTo7z(file) 197 | if err != nil { 198 | return err 199 | } 200 | 201 | out, err := os.Create(szPath) 202 | if err != nil { 203 | return err 204 | } 205 | defer out.Close() 206 | 207 | _, err = io.Copy(out, file) 208 | if err != nil { 209 | return err 210 | } 211 | 212 | return nil 213 | } 214 | 215 | func getDll(szPath, dllPath string) error { 216 | a, err := unarr.NewArchive(szPath) 217 | if err != nil { 218 | return err 219 | } 220 | defer a.Close() 221 | 222 | list, err := a.List() 223 | if err != nil { 224 | return err 225 | } 226 | 227 | var archivePath string 228 | for _, archivePath = range list { 229 | if path.Base(archivePath) == dllname { 230 | break 231 | } 232 | } 233 | 234 | err = a.EntryFor(archivePath) 235 | if err != nil { 236 | return err 237 | } 238 | 239 | data, err := a.ReadAll() 240 | if err != nil { 241 | return err 242 | } 243 | 244 | err = ioutil.WriteFile(dllPath, data, 0666) 245 | if err != nil { 246 | return err 247 | } 248 | 249 | return nil 250 | } 251 | 252 | func getDownloadDllPath() (string, error) { 253 | cacheDir, err := createCacheDir() 254 | if err != nil { 255 | return "", err 256 | } 257 | 258 | gfePath := filepath.Join(cacheDir, "gfe.exe") 259 | if _, err := os.Stat(gfePath); err != nil && os.IsNotExist(err) { 260 | log.Printf("Downloading Geforce Experience installer to %s", gfePath) 261 | err = getInstaller(gfePath) 262 | if err != nil { 263 | return "", err 264 | } 265 | } 266 | 267 | szPath := filepath.Join(cacheDir, "gfe.7z") 268 | if _, err := os.Stat(szPath); err != nil && os.IsNotExist(err) { 269 | err = getArchive(gfePath, szPath) 270 | if err != nil { 271 | return "", err 272 | } 273 | } 274 | 275 | dllPath := filepath.Join(cacheDir, dllname) 276 | if _, err := os.Stat(dllPath); err != nil && os.IsNotExist(err) { 277 | log.Printf("Extracting dll from Geforce Experience to %s", dllPath) 278 | err = getDll(szPath, dllPath) 279 | if err != nil { 280 | return "", err 281 | } 282 | } 283 | 284 | return dllPath, nil 285 | } 286 | --------------------------------------------------------------------------------