Add ability to create a git repository from a BG3 install
Add library to parse version info from a windows PE executable There are issues with go-git, will change to a wrapper library
This commit is contained in:
parent
59f8bdcc54
commit
b5d007c865
19
cmd/lsconvert/fileversion.go
Normal file
19
cmd/lsconvert/fileversion.go
Normal file
@ -0,0 +1,19 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/jdferrell3/peinfo-go/peinfo"
|
||||
)
|
||||
|
||||
func getPEFileVersion(filepath string) string {
|
||||
f, err := peinfo.Initialize(filepath, false, "", false)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer f.OSFile.Close()
|
||||
defer f.PEFile.Close()
|
||||
v, _, err := f.GetVersionInfo()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return v["FileVersion"]
|
||||
}
|
@ -1,15 +1,21 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/go-git/go-git/v5"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
|
||||
"git.narnian.us/lordwelch/lsgo/gog"
|
||||
ie "git.narnian.us/lordwelch/lsgo/internal/exec"
|
||||
"golang.org/x/sys/windows/registry"
|
||||
)
|
||||
@ -19,26 +25,202 @@ var (
|
||||
initFlags struct {
|
||||
BG3Path string
|
||||
divinePath string
|
||||
Repo string
|
||||
}
|
||||
)
|
||||
|
||||
func initRepository(arguments []string) {
|
||||
const ignore = `# ignores all binary files
|
||||
*.wem
|
||||
*.dds
|
||||
*.png
|
||||
*.jpg
|
||||
*.gr2
|
||||
*.bin
|
||||
*.patch
|
||||
*.bshd
|
||||
*.tga
|
||||
*.ttf
|
||||
*.fnt
|
||||
Assets_pak/Public/Shared/Assets/Characters/_Anims/Humans/_Female/HUM_F_Rig/HUM_F_Rig_DFLT_SPL_Somatic_MimeTalk_01
|
||||
Assets_pak/Public/Shared/Assets/Characters/_Models/_Creatures/Beholder/BEH_Spectator_A_GM_DDS
|
||||
Assets_pak/Public/Shared/Assets/Consumables/CONS_GEN_Food_Turkey_ABC/CONS_GEN_Food_Turkey_ABC.ma
|
||||
Assets_pak/Public/Shared/Assets/Decoration/Harbour/DEC_Harbour_Shell_ABC/DEC_Harbour_Shell_Venus_A
|
||||
*.bnk
|
||||
*.pak
|
||||
*.data
|
||||
*.osi
|
||||
*.cur
|
||||
*.gtp
|
||||
*.gts
|
||||
*.ffxanim
|
||||
*.ffxactor
|
||||
*.ffxbones
|
||||
*.bk2
|
||||
`
|
||||
|
||||
type BG3Repo struct {
|
||||
git *git.Repository
|
||||
gitPath string
|
||||
gog gog.GOGalaxy
|
||||
BG3Path string
|
||||
divinePath string
|
||||
Empty bool
|
||||
}
|
||||
|
||||
func openRepository(path string) (BG3Repo, error) {
|
||||
var (
|
||||
BG3Path, divinePath string
|
||||
repo *git.Repository
|
||||
err error
|
||||
b BG3Repo
|
||||
err error
|
||||
)
|
||||
BG3Path, divinePath = preRequisites()
|
||||
if BG3Path == "" {
|
||||
b.gitPath = path
|
||||
b.BG3Path, b.divinePath = preRequisites()
|
||||
if b.BG3Path == "" {
|
||||
panic("Could not locate Baldur's Gate 3")
|
||||
}
|
||||
fmt.Println(divinePath)
|
||||
panic("Baldur's Gate 3 located " + BG3Path)
|
||||
repo, err = git.PlainInit(".", false)
|
||||
|
||||
b.git, err = git.PlainOpen(b.gitPath)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
b.Empty = true
|
||||
}
|
||||
repo.Fetch(nil)
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (bgr *BG3Repo) Init() error {
|
||||
var err error
|
||||
if bgr.Empty || bgr.git == nil {
|
||||
bgr.git, err = git.PlainInit(bgr.gitPath, false)
|
||||
if err == git.ErrRepositoryAlreadyExists {
|
||||
bgr.git, err = git.PlainOpen(bgr.gitPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if citer, err := bgr.git.CommitObjects(); err == nil {
|
||||
if _, err := citer.Next(); err == nil {
|
||||
// Repo contains commits, nothing to do
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return bgr.AddNewVersion()
|
||||
}
|
||||
|
||||
func (bgr *BG3Repo) RetrieveGOGalaxy() error {
|
||||
var err error
|
||||
bgr.gog, err = gog.RetrieveGOGInfo("1456460669")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bgr *BG3Repo) AddNewVersion() error {
|
||||
var (
|
||||
version string
|
||||
changelog gog.Change
|
||||
err error
|
||||
)
|
||||
version = getPEFileVersion(filepath.Join(bgr.BG3Path, "bin/bg3.exe"))
|
||||
err = bgr.RetrieveGOGalaxy()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
changelog, err = gog.ParseChangelog(bgr.gog.Changelog, bgr.gog.Title)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
versionChanges := changelog.FindChange(version)
|
||||
if versionChanges == nil {
|
||||
return fmt.Errorf("no version information found for %q", version)
|
||||
}
|
||||
|
||||
// TODO: Replace with custom errors
|
||||
t, err := bgr.git.Worktree()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var stat git.Status
|
||||
stat, err = t.Status()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !stat.IsClean() {
|
||||
return errors.New("git working directory must be clean before adding a new version, please save your work:\n" + stat.String())
|
||||
}
|
||||
if _, err := t.Filesystem.Stat(".gitignore"); err != nil {
|
||||
gitignore, err := t.Filesystem.Create(".gitignore")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = gitignore.Write([]byte(ignore))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gitignore.Close()
|
||||
}
|
||||
|
||||
t.Add(".gitignore")
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
mut := &sync.Mutex{}
|
||||
errs := new(multierror.Error)
|
||||
paths := []string{}
|
||||
|
||||
err = filepath.Walk(filepath.Join(bgr.BG3Path, "Data"), func(path string, info os.FileInfo, err error) error {
|
||||
rpath := strings.TrimPrefix(path, filepath.Join(bgr.BG3Path, "Data"))
|
||||
fmt.Println("bg3 ", filepath.Join(bgr.BG3Path, "Data"))
|
||||
fmt.Println("path ", path)
|
||||
fmt.Println("rpath", rpath)
|
||||
repoPath := filepath.Join(filepath.Dir(rpath), strings.TrimSuffix(filepath.Base(rpath), filepath.Ext(rpath))+strings.ReplaceAll(filepath.Ext(rpath), ".", "_"))
|
||||
fmt.Println("repopath", repoPath)
|
||||
if filepath.Ext(info.Name()) != ".pak" || info.IsDir() || strings.Contains(info.Name(), "Textures") {
|
||||
return nil
|
||||
}
|
||||
|
||||
err = t.Filesystem.MkdirAll(repoPath, 0o660)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
paths = append(paths, repoPath)
|
||||
divine := exec.Command(bgr.divinePath, "-g", "bg3", "-s", path, "-d", filepath.Join(bgr.gitPath, repoPath), "-a", "extract-package")
|
||||
var buf bytes.Buffer
|
||||
divine.Stderr = &buf
|
||||
divine.Stdout = &buf
|
||||
err = divine.Run()
|
||||
if err != nil {
|
||||
return fmt.Errorf("an error occurred running divine.exe: %s", buf.String())
|
||||
}
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
if err := convert("-w", "-r", filepath.Join(bgr.gitPath, repoPath)); err != nil {
|
||||
mut.Lock()
|
||||
errs = multierror.Append(errs, err)
|
||||
mut.Unlock()
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
if errs.ErrorOrNil() != nil {
|
||||
return nil
|
||||
}
|
||||
for _, v := range paths {
|
||||
_, err = t.Add(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
_, err = t.Commit(strings.Replace(versionChanges.String(), versionChanges.Title, versionChanges.Title+"\n", 1), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func preRequisites() (BG3Path, divinePath string) {
|
||||
|
@ -1,15 +1,43 @@
|
||||
package main
|
||||
|
||||
import "flag"
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
switch flag.Arg(0) {
|
||||
// flag.Parse()
|
||||
arg := ""
|
||||
if len(os.Args) > 1 {
|
||||
arg = os.Args[1]
|
||||
}
|
||||
switch arg {
|
||||
case "convert":
|
||||
convert(flag.Args()[1:])
|
||||
err := convert(os.Args[1:]...)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
case "init":
|
||||
initRepository(flag.Args()[1:])
|
||||
p := "."
|
||||
if len(os.Args) > 2 {
|
||||
p = os.Args[2]
|
||||
}
|
||||
repo, err := openRepository(p)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
err = repo.Init()
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
default:
|
||||
convert(flag.Args())
|
||||
err := convert(os.Args[1:]...)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
4
go.mod
4
go.mod
@ -4,10 +4,14 @@ go 1.15
|
||||
|
||||
replace github.com/pierrec/lz4/v4 v4.1.3 => ./third_party/lz4
|
||||
|
||||
replace github.com/jdferrell3/peinfo-go v0.0.0-20191128021257-cff0d8506fb1 => ./third_party/peinfo-go
|
||||
|
||||
require (
|
||||
github.com/go-git/go-git/v5 v5.2.0
|
||||
github.com/go-kit/kit v0.10.0
|
||||
github.com/google/uuid v1.1.4
|
||||
github.com/hashicorp/go-multierror v1.1.0
|
||||
github.com/jdferrell3/peinfo-go v0.0.0-20191128021257-cff0d8506fb1
|
||||
github.com/kr/pretty v0.2.1
|
||||
github.com/pierrec/lz4/v4 v4.1.3
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a
|
||||
|
8
go.sum
8
go.sum
@ -121,11 +121,14 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
|
||||
github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
|
||||
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||
github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI=
|
||||
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
|
||||
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
|
||||
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
||||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
||||
@ -300,6 +303,8 @@ golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 h1:xMPOj6Pz6UipU1wXLkrtqpHbR0AVFnyPEQq/wRWz9lM=
|
||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
@ -354,10 +359,13 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210108172913-0df2131ae363 h1:wHn06sgWHMO1VsQ8F+KzDJx/JzqfsNLnc+oEi07qD7s=
|
||||
golang.org/x/sys v0.0.0-20210108172913-0df2131ae363/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221 h1:/ZHdbVpdR/jk3g30/d4yUL0JU9kksj8+F/bnQUVLGDM=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
|
15
third_party/peinfo-go/.gitignore
vendored
Normal file
15
third_party/peinfo-go/.gitignore
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, build with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Mac files
|
||||
.DS_Store
|
49
third_party/peinfo-go/LICENSE
vendored
Normal file
49
third_party/peinfo-go/LICENSE
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 John Ferrell
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
peinfo/revoked.go
|
||||
Copyright (c) 2014 CloudFlare Inc.
|
||||
Copyright (c) 2019 John Ferrell
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
||||
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
62
third_party/peinfo-go/README.md
vendored
Normal file
62
third_party/peinfo-go/README.md
vendored
Normal file
@ -0,0 +1,62 @@
|
||||
# peinfo-go
|
||||
|
||||
This is a PE (Portable Executable) parser written in GoLang. I wanted to learn more about the PE format, specifically how the certificates were stored. What better way is there than to write some code?
|
||||
|
||||
_This is a work in progress and will continue to change._
|
||||
|
||||
This leverages the `debug/pe` package for parsing of the common headers/sections.
|
||||
|
||||
Current state:
|
||||
- Displays some PE details
|
||||
- Validates certificate, verifies certificate chain, checks against CRL
|
||||
- Parses Version Info struct
|
||||
- Displays imports
|
||||
|
||||
TODO:
|
||||
- ~~Actually Parse Version Info struct (currently displayed as raw binary)~~
|
||||
- Re-write function for finding Version Info (currently written so I could better understand the structure)
|
||||
- ~~Custom certificate stores~~
|
||||
|
||||
## Example
|
||||
```
|
||||
[user:~/peinfo-go\ > go run cmd/main.go -certdir ~/RootCerts -versioninfo ~/Downloads/PsExec.exe
|
||||
type: pe32
|
||||
TimeDateStamp: 2016-06-28 18:43:09 +0000 UTC
|
||||
Characteristics: [Executable 32bit]
|
||||
Subsystem: IMAGE_SUBSYSTEM_WINDOWS_CUI
|
||||
|
||||
Cert:
|
||||
subject: CN=Microsoft Corporation,OU=MOPR,O=Microsoft Corporation,L=Redmond,ST=Washington,C=US
|
||||
issuer: CN=Microsoft Code Signing PCA,O=Microsoft Corporation,L=Redmond,ST=Washington,C=US
|
||||
not before: 2015-06-04 17:42:45 +0000 UTC
|
||||
not after: 2016-09-04 17:42:45 +0000 UTC
|
||||
CRL: [http://crl.microsoft.com/pki/crl/products/MicCodSigPCA_08-31-2010.crl]
|
||||
verified: true (chain expired: true)
|
||||
|
||||
Version Info:
|
||||
BuildDate :
|
||||
BuildVersion :
|
||||
Comments :
|
||||
CompanyName : Sysinternals - www.sysinternals.com
|
||||
Copyright :
|
||||
FileDescription : Execute processes remotely
|
||||
FileVersion : 2.2
|
||||
InternalName : PsExec
|
||||
LegalCopyright : Copyright (C) 2001-2016 Mark Russinovich
|
||||
LegalTrademarks :
|
||||
OriginalFilename : psexec.c
|
||||
PrivateBuild :
|
||||
ProductName : Sysinternals PsExec
|
||||
ProductVersion : 2.2
|
||||
SpecialBuild :
|
||||
langCharSet : 040904b0h$
|
||||
```
|
||||
|
||||
## References
|
||||
- https://golang.org/pkg/debug/pe/
|
||||
- http://www.pelib.com/resources/luevel.txt
|
||||
- https://github.com/exiftool/exiftool/blob/master/lib/Image/ExifTool/EXE.pm
|
||||
- https://github.com/deptofdefense/SalSA/blob/master/pe.py
|
||||
- https://docs.microsoft.com/en-us/windows/desktop/debug/pe-format#resource-directory-entries
|
||||
- https://github.com/quarkslab/dreamboot/blob/31e155b06802dce94367c38ea93316f7cb86cb15/QuarksUBootkit/PeCoffLib.c
|
||||
- https://docs.microsoft.com/en-us/windows/desktop/Debug/pe-format#the-attribute-certificate-table-image-only
|
7
third_party/peinfo-go/go.mod
vendored
Normal file
7
third_party/peinfo-go/go.mod
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
module github.com/jdferrell3/peinfo-go
|
||||
|
||||
require (
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
|
||||
)
|
||||
|
||||
go 1.13
|
10
third_party/peinfo-go/go.sum
vendored
Normal file
10
third_party/peinfo-go/go.sum
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191128160524-b544559bb6d1/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
17
third_party/peinfo-go/peinfo/consts.go
vendored
Normal file
17
third_party/peinfo-go/peinfo/consts.go
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
package peinfo
|
||||
|
||||
var FileOS = map[uint32]string{
|
||||
0x00001: "Win16",
|
||||
0x00002: "PM-16",
|
||||
0x00003: "PM-32",
|
||||
0x00004: "Win32",
|
||||
0x10000: "DOS",
|
||||
0x20000: "OS/2 16-bit",
|
||||
0x30000: "OS/2 32-bit",
|
||||
0x40000: "Windows NT",
|
||||
0x10001: "Windows 16-bit",
|
||||
0x10004: "Windows 32-bit",
|
||||
0x20002: "OS/2 16-bit PM-16",
|
||||
0x30003: "OS/2 32-bit PM-32",
|
||||
0x40004: "Windows NT 32-bit",
|
||||
}
|
31
third_party/peinfo-go/peinfo/initialize.go
vendored
Normal file
31
third_party/peinfo-go/peinfo/initialize.go
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
package peinfo
|
||||
|
||||
import (
|
||||
"debug/pe"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// Initialize returns the config for execution
|
||||
func Initialize(filePath string, verbose bool, rootCertDir string, extractCert bool) (ConfigT, error) {
|
||||
fh, err := os.Open(filePath)
|
||||
if nil != err {
|
||||
return ConfigT{}, err
|
||||
}
|
||||
|
||||
tempPE, err := pe.NewFile(fh)
|
||||
if nil != err {
|
||||
return ConfigT{}, err
|
||||
}
|
||||
|
||||
file := ConfigT{
|
||||
FileName: filepath.Base(filePath),
|
||||
OSFile: fh,
|
||||
PEFile: tempPE,
|
||||
Verbose: verbose,
|
||||
ExtractCert: extractCert,
|
||||
RootCertDir: rootCertDir,
|
||||
}
|
||||
|
||||
return file, nil
|
||||
}
|
124
third_party/peinfo-go/peinfo/peinfo.go
vendored
Normal file
124
third_party/peinfo-go/peinfo/peinfo.go
vendored
Normal file
@ -0,0 +1,124 @@
|
||||
package peinfo
|
||||
|
||||
import (
|
||||
"debug/pe"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
// http://www.pelib.com/resources/luevel.txt
|
||||
// https://github.com/exiftool/exiftool/blob/master/lib/Image/ExifTool/EXE.pm
|
||||
// https://github.com/deptofdefense/SalSA/blob/master/pe.py
|
||||
// https://docs.microsoft.com/en-us/windows/desktop/debug/pe-format#resource-directory-entries
|
||||
// https://github.com/quarkslab/dreamboot/blob/31e155b06802dce94367c38ea93316f7cb86cb15/QuarksUBootkit/PeCoffLib.c
|
||||
// https://docs.microsoft.com/en-us/windows/desktop/Debug/pe-format#the-attribute-certificate-table-image-only
|
||||
// https://docs.microsoft.com/en-us/windows/desktop/menurc/vs-versioninfo
|
||||
|
||||
var (
|
||||
sizeofOptionalHeader32 = uint16(binary.Size(pe.OptionalHeader32{}))
|
||||
sizeofOptionalHeader64 = uint16(binary.Size(pe.OptionalHeader64{}))
|
||||
)
|
||||
|
||||
type ImageDirectoryT struct {
|
||||
Type int
|
||||
VirtualAddress uint32
|
||||
Size uint32
|
||||
ImageBase uint64
|
||||
}
|
||||
|
||||
func (cfg *ConfigT) HeaderMagic() uint16 {
|
||||
pe64 := cfg.PEFile.Machine == pe.IMAGE_FILE_MACHINE_AMD64
|
||||
|
||||
if pe64 {
|
||||
return cfg.PEFile.OptionalHeader.(*pe.OptionalHeader64).Magic
|
||||
}
|
||||
|
||||
return cfg.PEFile.OptionalHeader.(*pe.OptionalHeader32).Magic
|
||||
}
|
||||
|
||||
func (cfg *ConfigT) GetPEType() string {
|
||||
t := "pe32"
|
||||
if cfg.PEFile.Machine == pe.IMAGE_FILE_MACHINE_AMD64 {
|
||||
t = "pe32+"
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
func (cfg *ConfigT) GetImageSubSystem() string {
|
||||
pe64 := cfg.PEFile.Machine == pe.IMAGE_FILE_MACHINE_AMD64
|
||||
|
||||
subsystem := map[uint16]string{
|
||||
0: "IMAGE_SUBSYSTEM_UNKNOWN",
|
||||
1: "IMAGE_SUBSYSTEM_NATIVE",
|
||||
2: "IMAGE_SUBSYSTEM_WINDOWS_GUI",
|
||||
3: "IMAGE_SUBSYSTEM_WINDOWS_CUI",
|
||||
4: "IMAGE_SUBSYSTEM_OS2_CUI",
|
||||
5: "IMAGE_SUBSYSTEM_POSIX_CUI",
|
||||
9: "IMAGE_SUBSYSTEM_WINDOWS_CE_GUI",
|
||||
10: "IMAGE_SUBSYSTEM_EFI_APPLICATION",
|
||||
11: "IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER",
|
||||
12: "IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER",
|
||||
13: "IMAGE_SUBSYSTEM_EFI_ROM",
|
||||
14: "IMAGE_SUBSYSTEM_XBOX",
|
||||
15: "IMAGE_SUBSYSTEM_WINDOWS_BOOT_APPLICATION",
|
||||
}
|
||||
|
||||
if pe64 {
|
||||
return subsystem[cfg.PEFile.OptionalHeader.(*pe.OptionalHeader64).Subsystem]
|
||||
}
|
||||
|
||||
return subsystem[cfg.PEFile.OptionalHeader.(*pe.OptionalHeader32).Subsystem]
|
||||
}
|
||||
|
||||
// GetCharacteristics returns a list of PE characteristics
|
||||
func (cfg *ConfigT) GetCharacteristics() []string {
|
||||
characteristics := []string{}
|
||||
|
||||
if (cfg.PEFile.FileHeader.Characteristics & 0x0002) > 1 {
|
||||
characteristics = append(characteristics, "Executable")
|
||||
}
|
||||
|
||||
if (cfg.PEFile.FileHeader.Characteristics & 0x0100) > 1 {
|
||||
characteristics = append(characteristics, "32bit")
|
||||
}
|
||||
|
||||
if (cfg.PEFile.FileHeader.Characteristics & 0x2000) > 1 {
|
||||
characteristics = append(characteristics, "DLL")
|
||||
}
|
||||
|
||||
return characteristics
|
||||
}
|
||||
|
||||
// GetTimeDateStamp returns the date-time stamp in the PE's header
|
||||
func (cfg *ConfigT) GetTimeDateStamp() string {
|
||||
tm := time.Unix(int64(cfg.PEFile.FileHeader.TimeDateStamp), 0)
|
||||
return fmt.Sprintf("%s", tm.UTC())
|
||||
}
|
||||
|
||||
// FindDataDirectory
|
||||
func (cfg *ConfigT) FindDataDirectory(imageDirectoryEntryType int) (idd ImageDirectoryT) {
|
||||
pe64 := cfg.PEFile.Machine == pe.IMAGE_FILE_MACHINE_AMD64
|
||||
|
||||
var dd pe.DataDirectory
|
||||
if pe64 {
|
||||
dd = cfg.PEFile.OptionalHeader.(*pe.OptionalHeader64).DataDirectory[imageDirectoryEntryType]
|
||||
idd.ImageBase = cfg.PEFile.OptionalHeader.(*pe.OptionalHeader64).ImageBase
|
||||
} else {
|
||||
dd = cfg.PEFile.OptionalHeader.(*pe.OptionalHeader32).DataDirectory[imageDirectoryEntryType]
|
||||
idd.ImageBase = uint64(cfg.PEFile.OptionalHeader.(*pe.OptionalHeader32).ImageBase)
|
||||
}
|
||||
|
||||
idd.VirtualAddress = dd.VirtualAddress
|
||||
idd.Size = dd.Size
|
||||
idd.Type = imageDirectoryEntryType
|
||||
|
||||
return idd
|
||||
}
|
||||
|
||||
// Tell is a wrapper for Seek()
|
||||
func (cfg *ConfigT) Tell() int64 {
|
||||
pos, _ := cfg.OSFile.Seek(0, os.SEEK_CUR)
|
||||
return pos
|
||||
}
|
90
third_party/peinfo-go/peinfo/structs.go
vendored
Normal file
90
third_party/peinfo-go/peinfo/structs.go
vendored
Normal file
@ -0,0 +1,90 @@
|
||||
package peinfo
|
||||
|
||||
import (
|
||||
"debug/pe"
|
||||
"os"
|
||||
)
|
||||
|
||||
type ConfigT struct {
|
||||
FileName string
|
||||
OSFile *os.File
|
||||
PEFile *pe.File
|
||||
ExtractCert bool
|
||||
Verbose bool
|
||||
RootCertDir string
|
||||
}
|
||||
|
||||
type ResourceDirectoryD struct {
|
||||
Characteristics uint32
|
||||
TimeDateStamp uint32
|
||||
MajorVersion uint16
|
||||
MinorVersion uint16
|
||||
NumberOfNamedEntries uint16
|
||||
NumberOfIdEntries uint16
|
||||
}
|
||||
|
||||
type CertDetails struct {
|
||||
Length uint32
|
||||
Revision uint16
|
||||
CertificateType uint16
|
||||
DER []byte
|
||||
}
|
||||
|
||||
// typedef struct _IMAGE_RESOURCE_DIRECTORY_ENTRY {
|
||||
// union {
|
||||
// struct {
|
||||
// DWORD NameOffset:31;
|
||||
// DWORD NameIsString:1;
|
||||
// };
|
||||
// DWORD Name;
|
||||
// WORD Id;
|
||||
// };
|
||||
// union {
|
||||
// DWORD OffsetToData;
|
||||
// struct {
|
||||
// DWORD OffsetToDirectory:31;
|
||||
// DWORD DataIsDirectory:1;
|
||||
// };
|
||||
// };
|
||||
// } IMAGE_RESOURCE_DIRECTORY_ENTRY, *PIMAGE_RESOURCE_DIRECTORY_ENTRY;
|
||||
|
||||
type ResourceDirectoryEntry struct {
|
||||
Name uint32
|
||||
OffsetToData uint32
|
||||
}
|
||||
|
||||
type ResourceDirectoryEntryNamed struct {
|
||||
Name uint32
|
||||
OffsetToData uint32
|
||||
}
|
||||
|
||||
/* Resource Directory Entry */
|
||||
// type ResourceDirectoryEntryT struct {
|
||||
// ResourceDirectoryEntry ResourceDirectoryEntry
|
||||
// FileOffset uint32
|
||||
// Size uint32
|
||||
// DataIsDirectory bool
|
||||
// }
|
||||
|
||||
type _IMAGE_RESOURCE_DATA_ENTRY struct {
|
||||
OffsetToData uint32
|
||||
Size uint32
|
||||
CodePage uint32
|
||||
Reserved uint32
|
||||
}
|
||||
|
||||
type VS_FIXEDFILEINFO struct {
|
||||
DwSignature uint32
|
||||
DwStrucVersion uint32
|
||||
DwFileVersionMS uint32
|
||||
DwFileVersionLS uint32
|
||||
DwProductVersionMS uint32
|
||||
DwProductVersionLS uint32
|
||||
DwFileFlagsMask uint32
|
||||
DwFileFlags uint32
|
||||
DwFileOS uint32
|
||||
DwFileType uint32
|
||||
DwFileSubtype uint32
|
||||
DwFileDateMS uint32
|
||||
DwFileDateLS uint32
|
||||
}
|
267
third_party/peinfo-go/peinfo/verinfo.go
vendored
Normal file
267
third_party/peinfo-go/peinfo/verinfo.go
vendored
Normal file
@ -0,0 +1,267 @@
|
||||
package peinfo
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"debug/pe"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
const (
|
||||
RT_VERSION = 16
|
||||
)
|
||||
|
||||
func (cfg *ConfigT) FindVerInfoOffset(fileOffset int64, sectionOffset uint32, sectionVirtualAddress uint32) (verInfoOffset int64, len uint32, err error) {
|
||||
pos, _ := cfg.OSFile.Seek(fileOffset, os.SEEK_SET)
|
||||
if pos != fileOffset {
|
||||
return 0, 0, fmt.Errorf("did not seek to offset")
|
||||
}
|
||||
type VerInfoDetailsT struct {
|
||||
Off uint32
|
||||
Len uint32
|
||||
D1 uint32
|
||||
D2 uint32
|
||||
}
|
||||
var peoff VerInfoDetailsT
|
||||
err = binary.Read(cfg.OSFile, binary.LittleEndian, &peoff)
|
||||
if nil != err {
|
||||
return verInfoOffset, len, err
|
||||
}
|
||||
|
||||
// $filePos = $off + $$section{Base} - $$section{VirtualAddress};
|
||||
verInfoOffset = int64(peoff.Off + sectionOffset - sectionVirtualAddress)
|
||||
return verInfoOffset, peoff.Len, nil
|
||||
}
|
||||
|
||||
func (cfg *ConfigT) GetVersionInfo() (vi map[string]string, keys []string, err error) {
|
||||
vi = map[string]string{
|
||||
"BuildDate": "",
|
||||
"BuildVersion": "",
|
||||
"Comments": "",
|
||||
"CompanyName": "",
|
||||
"Copyright": "",
|
||||
"FileDescription": "",
|
||||
"FileVersion": "",
|
||||
"InternalName": "",
|
||||
"LegalCopyright": "",
|
||||
"LegalTrademarks": "",
|
||||
"OriginalFilename": "",
|
||||
"PrivateBuild": "",
|
||||
"ProductName": "",
|
||||
"ProductVersion": "",
|
||||
"SpecialBuild": "",
|
||||
"langCharSet": "",
|
||||
// "varFileInfo": "",
|
||||
}
|
||||
keys = []string{
|
||||
"BuildDate",
|
||||
"BuildVersion",
|
||||
"Comments",
|
||||
"CompanyName",
|
||||
"Copyright",
|
||||
"FileDescription",
|
||||
"FileVersion",
|
||||
"InternalName",
|
||||
"LegalCopyright",
|
||||
"LegalTrademarks",
|
||||
"OriginalFilename",
|
||||
"PrivateBuild",
|
||||
"ProductName",
|
||||
"ProductVersion",
|
||||
"SpecialBuild",
|
||||
"langCharSet",
|
||||
// "varFileInfo"
|
||||
}
|
||||
|
||||
section := cfg.PEFile.Section(".rsrc")
|
||||
if section == nil {
|
||||
return vi, keys, fmt.Errorf("resource section not found")
|
||||
}
|
||||
// fmt.Printf("%+v\n", section)
|
||||
|
||||
// Resource
|
||||
_, err = cfg.OSFile.Seek(int64(0), os.SEEK_SET)
|
||||
if nil != err {
|
||||
return vi, keys, err
|
||||
}
|
||||
|
||||
idd := cfg.FindDataDirectory(pe.IMAGE_DIRECTORY_ENTRY_RESOURCE)
|
||||
idd.VirtualAddress -= (section.VirtualAddress - section.Offset)
|
||||
if cfg.Verbose {
|
||||
fmt.Printf("IMAGE_DIRECTORY_ENTRY_RESOURCE virtual address: %d\n", idd.VirtualAddress)
|
||||
fmt.Printf("IMAGE_DIRECTORY_ENTRY_RESOURCE size: %d\n", idd.Size)
|
||||
fmt.Printf("IMAGE_DIRECTORY_ENTRY_RESOURCE image base: %d\n", idd.ImageBase)
|
||||
}
|
||||
|
||||
pos, err := cfg.OSFile.Seek(int64(idd.VirtualAddress), os.SEEK_SET)
|
||||
if nil != err {
|
||||
return vi, keys, err
|
||||
}
|
||||
if pos != int64(idd.VirtualAddress) {
|
||||
fmt.Errorf("did not seek to VirtualAddress")
|
||||
}
|
||||
|
||||
var table ResourceDirectoryD
|
||||
err = binary.Read(cfg.OSFile, binary.LittleEndian, &table)
|
||||
if nil != err {
|
||||
return vi, keys, err
|
||||
}
|
||||
// fmt.Printf("table %+v\n", table)
|
||||
|
||||
x := 0
|
||||
for x < int(table.NumberOfNamedEntries+table.NumberOfIdEntries) {
|
||||
var entry ResourceDirectoryEntry
|
||||
err = binary.Read(cfg.OSFile, binary.LittleEndian, &entry)
|
||||
if nil != err {
|
||||
return vi, keys, err
|
||||
}
|
||||
|
||||
if entry.Name == RT_VERSION {
|
||||
// Directory
|
||||
if (entry.OffsetToData&0x80000000)>>31 == 1 {
|
||||
new := entry.OffsetToData&0x7fffffff + idd.VirtualAddress
|
||||
cfg.OSFile.Seek(int64(new), os.SEEK_SET)
|
||||
|
||||
var innerDir ResourceDirectoryD
|
||||
err = binary.Read(cfg.OSFile, binary.LittleEndian, &innerDir)
|
||||
if nil != err {
|
||||
return vi, keys, err
|
||||
}
|
||||
// pos := f.Tell()
|
||||
// fmt.Printf("level 1 innerDir %+v (file offset=%d)\n", innerDir, pos)
|
||||
|
||||
y := 0
|
||||
for y < int(innerDir.NumberOfNamedEntries+innerDir.NumberOfIdEntries) {
|
||||
var entry ResourceDirectoryEntry
|
||||
err = binary.Read(cfg.OSFile, binary.LittleEndian, &entry)
|
||||
if nil != err {
|
||||
return vi, keys, err
|
||||
}
|
||||
// pos := f.Tell()
|
||||
// fmt.Printf("item %d - level 2 buff %s (file offset=%d)\n", y, entry, pos)
|
||||
|
||||
if (entry.OffsetToData&0x80000000)>>31 == 1 {
|
||||
new := entry.OffsetToData&0x7fffffff + idd.VirtualAddress
|
||||
// fmt.Printf("level 2 DirStart 0x%x (%d)\n", new, new)
|
||||
cfg.OSFile.Seek(int64(new), os.SEEK_SET)
|
||||
}
|
||||
|
||||
var innerDir ResourceDirectoryD
|
||||
err = binary.Read(cfg.OSFile, binary.LittleEndian, &innerDir)
|
||||
if nil != err {
|
||||
return vi, keys, err
|
||||
}
|
||||
// pos = f.Tell()
|
||||
// fmt.Printf("level 3 innerDir %+v (file offset=%d)\n", innerDir, pos)
|
||||
|
||||
z := 0
|
||||
for z < int(innerDir.NumberOfNamedEntries+innerDir.NumberOfIdEntries) {
|
||||
var entry ResourceDirectoryEntry
|
||||
err = binary.Read(cfg.OSFile, binary.LittleEndian, &entry)
|
||||
if nil != err {
|
||||
return vi, keys, err
|
||||
}
|
||||
// pos := f.Tell()
|
||||
// fmt.Printf("item %d - level 3 buff %s (file offset=%d)\n", y, entry, pos)
|
||||
// fmt.Printf("ver: 0x%x\n", entry.OffsetToData+idd.VirtualAddress)
|
||||
|
||||
// find offset of VS_VERSION_INFO
|
||||
off := int64(entry.OffsetToData + idd.VirtualAddress)
|
||||
viPos, viLen, err := cfg.FindVerInfoOffset(off, section.SectionHeader.Offset, section.SectionHeader.VirtualAddress)
|
||||
if nil != err {
|
||||
return vi, keys, err
|
||||
}
|
||||
// fmt.Printf("VerInfo Struct filePos: 0x%x (%d)\n", viPos, viPos)
|
||||
|
||||
cfg.OSFile.Seek(viPos, os.SEEK_SET)
|
||||
b := make([]byte, viLen)
|
||||
err = binary.Read(cfg.OSFile, binary.LittleEndian, &b)
|
||||
if nil != err {
|
||||
return vi, keys, err
|
||||
}
|
||||
// fmt.Printf("%s\n", b)
|
||||
|
||||
if cfg.Verbose {
|
||||
fmt.Printf(" %s\n", hex.Dump(b))
|
||||
}
|
||||
|
||||
vi, err = parseVersionInfo(b, vi)
|
||||
if nil != err {
|
||||
return vi, keys, err
|
||||
}
|
||||
return vi, keys, nil
|
||||
}
|
||||
y++
|
||||
}
|
||||
}
|
||||
}
|
||||
x++
|
||||
}
|
||||
|
||||
return vi, keys, fmt.Errorf("no version info found")
|
||||
}
|
||||
|
||||
func parseVersionInfo(vi []byte, versionInfo map[string]string) (map[string]string, error) {
|
||||
// Grab everything after "StringFileInfo"
|
||||
stringFileInfoTemp := bytes.Split(vi, []byte{0x53, 0x0, 0x74, 0x0, 0x72, 0x0, 0x69, 0x0, 0x6e, 0x0, 0x67, 0x0, 0x46, 0x0, 0x69, 0x0, 0x6c, 0x0, 0x65, 0x0, 0x49, 0x0, 0x6e, 0x0, 0x66, 0x0, 0x6f})
|
||||
|
||||
if len(stringFileInfoTemp) < 2 {
|
||||
err := fmt.Errorf("can't find StringFileInfo bytes")
|
||||
return versionInfo, err
|
||||
}
|
||||
|
||||
stringFileInfo := stringFileInfoTemp[1]
|
||||
|
||||
divide := bytes.Split(stringFileInfo, []byte{0x0, 0x1, 0x0})
|
||||
|
||||
langCharSet := trimSlice(divide[1])
|
||||
versionInfo["langCharSet"] = string(langCharSet)
|
||||
|
||||
// check for slice out of bounds
|
||||
if len(divide) < 3 {
|
||||
err := fmt.Errorf("VersionInfo slice too small")
|
||||
return versionInfo, err
|
||||
}
|
||||
|
||||
end := len(divide) - 1
|
||||
if end < 2 {
|
||||
err := fmt.Errorf("slice end less than start")
|
||||
return versionInfo, err
|
||||
}
|
||||
|
||||
values := divide[2:end]
|
||||
|
||||
// TODO: handle varFileInfo, currently contains binary information which chrome does not display
|
||||
// varFileInfo := divide[len(divide)-1]
|
||||
// versionInfo["varFileInfo"] = string(trimSlice(varFileInfo))
|
||||
|
||||
for _, element := range values {
|
||||
temp := bytes.Split(element, []byte{0x0, 0x0, 0x0})
|
||||
valueInfo := temp[:len(temp)-1]
|
||||
|
||||
if len(valueInfo) > 1 {
|
||||
name := string(trimSlice(valueInfo[0]))
|
||||
value := string(trimSlice(valueInfo[1]))
|
||||
|
||||
versionInfo[name] = value
|
||||
}
|
||||
}
|
||||
|
||||
return versionInfo, nil
|
||||
}
|
||||
|
||||
func trimSlice(nonTrimmed []byte) (trimmed []byte) {
|
||||
for bytes.HasPrefix(nonTrimmed, []byte{0x0}) {
|
||||
nonTrimmed = nonTrimmed[1:]
|
||||
}
|
||||
|
||||
for i, val := range nonTrimmed {
|
||||
if i%2 == 0 && val != 0x0 {
|
||||
trimmed = append(trimmed, val)
|
||||
}
|
||||
}
|
||||
|
||||
return trimmed
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user