Add cf detect command (experimental but should mostly work)

This commit is contained in:
comp500 2020-08-06 22:24:48 +01:00
parent 48971d7b5a
commit 4d4f0d143e
4 changed files with 197 additions and 0 deletions

148
curseforge/detect.go Normal file
View File

@ -0,0 +1,148 @@
package curseforge
import (
"fmt"
"github.com/aviddiviner/go-murmur"
"github.com/comp500/packwiz/core"
"github.com/spf13/cobra"
"io/ioutil"
"os"
"path/filepath"
"strings"
)
// TODO: make all of this less bad and hardcoded
// detectCmd represents the detect command
var detectCmd = &cobra.Command{
Use: "detect",
Short: "Detect .jar files in the mods folder (experimental)",
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("Loading modpack...")
pack, err := core.LoadPack()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
index, err := pack.LoadIndex()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
// Walk files in the mods folder
var hashes []int
modPaths := make(map[int]string)
err = filepath.Walk("mods", func(path string, info os.FileInfo, err error) error {
if info.IsDir() {
return nil
}
if !strings.HasSuffix(path, ".jar") {
// TODO: make this less bad
return nil
}
fmt.Println("Hashing " + path)
bytes, err := ioutil.ReadFile(path)
if err != nil {
return err
}
hash := getByteArrayHash(bytes)
hashes = append(hashes, int(hash))
modPaths[int(hash)] = path
return nil
})
if err != nil {
fmt.Println(err)
os.Exit(1)
}
fmt.Printf("Found %d files, submitting...\n", len(hashes))
res, err := getFingerprintInfo(hashes)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("Successfully matched %d files\n", len(res.ExactFingerprints))
if len(res.PartialMatches) > 0 {
fmt.Println("The following fingerprints were partial and I don't know what to do!!!")
for _, v := range res.PartialMatches {
fmt.Printf("%s (%d)", modPaths[v], v)
}
}
if len(res.UnmatchedFingerprints) > 0 {
fmt.Printf("Failed to match the following %d files:\n", len(res.UnmatchedFingerprints))
for _, v := range res.UnmatchedFingerprints {
fmt.Printf("%s (%d)\n", modPaths[v], v)
}
}
fmt.Println("Installing...")
for _, v := range res.ExactMatches {
modInfoData, err := getModInfo(v.ID)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
err = createModFile(modInfoData, v.File, &index)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
path, ok := modPaths[v.File.Fingerprint]
if ok {
err = os.Remove(path)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
}
}
fmt.Println("Installation done")
err = index.Refresh()
if err != nil {
fmt.Println(err)
return
}
err = index.Write()
if err != nil {
fmt.Println(err)
return
}
err = pack.UpdateIndexHash()
if err != nil {
fmt.Println(err)
return
}
err = pack.Write()
if err != nil {
fmt.Println(err)
return
}
},
}
func init() {
curseforgeCmd.AddCommand(detectCmd)
}
func getByteArrayHash(bytes []byte) uint64 {
return uint64(murmur.MurmurHash2(computeNormalizedArray(bytes), 1))
}
func computeNormalizedArray(bytes []byte) []byte {
var newArray []byte
for _, b := range bytes {
if !isWhitespaceCharacter(b) {
newArray = append(newArray, b)
}
}
return newArray
}
func isWhitespaceCharacter(b byte) bool {
return b == 9 || b == 10 || b == 13 || b == 32
}

View File

@ -291,3 +291,49 @@ func getSearch(searchText string, gameVersion string) ([]modInfo, error) {
return infoRes, nil
}
type addonFingerprintResponse struct {
IsCacheBuilt bool `json:"isCacheBuilt"`
ExactMatches []struct {
ID int `json:"id"`
File modFileInfo `json:"file"`
LatestFiles []modFileInfo `json:"latestFiles"`
} `json:"exactMatches"`
ExactFingerprints []int `json:"exactFingerprints"`
PartialMatches []int `json:"partialMatches"`
PartialMatchFingerprints struct{} `json:"partialMatchFingerprints"`
InstalledFingerprints []int `json:"installedFingerprints"`
UnmatchedFingerprints []int `json:"unmatchedFingerprints"`
}
func getFingerprintInfo(hashes []int) (addonFingerprintResponse, error) {
var infoRes addonFingerprintResponse
client := &http.Client{}
hashesData, err := json.Marshal(hashes)
if err != nil {
return addonFingerprintResponse{}, err
}
req, err := http.NewRequest("POST", "https://addons-ecs.forgesvc.net/api/v2/fingerprint", bytes.NewBuffer(hashesData))
if err != nil {
return addonFingerprintResponse{}, err
}
// TODO: make this configurable application-wide
req.Header.Set("User-Agent", "comp500/packwiz client")
req.Header.Set("Accept", "application/json")
req.Header.Set("Content-Type", "application/json")
resp, err := client.Do(req)
if err != nil {
return addonFingerprintResponse{}, err
}
err = json.NewDecoder(resp.Body).Decode(&infoRes)
if err != nil && err != io.EOF {
return addonFingerprintResponse{}, err
}
return infoRes, nil
}

1
go.mod
View File

@ -2,6 +2,7 @@ module github.com/comp500/packwiz
require (
github.com/BurntSushi/toml v0.3.1
github.com/aviddiviner/go-murmur v0.0.0-20150519214947-b9740d71e571
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 // indirect
github.com/daviddengcn/go-colortext v0.0.0-20180409174941-186a3d44e920 // indirect
github.com/denormal/go-gitignore v0.0.0-20180930084346-ae8ad1d07817

2
go.sum
View File

@ -7,6 +7,8 @@ github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmx
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/aviddiviner/go-murmur v0.0.0-20150519214947-b9740d71e571 h1:seCdAEDyB0Hti/v1VajB7pAOIk9zmz/0/KE0D0oFqnc=
github.com/aviddiviner/go-murmur v0.0.0-20150519214947-b9740d71e571/go.mod h1:VzSzsYCY3W9xWYWD8T2GLDidWTe5rTZv+UdDMGhLfjg=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=