From 4d4f0d143e47b6019249b3f26047e632689bf4f4 Mon Sep 17 00:00:00 2001 From: comp500 Date: Thu, 6 Aug 2020 22:24:48 +0100 Subject: [PATCH] Add cf detect command (experimental but should mostly work) --- curseforge/detect.go | 148 ++++++++++++++++++++++++++++++++++++++++++ curseforge/request.go | 46 +++++++++++++ go.mod | 1 + go.sum | 2 + 4 files changed, 197 insertions(+) create mode 100644 curseforge/detect.go diff --git a/curseforge/detect.go b/curseforge/detect.go new file mode 100644 index 0000000..988d8ad --- /dev/null +++ b/curseforge/detect.go @@ -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 +} diff --git a/curseforge/request.go b/curseforge/request.go index 5c879e3..a4ac36a 100644 --- a/curseforge/request.go +++ b/curseforge/request.go @@ -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 +} diff --git a/go.mod b/go.mod index 3071e0d..2354758 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 43779f9..fecf2e3 100644 --- a/go.sum +++ b/go.sum @@ -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=