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=