Compare commits

...

8 Commits
master ... cmds

Author SHA1 Message Date
lordwelch b5d007c865 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
2021-02-15 21:47:38 -08:00
lordwelch 59f8bdcc54 Change convert to use a struct instead of global variables 2021-02-15 21:35:38 -08:00
lordwelch 97fcfd145c GOG Changelog
Add FindChange to find a changelog matching a version
Refactor
2021-02-15 21:33:51 -08:00
lordwelch 32e0180f91 Add package for retrieving information from GOG (the changelog) 2021-02-15 21:32:00 -08:00
lordwelch 9442d5993d Add checkPaths to find BG3 when registry fails 2021-02-15 21:32:00 -08:00
lordwelch 2e6c882ca9 Add new main 2021-02-15 21:32:00 -08:00
lordwelch 6f9ebf4906 Initial code to create a git repo
separate into separate sub commands
2021-02-15 21:32:00 -08:00
lordwelch 8f099fcd27 Add error handling for decompression 2021-02-15 21:30:46 -08:00
23 changed files with 1934 additions and 222 deletions

2
.gitignore vendored
View File

@ -1,2 +1,2 @@
lsconvert
/lsconvert
*.sublime-workspace

View File

@ -82,18 +82,18 @@ func MakeCompressionFlags(method CompressionMethod, level CompressionLevel) int
return flags | int(level)
}
func Decompress(compressed io.Reader, uncompressedSize int, compressionFlags byte, chunked bool) io.ReadSeeker {
func Decompress(compressed io.Reader, uncompressedSize int, compressionFlags byte, chunked bool) (io.ReadSeeker, error) {
switch CompressionMethod(compressionFlags & 0x0f) {
case CMNone:
if v, ok := compressed.(io.ReadSeeker); ok {
return v
return v, nil
}
panic(errors.New("compressed must be an io.ReadSeeker if there is no compression"))
return nil, errors.New("compressed must be an io.ReadSeeker if there is no compression")
case CMZlib:
zr, _ := zlib.NewReader(compressed)
v, _ := ioutil.ReadAll(zr)
return bytes.NewReader(v)
return bytes.NewReader(v), nil
case CMLZ4:
if chunked {
@ -101,21 +101,21 @@ func Decompress(compressed io.Reader, uncompressedSize int, compressionFlags byt
p := make([]byte, uncompressedSize)
_, err := zr.Read(p)
if err != nil {
panic(err)
return nil, err
}
return bytes.NewReader(p)
return bytes.NewReader(p), nil
}
src, _ := ioutil.ReadAll(compressed)
dst := make([]byte, uncompressedSize*2)
_, err := lz4.UncompressBlock(src, dst)
if err != nil {
panic(err)
return nil, err
}
return bytes.NewReader(dst)
return bytes.NewReader(dst), nil
default:
panic(fmt.Errorf("no decompressor found for this format: %v", compressionFlags))
return nil, fmt.Errorf("no decompressor found for this format: %v", compressionFlags)
}
}

229
cmd/lsconvert/convert.go Normal file
View File

@ -0,0 +1,229 @@
package main
import (
"bytes"
"encoding/xml"
"errors"
"flag"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"git.narnian.us/lordwelch/lsgo"
_ "git.narnian.us/lordwelch/lsgo/lsb"
_ "git.narnian.us/lordwelch/lsgo/lsf"
"github.com/go-kit/kit/log"
"github.com/kr/pretty"
)
type convertFlags struct {
write bool
printXML bool
printResource bool
recurse bool
logging bool
parts string
}
func convert(arguments ...string) error {
var (
convertFS = flag.NewFlagSet("convert", flag.ExitOnError)
cvFlags = convertFlags{}
)
convertFS.BoolVar(&cvFlags.write, "w", false, "Replace the file with XML data")
convertFS.BoolVar(&cvFlags.printXML, "x", false, "Print XML to stdout")
convertFS.BoolVar(&cvFlags.printResource, "R", false, "Print the resource struct to stderr")
convertFS.BoolVar(&cvFlags.recurse, "r", false, "Recurse into directories")
convertFS.BoolVar(&cvFlags.logging, "l", false, "Enable logging to stderr")
convertFS.StringVar(&cvFlags.parts, "p", "", "Parts to filter logging for, comma separated")
convertFS.Parse(arguments)
if cvFlags.logging {
lsgo.Logger = lsgo.NewFilter(map[string][]string{
"part": strings.Split(cvFlags.parts, ","),
}, log.NewLogfmtLogger(os.Stderr))
}
for _, v := range convertFS.Args() {
fi, err := os.Stat(v)
if err != nil {
return err
}
switch {
case !fi.IsDir():
err = cvFlags.openLSF(v)
if err != nil && !errors.As(err, &lsgo.HeaderError{}) {
return err
}
case cvFlags.recurse:
_ = filepath.Walk(v, func(path string, info os.FileInfo, err error) error {
if err != nil {
return nil
}
if info.IsDir() {
if info.Name() == ".git" {
return filepath.SkipDir
}
return nil
}
err = cvFlags.openLSF(path)
if err != nil && !errors.Is(err, lsgo.ErrFormat) {
fmt.Fprintln(os.Stderr, err)
}
return nil
})
default:
return fmt.Errorf("lsconvert: %s: Is a directory\n", v)
}
}
return nil
}
func (cf *convertFlags) openLSF(filename string) error {
var (
l *lsgo.Resource
err error
n string
f interface {
io.Writer
io.StringWriter
}
)
l, err = readLSF(filename)
if err != nil {
return fmt.Errorf("reading LSF file %s failed: %w", filename, err)
}
if cf.printResource {
pretty.Log(l)
}
if cf.printXML || cf.write {
n, err = marshalXML(l)
if err != nil {
return fmt.Errorf("creating XML from LSF file %s failed: %w", filename, err)
}
if cf.write {
f, err = os.OpenFile(filename, os.O_TRUNC|os.O_RDWR, 0o666)
if err != nil {
return fmt.Errorf("writing XML from LSF file %s failed: %w", filename, err)
}
} else if cf.printXML {
f = os.Stdout
}
err = writeXML(f, n)
fmt.Fprint(f, "\n")
if err != nil {
return fmt.Errorf("writing XML from LSF file %s failed: %w", filename, err)
}
}
return nil
}
func readLSF(filename string) (*lsgo.Resource, error) {
var (
l lsgo.Resource
r io.ReadSeeker
file *os.File
fi os.FileInfo
err error
)
switch filepath.Ext(filename) {
case ".lsf", ".lsb":
var b []byte
fi, err = os.Stat(filename)
if err != nil {
return nil, err
}
// Arbitrary size, no lsf file should reach 100 MB (I haven't found one over 90 KB)
// and if you don't have 100 MB of ram free you shouldn't be using this
if fi.Size() <= 100*1024*1024 {
b, err = ioutil.ReadFile(filename)
if err != nil {
return nil, err
}
r = bytes.NewReader(b)
break
}
fallthrough
default:
b := make([]byte, 4)
file, err = os.Open(filename)
if err != nil {
return nil, err
}
defer file.Close()
_, err = file.Read(b)
if err != nil {
return nil, err
}
if !lsgo.SupportedFormat(b) {
return nil, lsgo.ErrFormat
}
_, err = file.Seek(0, io.SeekStart)
if err != nil {
return nil, err
}
fi, _ = os.Stat(filename)
// I have never seen a valid "ls*" file over 90 KB
if fi.Size() < 1*1024*1024 {
b, err = ioutil.ReadAll(file)
if err != nil {
return nil, err
}
r = bytes.NewReader(b)
} else {
r = file
}
}
l, _, err = lsgo.Decode(r)
if err != nil {
return nil, fmt.Errorf("decoding %q failed: %w", filename, err)
}
return &l, nil
}
func marshalXML(l *lsgo.Resource) (string, error) {
var (
v []byte
err error
)
v, err = xml.MarshalIndent(struct {
*lsgo.Resource
XMLName string `xml:"save"`
}{l, ""}, "", "\t")
if err != nil {
return string(v), err
}
n := string(v)
n = strings.ReplaceAll(n, "></version>", " />")
n = strings.ReplaceAll(n, "></attribute>", " />")
n = strings.ReplaceAll(n, "></node>", " />")
n = strings.ReplaceAll(n, "false", "False")
n = strings.ReplaceAll(n, "true", "True")
n = strings.ReplaceAll(n, "&#39;", "'")
n = strings.ReplaceAll(n, "&#34;", "&quot;")
return n, nil
}
func writeXML(f io.StringWriter, n string) error {
var err error
_, err = f.WriteString(strings.ToLower(xml.Header))
if err != nil {
return err
}
_, err = f.WriteString(n)
if err != nil {
return err
}
return nil
}

View 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"]
}

308
cmd/lsconvert/init.go Normal file
View File

@ -0,0 +1,308 @@
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"
)
var (
initFS = flag.NewFlagSet("init", flag.ExitOnError)
initFlags struct {
BG3Path string
divinePath string
Repo 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 (
b BG3Repo
err error
)
b.gitPath = path
b.BG3Path, b.divinePath = preRequisites()
if b.BG3Path == "" {
panic("Could not locate Baldur's Gate 3")
}
b.git, err = git.PlainOpen(b.gitPath)
if err != nil {
b.Empty = true
}
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) {
BG3Path = locateBG3()
divinePath = locateLSLIB()
return BG3Path, divinePath
}
// locateLSLIB specifically locates divine.exe
func locateLSLIB() string {
var searchPath []string
folderName := "ExportTool-*"
if runtime.GOOS == "windows" {
for _, v := range "cdefgh" {
for _, vv := range []string{`:\Program Files*\*\`, `:\app*\`, `:\`} {
paths, err := filepath.Glob(filepath.Join(string(v)+vv, folderName))
if err != nil {
panic(err)
}
searchPath = append(searchPath, paths...)
}
}
for _, v := range []string{os.ExpandEnv(`${USERPROFILE}\app*\`), os.ExpandEnv(`${USERPROFILE}\Downloads\`), os.ExpandEnv(`${USERPROFILE}\Documents\`)} {
paths, err := filepath.Glob(filepath.Join(v, folderName))
if err != nil {
panic(err)
}
searchPath = append(searchPath, paths...)
}
}
divine, _ := ie.LookPath("divine.exe", strings.Join(searchPath, string(os.PathListSeparator)))
return divine
}
func checkPaths() string {
var searchPath []string
folderName := `Baldurs Gate 3\bin\`
if runtime.GOOS == "windows" {
for _, v := range "cdefgh" {
for _, vv := range []string{`:\Program Files*\*\`, `:\Program Files*\GOG Galaxy\Games\`, `:\GOG Galaxy\Games\`, `:\GOG Games\`, `:\app*\`, `:\`} {
paths, err := filepath.Glob(filepath.Join(string(v)+vv, folderName))
if err != nil {
panic(err)
}
searchPath = append(searchPath, paths...)
}
}
}
bg3, _ := ie.LookPath("bg3.exe", strings.Join(searchPath, string(os.PathListSeparator)))
return strings.TrimSuffix(bg3, `\bin\bg3.exe`)
}
func locateBG3() string {
installPath := initFlags.BG3Path
if installPath == "" {
installPath = checkRegistry()
}
if installPath == "" {
installPath = checkPaths()
}
return installPath
}
func checkRegistry() string {
var (
k registry.Key
err error
installPath string
)
k, err = registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\1456460669_is1`, registry.QUERY_VALUE)
if err != nil {
return ""
}
defer k.Close()
for _, v := range []string{"Inno Setup: App Path", "InstallLocation"} {
var s string
s, _, err = k.GetStringValue(v)
if err == nil {
installPath = s
break
}
}
return installPath
}

View File

@ -1,223 +1,43 @@
package main
import (
"bytes"
"encoding/xml"
"errors"
"flag"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"git.narnian.us/lordwelch/lsgo"
_ "git.narnian.us/lordwelch/lsgo/lsb"
_ "git.narnian.us/lordwelch/lsgo/lsf"
"github.com/go-kit/kit/log"
"github.com/kr/pretty"
)
var (
write = flag.Bool("w", false, "replace the file with xml data")
printXML = flag.Bool("x", false, "print xml to stdout")
printResource = flag.Bool("R", false, "print the resource struct to stderr")
recurse = flag.Bool("r", false, "recurse into directories")
logging = flag.Bool("l", false, "enable logging to stderr")
parts = flag.String("p", "", "parts to filter logging for, comma separated")
)
func init() {
flag.Parse()
if *logging {
lsgo.Logger = lsgo.NewFilter(map[string][]string{
"part": strings.Split(*parts, ","),
}, log.NewLogfmtLogger(os.Stderr))
}
}
func main() {
for _, v := range flag.Args() {
fi, err := os.Stat(v)
// flag.Parse()
arg := ""
if len(os.Args) > 1 {
arg = os.Args[1]
}
switch arg {
case "convert":
err := convert(os.Args[1:]...)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
switch {
case !fi.IsDir():
err = openLSF(v)
if err != nil && !errors.As(err, &lsgo.HeaderError{}) {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
case *recurse:
_ = filepath.Walk(v, func(path string, info os.FileInfo, err error) error {
if err != nil {
return nil
}
if info.IsDir() {
if info.Name() == ".git" {
return filepath.SkipDir
}
return nil
}
err = openLSF(path)
if err != nil && !errors.As(err, &lsgo.HeaderError{}) {
fmt.Fprintln(os.Stderr, err)
}
return nil
})
default:
fmt.Fprintf(os.Stderr, "lsconvert: %s: Is a directory\n", v)
case "init":
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:
err := convert(os.Args[1:]...)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
}
func openLSF(filename string) error {
var (
l *lsgo.Resource
err error
n string
f interface {
io.Writer
io.StringWriter
}
)
l, err = readLSF(filename)
if err != nil {
return fmt.Errorf("reading LSF file %s failed: %w", filename, err)
}
if *printResource {
pretty.Log(l)
}
if *printXML || *write {
n, err = marshalXML(l)
if err != nil {
return fmt.Errorf("creating XML from LSF file %s failed: %w", filename, err)
}
if *write {
f, err = os.OpenFile(filename, os.O_TRUNC|os.O_RDWR, 0o666)
if err != nil {
return fmt.Errorf("writing XML from LSF file %s failed: %w", filename, err)
}
} else if *printXML {
f = os.Stdout
}
err = writeXML(f, n)
fmt.Fprint(f, "\n")
if err != nil {
return fmt.Errorf("writing XML from LSF file %s failed: %w", filename, err)
}
}
return nil
}
func readLSF(filename string) (*lsgo.Resource, error) {
var (
l lsgo.Resource
r io.ReadSeeker
file *os.File
fi os.FileInfo
err error
)
switch filepath.Ext(filename) {
case ".lsf", ".lsb":
var b []byte
fi, err = os.Stat(filename)
if err != nil {
return nil, err
}
// Arbitrary size, no lsf file should reach 100 MB (I haven't found one over 90 KB)
// and if you don't have 100 MB of ram free you shouldn't be using this
if fi.Size() <= 100*1024*1024 {
b, err = ioutil.ReadFile(filename)
if err != nil {
return nil, err
}
r = bytes.NewReader(b)
break
}
fallthrough
default:
b := make([]byte, 4)
file, err = os.Open(filename)
if err != nil {
return nil, err
}
defer file.Close()
_, err = file.Read(b)
if err != nil {
return nil, err
}
if !lsgo.SupportedFormat(b) {
return nil, lsgo.ErrFormat
}
_, err = file.Seek(0, io.SeekStart)
if err != nil {
return nil, err
}
fi, _ = os.Stat(filename)
// I have never seen a valid "ls*" file over 90 KB
if fi.Size() < 1*1024*1024 {
b, err = ioutil.ReadAll(file)
if err != nil {
return nil, err
}
r = bytes.NewReader(b)
} else {
r = file
}
}
l, _, err = lsgo.Decode(r)
if err != nil {
return nil, err
}
return &l, nil
}
func marshalXML(l *lsgo.Resource) (string, error) {
var (
v []byte
err error
)
v, err = xml.MarshalIndent(struct {
*lsgo.Resource
XMLName string `xml:"save"`
}{l, ""}, "", "\t")
if err != nil {
return string(v), err
}
n := string(v)
n = strings.ReplaceAll(n, "></version>", " />")
n = strings.ReplaceAll(n, "></attribute>", " />")
n = strings.ReplaceAll(n, "></node>", " />")
n = strings.ReplaceAll(n, "false", "False")
n = strings.ReplaceAll(n, "true", "True")
n = strings.ReplaceAll(n, "&#39;", "'")
n = strings.ReplaceAll(n, "&#34;", "&quot;")
return n, nil
}
func writeXML(f io.StringWriter, n string) error {
var err error
_, err = f.WriteString(strings.ToLower(xml.Header))
if err != nil {
return err
}
_, err = f.WriteString(n)
if err != nil {
return err
}
return nil
}

7
go.mod
View File

@ -4,10 +4,17 @@ 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
golang.org/x/sys v0.0.0-20210108172913-0df2131ae363
gonum.org/v1/gonum v0.8.2
)

68
go.sum
View File

@ -7,15 +7,21 @@ github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMx
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs=
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
@ -37,7 +43,9 @@ github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
@ -45,15 +53,29 @@ github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5m
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4=
github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E=
github.com/go-git/go-billy/v5 v5.0.0 h1:7NQHvd9FVid8VL4qVUMm8XifBK+2xCoZ2lSk0agRrHM=
github.com/go-git/go-billy/v5 v5.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12 h1:PbKy9zOy4aAKrJ5pibIRpVO2BXnK1Tlcg+caKI7Ox5M=
github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw=
github.com/go-git/go-git/v5 v5.2.0 h1:YPBLG/3UK1we1ohRkncLjaXWLW+HKp5QNM/jTli2JgI=
github.com/go-git/go-git/v5 v5.2.0/go.mod h1:kh02eMX+wdqqxgNMEyq8YgwlIOsDOa9homkUq1PoTMs=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.10.0 h1:dXFJfIHVvUcpSgDOV+Ne6t7jXri8Tfv2uOLHUZ2XNuo=
@ -82,6 +104,7 @@ github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Z
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
@ -98,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=
@ -118,8 +144,13 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg=
github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
@ -128,6 +159,8 @@ github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd h1:Coekwdh0v2wtGp9Gmz1Ze3eVRAWJMLokvN3QjdzCHLY=
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
@ -136,8 +169,9 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
@ -149,6 +183,8 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
@ -166,6 +202,8 @@ github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzE
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
@ -189,8 +227,10 @@ github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0
github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
@ -216,6 +256,8 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
@ -232,10 +274,13 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70=
github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
@ -251,10 +296,15 @@ go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
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=
@ -284,6 +334,8 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -301,13 +353,21 @@ golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/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-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@ -355,16 +415,22 @@ google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

236
gog/changelog.go Normal file
View File

@ -0,0 +1,236 @@
package gog
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"strings"
"golang.org/x/net/html"
"golang.org/x/net/html/atom"
)
type Change struct {
Title string
Changes []string
Sub []Change
recurse bool
}
func (c Change) str(indent int) string {
var (
s = new(strings.Builder)
ind = strings.Repeat("\t", indent)
)
fmt.Fprintln(s, ind+c.Title)
for _, v := range c.Changes {
fmt.Fprintln(s, ind+"\t"+v)
}
for _, v := range c.Sub {
s.WriteString(v.str(indent + 1))
}
return s.String()
}
func (c Change) String() string {
return c.str(0)
}
func (c Change) FindChange(version string) *Change {
if strings.Contains(c.Title, version) {
return &c
}
for _, v := range c.Sub {
cc := v.FindChange(version)
if cc != nil {
return cc
}
}
return nil
}
func debug(f ...interface{}) {
if len(os.Args) > 2 && os.Args[2] == "debug" {
fmt.Println(f...)
}
}
// Stringify returns the text from a node and all its children
func stringify(h *html.Node) string {
var (
f func(*html.Node)
str string
def = h
)
f = func(n *html.Node) {
if n.Type == html.TextNode {
str += strings.TrimSpace(n.Data) + " "
}
if n.DataAtom == atom.Br {
str += "\n"
}
for c := n.FirstChild; c != nil; c = c.NextSibling {
f(c)
}
}
f(def)
return str
}
// ParseChange returns the parsed change and the next node to start parsing the next change
func ParseChange(h *html.Node, title string) (Change, *html.Node) {
var c Change
c.Title = title
debug("change", strings.TrimSpace(h.Data))
l:
for h.Data != "hr" {
switch h.Type {
case html.ErrorNode:
panic("An error happened!?!?")
case html.TextNode:
debug("text", strings.TrimSpace(h.Data))
if strings.TrimSpace(h.Data) != "" {
c.Changes = append(c.Changes, strings.TrimSpace(h.Data))
}
case html.ElementNode:
switch h.DataAtom {
case atom.H1, atom.H2, atom.H3, atom.H4, atom.H5, atom.H6, atom.P:
debug(h.DataAtom.String())
if c.Title == "" {
c.Title = strings.TrimSpace(stringify(h))
} else {
tmp := drain(h.NextSibling)
switch tmp.DataAtom {
case atom.H1, atom.H2, atom.H3, atom.H4, atom.H5, atom.H6:
h = tmp
c.Changes = append(c.Changes, strings.TrimSpace(stringify(h)))
break
}
var cc Change
debug("h", strings.TrimSpace(h.Data))
debug("h", strings.TrimSpace(h.NextSibling.Data))
cc, h = ParseChange(h.NextSibling, stringify(h))
debug("h2", h.Data, h.PrevSibling.Data)
// h = h.Parent
c.Sub = append(c.Sub, cc)
continue
}
case atom.Ul:
debug(h.DataAtom.String())
h = h.FirstChild
continue
case atom.Li:
debug("li", h.DataAtom.String())
if h.FirstChild != h.LastChild && h.FirstChild.NextSibling.DataAtom != atom.P && h.FirstChild.NextSibling.DataAtom != atom.A {
var cc Change
cc, h = ParseChange(h.FirstChild.NextSibling, strings.TrimSpace(h.FirstChild.Data))
h = h.Parent
if h.NextSibling != nil {
h = h.NextSibling
}
// pretty.Println(cc)
c.Sub = append(c.Sub, cc)
break l
}
fallthrough
default:
var (
f func(*html.Node)
str string
def = h
)
f = func(n *html.Node) {
if n.Type == html.TextNode {
str += strings.TrimSpace(n.Data) + " "
}
for c := n.FirstChild; c != nil; c = c.NextSibling {
f(c)
}
}
f(def)
c.Changes = append(c.Changes, strings.TrimSpace(str))
}
}
if h.DataAtom == atom.Ul {
h = h.Parent
}
if h.NextSibling != nil { // Move to the next node, h should never be nil
debug("next", h.Type, h.NextSibling.Type)
h = h.NextSibling
} else if h.DataAtom == atom.Li || h.PrevSibling.DataAtom == atom.Li && h.Parent.DataAtom != atom.Body { // If we are currently in a list then go up one unless that would take us to body
debug("ul", strings.TrimSpace(h.Data), strings.TrimSpace(h.Parent.Data))
if h.Parent.NextSibling == nil {
h = h.Parent
} else { // go to parents next sibling so we don't parse the same node again
h = h.Parent.NextSibling
}
debug("break2", strings.TrimSpace(h.Data))
break
} else {
debug("I don't believe this should ever happen")
break
}
}
h = drain(h)
return c, h
}
// drain skips over non-Element Nodes
func drain(h *html.Node) *html.Node {
for h.NextSibling != nil {
if h.Type == html.ElementNode {
break
}
h = h.NextSibling
}
return h
}
func ParseChangelog(ch, title string) (Change, error) {
var (
p *html.Node
v Change
err error
)
v.Title = title
p, err = html.Parse(strings.NewReader(ch))
if err != nil {
return v, err
}
p = p.FirstChild.FirstChild.NextSibling.FirstChild
for p != nil && p.NextSibling != nil {
var tmp Change
tmp, p = ParseChange(p, "")
if p.DataAtom == atom.Hr {
p = p.NextSibling
}
v.Sub = append(v.Sub, tmp)
}
return v, nil
}
func RetrieveGOGInfo(id string) (GOGalaxy, error) {
var (
r *http.Response
err error
b []byte
info GOGalaxy
)
r, err = http.Get("https://api.gog.com/products/" + id + "?expand=downloads,expanded_dlcs,description,screenshots,videos,related_products,changelog")
if err != nil {
return GOGalaxy{}, fmt.Errorf("failed to retrieve GOG info: %w", err)
}
b, err = ioutil.ReadAll(r.Body)
if err != nil {
return GOGalaxy{}, fmt.Errorf("failed to retrieve GOG info: %w", err)
}
r.Body.Close()
err = json.Unmarshal(b, &info)
if err != nil {
return GOGalaxy{}, fmt.Errorf("failed to retrieve GOG info: %w", err)
}
return info, nil
}

201
gog/gog.go Normal file
View File

@ -0,0 +1,201 @@
package gog
import (
"bytes"
"encoding/json"
"reflect"
"time"
)
type GOGalaxy struct {
ID int `json:"id"`
Title string `json:"title"`
PurchaseLink string `json:"purchase_link"`
Slug string `json:"slug"`
ContentSystemCompatibility struct {
Windows bool `json:"windows"`
Osx bool `json:"osx"`
Linux bool `json:"linux"`
} `json:"content_system_compatibility"`
Languages map[string]string `json:"languages"`
Links struct {
PurchaseLink string `json:"purchase_link"`
ProductCard string `json:"product_card"`
Support string `json:"support"`
Forum string `json:"forum"`
} `json:"links"`
InDevelopment struct {
Active bool `json:"active"`
Until interface{} `json:"until"`
} `json:"in_development"`
IsSecret bool `json:"is_secret"`
IsInstallable bool `json:"is_installable"`
GameType string `json:"game_type"`
IsPreOrder bool `json:"is_pre_order"`
ReleaseDate time.Time `json:"release_date"`
Images Images `json:"images"`
Dlcs DLCs `json:"dlcs"`
Downloads struct {
Installers []Download `json:"installers"`
Patches []Download `json:"patches"`
LanguagePacks []Download `json:"language_packs"`
BonusContent []Download `json:"bonus_content"`
} `json:"downloads"`
ExpandedDlcs []Product `json:"expanded_dlcs"`
Description struct {
Lead string `json:"lead"`
Full string `json:"full"`
WhatsCoolAboutIt string `json:"whats_cool_about_it"`
} `json:"description"`
Screenshots []struct {
ImageID string `json:"image_id"`
FormatterTemplateURL string `json:"formatter_template_url"`
FormattedImages []struct {
FormatterName string `json:"formatter_name"`
ImageURL string `json:"image_url"`
} `json:"formatted_images"`
} `json:"screenshots"`
Videos []struct {
VideoURL string `json:"video_url"`
ThumbnailURL string `json:"thumbnail_url"`
Provider string `json:"provider"`
} `json:"videos"`
RelatedProducts []Product `json:"related_products"`
Changelog string `json:"changelog"`
}
func (gog *GOGalaxy) UnmarshalJSON(d []byte) error {
var err error
type T2 GOGalaxy // create new type with same structure as T but without its method set to avoid infinite `UnmarshalJSON` call stack
x := struct {
T2
ReleaseDate string `json:"release_date"`
}{} // don't forget this, if you do and 't' already has some fields set you would lose them; see second example
if err = json.Unmarshal(d, &x); err != nil {
return err
}
*gog = GOGalaxy(x.T2)
if gog.ReleaseDate, err = time.Parse("2006-01-02T15:04:05-0700", x.ReleaseDate); err != nil {
return err
}
return nil
}
type DLCs struct {
Products []struct {
ID int `json:"id"`
Link string `json:"link"`
ExpandedLink string `json:"expanded_link"`
} `json:"products"`
AllProductsURL string `json:"all_products_url"`
ExpandedAllProductsURL string `json:"expanded_all_products_url"`
}
func (DLC *DLCs) UnmarshalJSON(d []byte) error {
type T2 GOGalaxy
var x T2
dec := json.NewDecoder(bytes.NewBuffer(d))
t, err := dec.Token()
if err != nil {
return err
}
if delim, ok := t.(json.Delim); ok {
if delim == '[' {
return nil
} else {
return json.Unmarshal(d, &x)
}
} else {
return &json.UnmarshalTypeError{
Value: reflect.TypeOf(t).String(),
Type: reflect.TypeOf(*DLC),
Offset: 0,
Struct: "DLCs",
Field: ".",
}
}
}
type Languages map[string]string
type Download struct {
ID string `json:"id"`
Name string `json:"name"`
Os string `json:"os"`
Language string `json:"language"`
LanguageFull string `json:"language_full"`
Version string `json:"version"`
TotalSize int64 `json:"total_size"`
Files []File `json:"files"`
}
func (d *Download) UnmarshalJSON(data []byte) error {
var i json.Number
type T2 Download // create new type with same structure as T but without its method set to avoid infinite `UnmarshalJSON` call stack
x := struct {
T2
ID json.RawMessage `json:"id"`
}{} // don't forget this, if you do and 't' already has some fields set you would lose them; see second example
if err := json.Unmarshal(data, &x); err != nil {
return err
}
*d = Download(x.T2)
if err := json.Unmarshal(x.ID, &i); err != nil {
if err := json.Unmarshal(x.ID, &d.ID); err != nil {
return err
}
return nil
}
d.ID = i.String()
return nil
}
type File struct {
ID string `json:"id"`
Size int `json:"size"`
Downlink string `json:"downlink"`
}
type Product struct {
ID int `json:"id"`
Title string `json:"title"`
PurchaseLink string `json:"purchase_link"`
Slug string `json:"slug"`
ContentSystemCompatibility struct {
Windows bool `json:"windows"`
Osx bool `json:"osx"`
Linux bool `json:"linux"`
} `json:"content_system_compatibility"`
Links struct {
PurchaseLink string `json:"purchase_link"`
ProductCard string `json:"product_card"`
Support string `json:"support"`
Forum string `json:"forum"`
} `json:"links"`
InDevelopment struct {
Active bool `json:"active"`
Until time.Time `json:"until"`
} `json:"in_development"`
IsSecret bool `json:"is_secret"`
IsInstallable bool `json:"is_installable"`
GameType string `json:"game_type"`
IsPreOrder bool `json:"is_pre_order"`
ReleaseDate string `json:"release_date"`
Images Images `json:"images"`
Languages Languages `json:"languages,omitempty"`
}
type Images struct {
Background string `json:"background"`
Logo string `json:"logo"`
Logo2X string `json:"logo2x"`
Icon string `json:"icon"`
SidebarIcon string `json:"sidebarIcon"`
SidebarIcon2X string `json:"sidebarIcon2x"`
MenuNotificationAv string `json:"menuNotificationAv"`
MenuNotificationAv2 string `json:"menuNotificationAv2"`
}

53
internal/exec/lp_unix.go Normal file
View File

@ -0,0 +1,53 @@
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris
package exec
import (
"os"
"os/exec"
"path/filepath"
"strings"
)
func findExecutable(file string) error {
d, err := os.Stat(file)
if err != nil {
return err
}
if m := d.Mode(); !m.IsDir() && m&0111 != 0 {
return nil
}
return os.ErrPermission
}
// LookPath searches for an executable named file in the
// directories named by the PATH environment variable.
// If file contains a slash, it is tried directly and the PATH is not consulted.
// The result may be an absolute path or a path relative to the current directory.
func LookPath(file, path string) (string, error) {
// NOTE(rsc): I wish we could use the Plan 9 behavior here
// (only bypass the path if file begins with / or ./ or ../)
// but that would not match all the Unix shells.
if strings.Contains(file, "/") {
err := findExecutable(file)
if err == nil {
return file, nil
}
return "", &exec.Error{file, err}
}
for _, dir := range filepath.SplitList(path) {
if dir == "" {
// Unix shell semantics: path element "" means "."
dir = "."
}
path := filepath.Join(dir, file)
if err := findExecutable(path); err == nil {
return path, nil
}
}
return "", &exec.Error{file, exec.ErrNotFound}
}

View File

@ -0,0 +1,89 @@
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package exec
import (
"os"
"os/exec"
"path/filepath"
"strings"
)
func chkStat(file string) error {
d, err := os.Stat(file)
if err != nil {
return err
}
if d.IsDir() {
return os.ErrPermission
}
return nil
}
func hasExt(file string) bool {
i := strings.LastIndex(file, ".")
if i < 0 {
return false
}
return strings.LastIndexAny(file, `:\/`) < i
}
func findExecutable(file string, exts []string) (string, error) {
if len(exts) == 0 {
return file, chkStat(file)
}
if hasExt(file) {
if chkStat(file) == nil {
return file, nil
}
}
for _, e := range exts {
if f := file + e; chkStat(f) == nil {
return f, nil
}
}
return "", os.ErrNotExist
}
// LookPath searches for an executable named file in the
// directories named by the PATH environment variable.
// If file contains a slash, it is tried directly and the PATH is not consulted.
// LookPath also uses PATHEXT environment variable to match
// a suitable candidate.
// The result may be an absolute path or a path relative to the current directory.
func LookPath(file, path string) (string, error) {
var exts []string
x := os.Getenv(`PATHEXT`)
if x != "" {
for _, e := range strings.Split(strings.ToLower(x), `;`) {
if e == "" {
continue
}
if e[0] != '.' {
e = "." + e
}
exts = append(exts, e)
}
} else {
exts = []string{".com", ".exe", ".bat", ".cmd"}
}
if strings.ContainsAny(file, `:\/`) {
if f, err := findExecutable(file, exts); err == nil {
return f, nil
} else {
return "", &exec.Error{file, err}
}
}
if f, err := findExecutable(filepath.Join(".", file), exts); err == nil {
return f, nil
}
for _, dir := range filepath.SplitList(path) {
if f, err := findExecutable(filepath.Join(dir, file), exts); err == nil {
return f, nil
}
}
return "", &exec.Error{file, exec.ErrNotFound}
}

View File

@ -678,7 +678,10 @@ func Read(r io.ReadSeeker) (lsgo.Resource, error) {
uncompressed := lsgo.LimitReadSeeker(r, int64(hdr.StringsSizeOnDisk))
if isCompressed {
uncompressed = lsgo.Decompress(uncompressed, int(hdr.StringsUncompressedSize), hdr.CompressionFlags, false)
uncompressed, err = lsgo.Decompress(uncompressed, int(hdr.StringsUncompressedSize), hdr.CompressionFlags, false)
if err != nil {
return lsgo.Resource{}, fmt.Errorf("decompressing LSF name failed: %w", err)
}
}
// using (var nodesFile = new FileStream("names.bin", FileMode.Create, FileAccess.Write))
@ -703,7 +706,10 @@ func Read(r io.ReadSeeker) (lsgo.Resource, error) {
if hdr.NodesSizeOnDisk > 0 || hdr.NodesUncompressedSize > 0 {
uncompressed := lsgo.LimitReadSeeker(r, int64(hdr.NodesSizeOnDisk))
if isCompressed {
uncompressed = lsgo.Decompress(uncompressed, int(hdr.NodesUncompressedSize), hdr.CompressionFlags, hdr.Version >= lsgo.VerChunkedCompress)
uncompressed, err = lsgo.Decompress(uncompressed, int(hdr.NodesUncompressedSize), hdr.CompressionFlags, hdr.Version >= lsgo.VerChunkedCompress)
if err != nil {
return lsgo.Resource{}, fmt.Errorf("decompressing LSF nodes failed: %w", err)
}
}
// using (var nodesFile = new FileStream("nodes.bin", FileMode.Create, FileAccess.Write))
@ -729,7 +735,10 @@ func Read(r io.ReadSeeker) (lsgo.Resource, error) {
if hdr.AttributesSizeOnDisk > 0 || hdr.AttributesUncompressedSize > 0 {
var uncompressed io.ReadSeeker = lsgo.LimitReadSeeker(r, int64(hdr.AttributesSizeOnDisk))
if isCompressed {
uncompressed = lsgo.Decompress(uncompressed, int(hdr.AttributesUncompressedSize), hdr.CompressionFlags, hdr.Version >= lsgo.VerChunkedCompress)
uncompressed, err = lsgo.Decompress(uncompressed, int(hdr.AttributesUncompressedSize), hdr.CompressionFlags, hdr.Version >= lsgo.VerChunkedCompress)
if err != nil {
return lsgo.Resource{}, fmt.Errorf("decompressing LSF attributes failed: %w", err)
}
}
// using (var attributesFile = new FileStream("attributes.bin", FileMode.Create, FileAccess.Write))
@ -752,7 +761,10 @@ func Read(r io.ReadSeeker) (lsgo.Resource, error) {
var uncompressed io.ReadSeeker = lsgo.LimitReadSeeker(r, int64(hdr.ValuesSizeOnDisk))
if hdr.ValuesSizeOnDisk > 0 || hdr.ValuesUncompressedSize > 0 {
if isCompressed {
uncompressed = lsgo.Decompress(r, int(hdr.ValuesUncompressedSize), hdr.CompressionFlags, hdr.Version >= lsgo.VerChunkedCompress)
uncompressed, err = lsgo.Decompress(r, int(hdr.ValuesUncompressedSize), hdr.CompressionFlags, hdr.Version >= lsgo.VerChunkedCompress)
if err != nil {
return lsgo.Resource{}, fmt.Errorf("decompressing LSF values failed: %w", err)
}
}
}

15
third_party/peinfo-go/.gitignore vendored Normal file
View 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
View 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
View 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
View 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
View 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
View 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",
}

View 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
View 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
View 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
View 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
}