From 896b9548de7a7d905e7ae0693b44c65b2de85395 Mon Sep 17 00:00:00 2001 From: comp500 Date: Tue, 15 Jun 2021 03:09:49 +0100 Subject: [PATCH] Fabric filtering and native export for CurseForge --- cmd/update.go | 4 +- core/interfaces.go | 2 +- curseforge/cursedir_windows.go | 2 +- curseforge/curseforge.go | 60 ++++++++-- curseforge/export.go | 117 +------------------- curseforge/import.go | 9 +- curseforge/install.go | 27 ++--- curseforge/packinterop/minecraftinstance.go | 8 ++ curseforge/packinterop/translation.go | 11 +- curseforge/request.go | 31 ++++-- modrinth/updater.go | 7 +- 11 files changed, 107 insertions(+), 171 deletions(-) diff --git a/cmd/update.go b/cmd/update.go index ab8a6af..870d2bc 100644 --- a/cmd/update.go +++ b/cmd/update.go @@ -72,7 +72,7 @@ var updateCmd = &cobra.Command{ updaterPointerMap := make(map[string][]*core.Mod) updaterCachedStateMap := make(map[string][]interface{}) for k, v := range updaterMap { - checks, err := core.Updaters[k].CheckUpdate(v, mcVersion) + checks, err := core.Updaters[k].CheckUpdate(v, mcVersion, pack) if err != nil { // TODO: do we return err code 1? fmt.Println(err.Error()) @@ -159,7 +159,7 @@ var updateCmd = &cobra.Command{ } updaterFound = true - check, err := updater.CheckUpdate([]core.Mod{modData}, mcVersion) + check, err := updater.CheckUpdate([]core.Mod{modData}, mcVersion, pack) if err != nil { fmt.Println(err) os.Exit(1) diff --git a/core/interfaces.go b/core/interfaces.go index 6f2ba79..9edc6bd 100644 --- a/core/interfaces.go +++ b/core/interfaces.go @@ -10,7 +10,7 @@ type Updater interface { ParseUpdate(map[string]interface{}) (interface{}, error) // CheckUpdate checks whether there is an update for each of the mods in the given slice, for the given MC version, // called for all of the mods that this updater handles - CheckUpdate([]Mod, string) ([]UpdateCheck, error) + CheckUpdate([]Mod, string, Pack) ([]UpdateCheck, error) // DoUpdate carries out the update previously queried in CheckUpdate, on each Mod's metadata, // given pointers to Mods and the value of CachedState for each mod DoUpdate([]*Mod, []interface{}) error diff --git a/curseforge/cursedir_windows.go b/curseforge/cursedir_windows.go index 0fc373e..3496079 100644 --- a/curseforge/cursedir_windows.go +++ b/curseforge/cursedir_windows.go @@ -9,7 +9,7 @@ import ( ) func getCurseDir() (string, error) { - path, err := windows.KnownFolderPath(windows.FOLDERID_Documents, 0) + path, err := windows.KnownFolderPath(windows.FOLDERID_Profile, 0) if err != nil { return "", err } diff --git a/curseforge/curseforge.go b/curseforge/curseforge.go index dc02b78..d680363 100644 --- a/curseforge/curseforge.go +++ b/curseforge/curseforge.go @@ -202,6 +202,53 @@ func createModFile(modInfo modInfo, fileInfo modFileInfo, index *core.Index) err return index.RefreshFileWithHash(path, format, hash, true) } +func getLoader(pack core.Pack) int { + dependencies := pack.Versions + + _, hasFabric := dependencies["fabric"] + _, hasForge := dependencies["forge"] + if hasFabric && hasForge { + return modloaderTypeAny + } else if hasFabric { + return modloaderTypeFabric + } else if hasForge { + return modloaderTypeForge + } else { + return modloaderTypeAny + } +} + +func matchLoaderType(packLoaderType int, modLoaderType int) bool { + if packLoaderType == modloaderTypeAny || modLoaderType == modloaderTypeAny { + return true + } else { + return packLoaderType == modLoaderType + } +} + +func matchLoaderTypeFileInfo(packLoaderType int, fileInfoData modFileInfo) bool { + if packLoaderType == modloaderTypeAny { + return true + } else { + if packLoaderType == modloaderTypeFabric { + for _, v := range fileInfoData.GameVersions { + if v == "Fabric" { + return true + } + } + } else if packLoaderType == modloaderTypeForge { + for _, v := range fileInfoData.GameVersions { + if v == "Forge" { + return true + } + } + } else { + return true + } + return false + } +} + func matchGameVersion(mcVersion string, modMcVersion string) bool { if getCurseforgeVersion(mcVersion) == modMcVersion { return true @@ -257,7 +304,7 @@ type cachedStateStore struct { fileInfo modFileInfo } -func (u cfUpdater) CheckUpdate(mods []core.Mod, mcVersion string) ([]core.UpdateCheck, error) { +func (u cfUpdater) CheckUpdate(mods []core.Mod, mcVersion string, pack core.Pack) ([]core.UpdateCheck, error) { results := make([]core.UpdateCheck, len(mods)) modIDs := make([]int, len(mods)) modInfos := make([]modInfo, len(mods)) @@ -285,6 +332,8 @@ func (u cfUpdater) CheckUpdate(mods []core.Mod, mcVersion string) ([]core.Update } } + packLoaderType := getLoader(pack) + for i, v := range mods { projectRaw, ok := v.GetParsedUpdateData("curseforge") if !ok { @@ -302,7 +351,7 @@ func (u cfUpdater) CheckUpdate(mods []core.Mod, mcVersion string) ([]core.Update // For snapshots, curseforge doesn't put them in GameVersionLatestFiles for _, v := range modInfos[i].LatestFiles { // Choose "newest" version by largest ID - if matchGameVersions(mcVersion, v.GameVersions) && v.ID > fileID { + if matchGameVersions(mcVersion, v.GameVersions) && v.ID > fileID && matchLoaderTypeFileInfo(packLoaderType, v) { updateAvailable = true fileID = v.ID fileInfoData = v @@ -315,7 +364,7 @@ func (u cfUpdater) CheckUpdate(mods []core.Mod, mcVersion string) ([]core.Update // TODO: change to timestamp-based comparison?? // TODO: manage alpha/beta/release correctly, check update channel? // Choose "newest" version by largest ID - if matchGameVersion(mcVersion, file.GameVersion) && file.ID > fileID { + if matchGameVersion(mcVersion, file.GameVersion) && file.ID > fileID && matchLoaderType(packLoaderType, file.Modloader) { updateAvailable = true fileID = file.ID fileName = file.Name @@ -379,10 +428,7 @@ func (u cfUpdater) DoUpdate(mods []*core.Mod, cachedState []interface{}) error { } type cfExportData struct { - ProjectID int `mapstructure:"project-id"` - DisableJumploader bool `mapstructure:"disable-jumploader"` - JumploaderForgeVersion string `mapstructure:"jumploader-forge-version"` - JumploaderFileID int `mapstructure:"jumploader-version-id"` + ProjectID int `mapstructure:"project-id"` } func (e cfExportData) ToMap() (map[string]interface{}, error) { diff --git a/curseforge/export.go b/curseforge/export.go index 273b76e..29299df 100644 --- a/curseforge/export.go +++ b/curseforge/export.go @@ -3,7 +3,6 @@ package curseforge import ( "archive/zip" "bufio" - "encoding/json" "fmt" "github.com/comp500/packwiz/curseforge/packinterop" "github.com/spf13/viper" @@ -104,8 +103,6 @@ var exportCmd = &cobra.Command{ } cfFileRefs := make([]packinterop.AddonFileReference, 0, len(mods)) - jumploaderIncluded := false - jumploaderProjectID := 361988 for _, mod := range mods { projectRaw, ok := mod.GetParsedUpdateData("curseforge") // If the mod has curseforge metadata, add it to cfFileRefs @@ -117,9 +114,6 @@ var exportCmd = &cobra.Command{ FileID: p.FileID, OptionalDisabled: mod.Option != nil && mod.Option.Optional && !mod.Option.Default, }) - if p.ProjectID == jumploaderProjectID { - jumploaderIncluded = true - } } else { // If the mod doesn't have the metadata, save it into the zip path, err := filepath.Rel(filepath.Dir(indexPath), mod.GetDestFilePath()) @@ -143,82 +137,6 @@ var exportCmd = &cobra.Command{ } } - fabricVersion, usingFabric := pack.Versions["fabric"] - dataUpdated := false - - if usingFabric { - if len(fabricVersion) == 0 { - fmt.Println("Invalid version of Fabric found!") - os.Exit(1) - } - - if len(exportData.JumploaderForgeVersion) == 0 { - dataUpdated = true - - // TODO: this code is horrible, I hate it - _, latest, err := core.ModLoaders["forge"][0].VersionListGetter(pack.Versions["minecraft"]) - if err != nil { - fmt.Printf("Failed to get the latest Forge version: %s\n", err) - os.Exit(1) - } - exportData.JumploaderForgeVersion = latest - } - } - - if !jumploaderIncluded && usingFabric && !exportData.DisableJumploader { - fmt.Println("Fabric isn't natively supported by CurseForge, adding Jumploader...") - - if exportData.JumploaderFileID == 0 { - dataUpdated = true - modInfoData, err := getModInfo(jumploaderProjectID) - if err != nil { - fmt.Printf("Failed to fetch Jumploader latest file: %s\n", err) - os.Exit(1) - } - var fileID int - for _, v := range modInfoData.LatestFiles { - // Choose "newest" version by largest ID - if v.ID > fileID { - fileID = v.ID - } - } - if fileID == 0 { - fmt.Printf("Failed to fetch Jumploader latest file: no file found") - os.Exit(1) - } - exportData.JumploaderFileID = fileID - } - - cfFileRefs = append(cfFileRefs, packinterop.AddonFileReference{ - ProjectID: jumploaderProjectID, - FileID: exportData.JumploaderFileID, - OptionalDisabled: false, - }) - - err = createJumploaderConfig(exp, fabricVersion) - if err != nil { - fmt.Printf("Error creating Jumploader config file: %s\n", err.Error()) - os.Exit(1) - } - } - - if dataUpdated { - newMap, err := exportData.ToMap() - if err != nil { - fmt.Printf("Failed to update metadata: %s\n", err) - os.Exit(1) - } - if pack.Export == nil { - pack.Export = make(map[string]map[string]interface{}) - } - pack.Export["curseforge"] = newMap - err = pack.Write() - if err != nil { - fmt.Println(err) - return - } - } - manifestFile, err := exp.Create("manifest.json") if err != nil { _ = exp.Close() @@ -227,7 +145,7 @@ var exportCmd = &cobra.Command{ os.Exit(1) } - err = packinterop.WriteManifestFromPack(pack, cfFileRefs, exportData.ProjectID, exportData.JumploaderForgeVersion, manifestFile) + err = packinterop.WriteManifestFromPack(pack, cfFileRefs, exportData.ProjectID, manifestFile) if err != nil { _ = exp.Close() _ = expFile.Close() @@ -321,39 +239,6 @@ func createModlist(zw *zip.Writer, mods []core.Mod) error { return w.Flush() } -type jumploaderConfig struct { - ConfigVersion int `json:"configVersion"` - Sources []string `json:"sources"` - GameVersion string `json:"gameVersion"` - GameSide string `json:"gameSide"` - DisableUI bool `json:"disableUI"` - LoadJarsFromFolder interface{} `json:"loadJarsFromFolder"` - OverrideMainClass interface{} `json:"overrideMainClass"` - PinFabricLoaderVersion string `json:"pinFabricLoaderVersion"` -} - -func createJumploaderConfig(zw *zip.Writer, loaderVersion string) error { - jumploaderConfigFile, err := zw.Create("overrides/config/jumploader.json") - if err != nil { - return err - } - - j := jumploaderConfig{ - ConfigVersion: 2, - Sources: []string{"minecraft", "fabric"}, - GameVersion: "current", - GameSide: "current", - DisableUI: false, - LoadJarsFromFolder: nil, - OverrideMainClass: nil, - PinFabricLoaderVersion: loaderVersion, - } - - w := json.NewEncoder(jumploaderConfigFile) - w.SetIndent("", " ") // Match CF export - return w.Encode(j) -} - func loadMods(index core.Index) []core.Mod { modPaths := index.GetAllMods() mods := make([]core.Mod, len(modPaths)) diff --git a/curseforge/import.go b/curseforge/import.go index fc4c330..0216ff7 100644 --- a/curseforge/import.go +++ b/curseforge/import.go @@ -261,13 +261,8 @@ var importCmd = &cobra.Command{ successes++ continue } - if v.Name() == "minecraftinstance.json" { - fmt.Println("Ignored file \"minecraftinstance.json\"") - successes++ - continue - } - if v.Name() == "manifest.json" { - fmt.Println("Ignored file \"manifest.json\"") + if v.Name() == "manifest.json" || v.Name() == "minecraftinstance.json" || v.Name() == ".curseclient" { + fmt.Printf("Ignored file \"%s\"\n", v.Name()) successes++ continue } diff --git a/curseforge/install.go b/curseforge/install.go index 2c10607..93eb8a8 100644 --- a/curseforge/install.go +++ b/curseforge/install.go @@ -79,7 +79,7 @@ var installCmd = &cobra.Command{ if !done { var cancelled bool - cancelled, modInfoData = searchCurseforgeInternal(args, mcVersion) + cancelled, modInfoData = searchCurseforgeInternal(args, mcVersion, getLoader(pack)) if cancelled { return } @@ -106,7 +106,7 @@ var installCmd = &cobra.Command{ } var fileInfoData modFileInfo - fileInfoData, err = getLatestFile(modInfoData, mcVersion, fileID) + fileInfoData, err = getLatestFile(modInfoData, mcVersion, fileID, getLoader(pack)) if err != nil { fmt.Println(err) os.Exit(1) @@ -175,7 +175,7 @@ var installCmd = &cobra.Command{ depIDPendingQueue = depIDPendingQueue[:0] for _, currData := range depInfoData { - depFileInfo, err := getLatestFile(currData, mcVersion, 0) + depFileInfo, err := getLatestFile(currData, mcVersion, 0, getLoader(pack)) if err != nil { fmt.Printf("Error retrieving dependency data: %s\n", err.Error()) continue @@ -266,7 +266,7 @@ func (r modResultsList) Len() int { return len(r) } -func searchCurseforgeInternal(args []string, mcVersion string) (bool, modInfo) { +func searchCurseforgeInternal(args []string, mcVersion string, packLoaderType int) (bool, modInfo) { fmt.Println("Searching CurseForge...") searchTerm := strings.Join(args, " ") @@ -275,7 +275,7 @@ func searchCurseforgeInternal(args []string, mcVersion string) (bool, modInfo) { if len(viper.GetStringSlice("acceptable-game-versions")) > 0 { filterGameVersion = "" } - results, err := getSearch(searchTerm, filterGameVersion) + results, err := getSearch(searchTerm, filterGameVersion, packLoaderType) if err != nil { fmt.Println(err) os.Exit(1) @@ -333,16 +333,7 @@ func searchCurseforgeInternal(args []string, mcVersion string) (bool, modInfo) { } } -func sliceContainsString(slice []string, elem string) bool { - for _, a := range slice { - if a == elem { - return true - } - } - return false -} - -func getLatestFile(modInfoData modInfo, mcVersion string, fileID int) (modFileInfo, error) { +func getLatestFile(modInfoData modInfo, mcVersion string, fileID int, packLoaderType int) (modFileInfo, error) { // For snapshots, curseforge doesn't put them in GameVersionLatestFiles if fileID == 0 { var fileInfoData modFileInfo @@ -350,7 +341,7 @@ func getLatestFile(modInfoData modInfo, mcVersion string, fileID int) (modFileIn for _, v := range modInfoData.LatestFiles { // Choose "newest" version by largest ID - if matchGameVersions(mcVersion, v.GameVersions) && v.ID > fileID { + if matchGameVersions(mcVersion, v.GameVersions) && v.ID > fileID && matchLoaderTypeFileInfo(packLoaderType, v) { fileID = v.ID fileInfoData = v fileInfoObtained = true @@ -359,7 +350,7 @@ func getLatestFile(modInfoData modInfo, mcVersion string, fileID int) (modFileIn // TODO: change to timestamp-based comparison?? for _, v := range modInfoData.GameVersionLatestFiles { // Choose "newest" version by largest ID - if matchGameVersion(mcVersion, v.GameVersion) && v.ID > fileID { + if matchGameVersion(mcVersion, v.GameVersion) && v.ID > fileID && matchLoaderType(packLoaderType, v.Modloader) { fileID = v.ID fileInfoObtained = false // Make sure we get the file info } @@ -370,7 +361,7 @@ func getLatestFile(modInfoData modInfo, mcVersion string, fileID int) (modFileIn } if fileID == 0 { - return modFileInfo{}, errors.New("mod not available for the configured Minecraft version(s) (use the acceptable-game-versions option to accept more)") + return modFileInfo{}, errors.New("mod not available for the configured Minecraft version(s) (use the acceptable-game-versions option to accept more) or loader") } fileInfoData, err := getFileInfo(modInfoData.ID, fileID) diff --git a/curseforge/packinterop/minecraftinstance.go b/curseforge/packinterop/minecraftinstance.go index 1bd6c8f..f384ee7 100644 --- a/curseforge/packinterop/minecraftinstance.go +++ b/curseforge/packinterop/minecraftinstance.go @@ -45,6 +45,14 @@ func (m twitchInstalledPackMeta) Versions() map[string]string { } // Remove the minecraft version prefix, if it exists vers["forge"] = strings.TrimPrefix(vers["forge"], m.MCVersion+"-") + } else if strings.HasPrefix(m.Modloader.Name, "fabric") { + if len(m.Modloader.MavenVersionString) > 0 { + vers["fabric"] = strings.TrimPrefix(m.Modloader.MavenVersionString, "net.fabricmc:fabric-loader:") + } else { + vers["fabric"] = strings.TrimPrefix(m.Modloader.Name, "fabric-") + } + // Remove the minecraft version suffix, if it exists + vers["fabric"] = strings.TrimSuffix(vers["fabric"], m.MCVersion+"-") } return vers } diff --git a/curseforge/packinterop/translation.go b/curseforge/packinterop/translation.go index d7e83fc..0664a37 100644 --- a/curseforge/packinterop/translation.go +++ b/curseforge/packinterop/translation.go @@ -69,7 +69,7 @@ type AddonFileReference struct { OptionalDisabled bool } -func WriteManifestFromPack(pack core.Pack, fileRefs []AddonFileReference, projectID int, jumploaderForgeVersion string, out io.Writer) error { +func WriteManifestFromPack(pack core.Pack, fileRefs []AddonFileReference, projectID int, out io.Writer) error { files := make([]struct { ProjectID int `json:"projectID"` FileID int `json:"fileID"` @@ -84,15 +84,14 @@ func WriteManifestFromPack(pack core.Pack, fileRefs []AddonFileReference, projec } modLoaders := make([]modLoaderDef, 0, 1) - forgeVersion, ok := pack.Versions["forge"] - if ok { + if fabricVersion, ok := pack.Versions["fabric"]; ok { modLoaders = append(modLoaders, modLoaderDef{ - ID: "forge-" + forgeVersion, + ID: "fabric-" + fabricVersion, Primary: true, }) - } else if len(jumploaderForgeVersion) > 0 { + } else if forgeVersion, ok := pack.Versions["forge"]; ok { modLoaders = append(modLoaders, modLoaderDef{ - ID: "forge-" + jumploaderForgeVersion, + ID: "forge-" + forgeVersion, Primary: true, }) } diff --git a/curseforge/request.go b/curseforge/request.go index 288c9a1..d025f3a 100644 --- a/curseforge/request.go +++ b/curseforge/request.go @@ -114,6 +114,16 @@ const ( dependencyTypeInclude ) +//noinspection GoUnusedConst +const ( + // modloaderTypeAny should not be passed to the API - it does not work + modloaderTypeAny int = iota + modloaderTypeForge + modloaderTypeCauldron + modloaderTypeLiteloader + modloaderTypeFabric +) + // modInfo is a subset of the deserialised JSON response from the Curse API for mods (addons) type modInfo struct { Name string `json:"name"` @@ -128,7 +138,9 @@ type modInfo struct { ID int `json:"projectFileId"` Name string `json:"projectFileName"` FileType int `json:"fileType"` + Modloader int `json:"modLoader"` } `json:"gameVersionLatestFiles"` + ModLoaders []string `json:"modLoaders"` } func getModInfo(modID int) (modInfo, error) { @@ -267,19 +279,24 @@ func getFileInfo(modID int, fileID int) (modFileInfo, error) { return infoRes, nil } -func getSearch(searchText string, gameVersion string) ([]modInfo, error) { +func getSearch(searchText string, gameVersion string, modloaderType int) ([]modInfo, error) { var infoRes []modInfo client := &http.Client{} - textEscaped := url.QueryEscape(searchText) - var reqURL string + reqURL, err := url.Parse("https://addons-ecs.forgesvc.net/api/v2/addon/search?gameId=432&pageSize=10&categoryId=0§ionId=6") + if err != nil { + return []modInfo{}, err + } + reqURL.Query().Set("searchFilter", searchText) + if len(gameVersion) > 0 { - reqURL = "https://addons-ecs.forgesvc.net/api/v2/addon/search?gameId=432&pageSize=10&categoryId=0§ionId=6&searchFilter=" + textEscaped + "&gameVersion=" + gameVersion - } else { - reqURL = "https://addons-ecs.forgesvc.net/api/v2/addon/search?gameId=432&pageSize=10&categoryId=0§ionId=6&searchFilter=" + textEscaped + reqURL.Query().Set("gameVersion", gameVersion) + } + if modloaderType != modloaderTypeAny { + reqURL.Query().Set("modLoaderType", strconv.Itoa(modloaderType)) } - req, err := http.NewRequest("GET", reqURL, nil) + req, err := http.NewRequest("GET", reqURL.String(), nil) if err != nil { return []modInfo{}, err } diff --git a/modrinth/updater.go b/modrinth/updater.go index a1cd242..379f324 100644 --- a/modrinth/updater.go +++ b/modrinth/updater.go @@ -31,14 +31,9 @@ type cachedStateStore struct { Version Version } -func (u mrUpdater) CheckUpdate(mods []core.Mod, mcVersion string) ([]core.UpdateCheck, error) { +func (u mrUpdater) CheckUpdate(mods []core.Mod, mcVersion string, pack core.Pack) ([]core.UpdateCheck, error) { results := make([]core.UpdateCheck, len(mods)) - pack, err := core.LoadPack() - if err != nil { - return results, err - } - for i, mod := range mods { rawData, ok := mod.GetParsedUpdateData("modrinth") if !ok {