Fabric filtering and native export for CurseForge

This commit is contained in:
comp500 2021-06-15 03:09:49 +01:00
parent fd94e671cd
commit 896b9548de
11 changed files with 107 additions and 171 deletions

View File

@ -72,7 +72,7 @@ var updateCmd = &cobra.Command{
updaterPointerMap := make(map[string][]*core.Mod) updaterPointerMap := make(map[string][]*core.Mod)
updaterCachedStateMap := make(map[string][]interface{}) updaterCachedStateMap := make(map[string][]interface{})
for k, v := range updaterMap { 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 { if err != nil {
// TODO: do we return err code 1? // TODO: do we return err code 1?
fmt.Println(err.Error()) fmt.Println(err.Error())
@ -159,7 +159,7 @@ var updateCmd = &cobra.Command{
} }
updaterFound = true updaterFound = true
check, err := updater.CheckUpdate([]core.Mod{modData}, mcVersion) check, err := updater.CheckUpdate([]core.Mod{modData}, mcVersion, pack)
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
os.Exit(1) os.Exit(1)

View File

@ -10,7 +10,7 @@ type Updater interface {
ParseUpdate(map[string]interface{}) (interface{}, error) 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, // 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 // 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, // 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 // given pointers to Mods and the value of CachedState for each mod
DoUpdate([]*Mod, []interface{}) error DoUpdate([]*Mod, []interface{}) error

View File

@ -9,7 +9,7 @@ import (
) )
func getCurseDir() (string, error) { func getCurseDir() (string, error) {
path, err := windows.KnownFolderPath(windows.FOLDERID_Documents, 0) path, err := windows.KnownFolderPath(windows.FOLDERID_Profile, 0)
if err != nil { if err != nil {
return "", err return "", err
} }

View File

@ -202,6 +202,53 @@ func createModFile(modInfo modInfo, fileInfo modFileInfo, index *core.Index) err
return index.RefreshFileWithHash(path, format, hash, true) 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 { func matchGameVersion(mcVersion string, modMcVersion string) bool {
if getCurseforgeVersion(mcVersion) == modMcVersion { if getCurseforgeVersion(mcVersion) == modMcVersion {
return true return true
@ -257,7 +304,7 @@ type cachedStateStore struct {
fileInfo modFileInfo 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)) results := make([]core.UpdateCheck, len(mods))
modIDs := make([]int, len(mods)) modIDs := make([]int, len(mods))
modInfos := make([]modInfo, 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 { for i, v := range mods {
projectRaw, ok := v.GetParsedUpdateData("curseforge") projectRaw, ok := v.GetParsedUpdateData("curseforge")
if !ok { 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 snapshots, curseforge doesn't put them in GameVersionLatestFiles
for _, v := range modInfos[i].LatestFiles { for _, v := range modInfos[i].LatestFiles {
// Choose "newest" version by largest ID // 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 updateAvailable = true
fileID = v.ID fileID = v.ID
fileInfoData = v fileInfoData = v
@ -315,7 +364,7 @@ func (u cfUpdater) CheckUpdate(mods []core.Mod, mcVersion string) ([]core.Update
// TODO: change to timestamp-based comparison?? // TODO: change to timestamp-based comparison??
// TODO: manage alpha/beta/release correctly, check update channel? // TODO: manage alpha/beta/release correctly, check update channel?
// Choose "newest" version by largest ID // 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 updateAvailable = true
fileID = file.ID fileID = file.ID
fileName = file.Name fileName = file.Name
@ -380,9 +429,6 @@ func (u cfUpdater) DoUpdate(mods []*core.Mod, cachedState []interface{}) error {
type cfExportData struct { type cfExportData struct {
ProjectID int `mapstructure:"project-id"` ProjectID int `mapstructure:"project-id"`
DisableJumploader bool `mapstructure:"disable-jumploader"`
JumploaderForgeVersion string `mapstructure:"jumploader-forge-version"`
JumploaderFileID int `mapstructure:"jumploader-version-id"`
} }
func (e cfExportData) ToMap() (map[string]interface{}, error) { func (e cfExportData) ToMap() (map[string]interface{}, error) {

View File

@ -3,7 +3,6 @@ package curseforge
import ( import (
"archive/zip" "archive/zip"
"bufio" "bufio"
"encoding/json"
"fmt" "fmt"
"github.com/comp500/packwiz/curseforge/packinterop" "github.com/comp500/packwiz/curseforge/packinterop"
"github.com/spf13/viper" "github.com/spf13/viper"
@ -104,8 +103,6 @@ var exportCmd = &cobra.Command{
} }
cfFileRefs := make([]packinterop.AddonFileReference, 0, len(mods)) cfFileRefs := make([]packinterop.AddonFileReference, 0, len(mods))
jumploaderIncluded := false
jumploaderProjectID := 361988
for _, mod := range mods { for _, mod := range mods {
projectRaw, ok := mod.GetParsedUpdateData("curseforge") projectRaw, ok := mod.GetParsedUpdateData("curseforge")
// If the mod has curseforge metadata, add it to cfFileRefs // If the mod has curseforge metadata, add it to cfFileRefs
@ -117,9 +114,6 @@ var exportCmd = &cobra.Command{
FileID: p.FileID, FileID: p.FileID,
OptionalDisabled: mod.Option != nil && mod.Option.Optional && !mod.Option.Default, OptionalDisabled: mod.Option != nil && mod.Option.Optional && !mod.Option.Default,
}) })
if p.ProjectID == jumploaderProjectID {
jumploaderIncluded = true
}
} else { } else {
// If the mod doesn't have the metadata, save it into the zip // If the mod doesn't have the metadata, save it into the zip
path, err := filepath.Rel(filepath.Dir(indexPath), mod.GetDestFilePath()) 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") manifestFile, err := exp.Create("manifest.json")
if err != nil { if err != nil {
_ = exp.Close() _ = exp.Close()
@ -227,7 +145,7 @@ var exportCmd = &cobra.Command{
os.Exit(1) os.Exit(1)
} }
err = packinterop.WriteManifestFromPack(pack, cfFileRefs, exportData.ProjectID, exportData.JumploaderForgeVersion, manifestFile) err = packinterop.WriteManifestFromPack(pack, cfFileRefs, exportData.ProjectID, manifestFile)
if err != nil { if err != nil {
_ = exp.Close() _ = exp.Close()
_ = expFile.Close() _ = expFile.Close()
@ -321,39 +239,6 @@ func createModlist(zw *zip.Writer, mods []core.Mod) error {
return w.Flush() 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 { func loadMods(index core.Index) []core.Mod {
modPaths := index.GetAllMods() modPaths := index.GetAllMods()
mods := make([]core.Mod, len(modPaths)) mods := make([]core.Mod, len(modPaths))

View File

@ -261,13 +261,8 @@ var importCmd = &cobra.Command{
successes++ successes++
continue continue
} }
if v.Name() == "minecraftinstance.json" { if v.Name() == "manifest.json" || v.Name() == "minecraftinstance.json" || v.Name() == ".curseclient" {
fmt.Println("Ignored file \"minecraftinstance.json\"") fmt.Printf("Ignored file \"%s\"\n", v.Name())
successes++
continue
}
if v.Name() == "manifest.json" {
fmt.Println("Ignored file \"manifest.json\"")
successes++ successes++
continue continue
} }

View File

@ -79,7 +79,7 @@ var installCmd = &cobra.Command{
if !done { if !done {
var cancelled bool var cancelled bool
cancelled, modInfoData = searchCurseforgeInternal(args, mcVersion) cancelled, modInfoData = searchCurseforgeInternal(args, mcVersion, getLoader(pack))
if cancelled { if cancelled {
return return
} }
@ -106,7 +106,7 @@ var installCmd = &cobra.Command{
} }
var fileInfoData modFileInfo var fileInfoData modFileInfo
fileInfoData, err = getLatestFile(modInfoData, mcVersion, fileID) fileInfoData, err = getLatestFile(modInfoData, mcVersion, fileID, getLoader(pack))
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
os.Exit(1) os.Exit(1)
@ -175,7 +175,7 @@ var installCmd = &cobra.Command{
depIDPendingQueue = depIDPendingQueue[:0] depIDPendingQueue = depIDPendingQueue[:0]
for _, currData := range depInfoData { for _, currData := range depInfoData {
depFileInfo, err := getLatestFile(currData, mcVersion, 0) depFileInfo, err := getLatestFile(currData, mcVersion, 0, getLoader(pack))
if err != nil { if err != nil {
fmt.Printf("Error retrieving dependency data: %s\n", err.Error()) fmt.Printf("Error retrieving dependency data: %s\n", err.Error())
continue continue
@ -266,7 +266,7 @@ func (r modResultsList) Len() int {
return len(r) 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...") fmt.Println("Searching CurseForge...")
searchTerm := strings.Join(args, " ") searchTerm := strings.Join(args, " ")
@ -275,7 +275,7 @@ func searchCurseforgeInternal(args []string, mcVersion string) (bool, modInfo) {
if len(viper.GetStringSlice("acceptable-game-versions")) > 0 { if len(viper.GetStringSlice("acceptable-game-versions")) > 0 {
filterGameVersion = "" filterGameVersion = ""
} }
results, err := getSearch(searchTerm, filterGameVersion) results, err := getSearch(searchTerm, filterGameVersion, packLoaderType)
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
os.Exit(1) os.Exit(1)
@ -333,16 +333,7 @@ func searchCurseforgeInternal(args []string, mcVersion string) (bool, modInfo) {
} }
} }
func sliceContainsString(slice []string, elem string) bool { func getLatestFile(modInfoData modInfo, mcVersion string, fileID int, packLoaderType int) (modFileInfo, error) {
for _, a := range slice {
if a == elem {
return true
}
}
return false
}
func getLatestFile(modInfoData modInfo, mcVersion string, fileID int) (modFileInfo, error) {
// For snapshots, curseforge doesn't put them in GameVersionLatestFiles // For snapshots, curseforge doesn't put them in GameVersionLatestFiles
if fileID == 0 { if fileID == 0 {
var fileInfoData modFileInfo var fileInfoData modFileInfo
@ -350,7 +341,7 @@ func getLatestFile(modInfoData modInfo, mcVersion string, fileID int) (modFileIn
for _, v := range modInfoData.LatestFiles { for _, v := range modInfoData.LatestFiles {
// Choose "newest" version by largest ID // 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 fileID = v.ID
fileInfoData = v fileInfoData = v
fileInfoObtained = true fileInfoObtained = true
@ -359,7 +350,7 @@ func getLatestFile(modInfoData modInfo, mcVersion string, fileID int) (modFileIn
// TODO: change to timestamp-based comparison?? // TODO: change to timestamp-based comparison??
for _, v := range modInfoData.GameVersionLatestFiles { for _, v := range modInfoData.GameVersionLatestFiles {
// Choose "newest" version by largest ID // 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 fileID = v.ID
fileInfoObtained = false // Make sure we get the file info 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 { 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) fileInfoData, err := getFileInfo(modInfoData.ID, fileID)

View File

@ -45,6 +45,14 @@ func (m twitchInstalledPackMeta) Versions() map[string]string {
} }
// Remove the minecraft version prefix, if it exists // Remove the minecraft version prefix, if it exists
vers["forge"] = strings.TrimPrefix(vers["forge"], m.MCVersion+"-") 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 return vers
} }

View File

@ -69,7 +69,7 @@ type AddonFileReference struct {
OptionalDisabled bool 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 { files := make([]struct {
ProjectID int `json:"projectID"` ProjectID int `json:"projectID"`
FileID int `json:"fileID"` FileID int `json:"fileID"`
@ -84,15 +84,14 @@ func WriteManifestFromPack(pack core.Pack, fileRefs []AddonFileReference, projec
} }
modLoaders := make([]modLoaderDef, 0, 1) modLoaders := make([]modLoaderDef, 0, 1)
forgeVersion, ok := pack.Versions["forge"] if fabricVersion, ok := pack.Versions["fabric"]; ok {
if ok {
modLoaders = append(modLoaders, modLoaderDef{ modLoaders = append(modLoaders, modLoaderDef{
ID: "forge-" + forgeVersion, ID: "fabric-" + fabricVersion,
Primary: true, Primary: true,
}) })
} else if len(jumploaderForgeVersion) > 0 { } else if forgeVersion, ok := pack.Versions["forge"]; ok {
modLoaders = append(modLoaders, modLoaderDef{ modLoaders = append(modLoaders, modLoaderDef{
ID: "forge-" + jumploaderForgeVersion, ID: "forge-" + forgeVersion,
Primary: true, Primary: true,
}) })
} }

View File

@ -114,6 +114,16 @@ const (
dependencyTypeInclude 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) // modInfo is a subset of the deserialised JSON response from the Curse API for mods (addons)
type modInfo struct { type modInfo struct {
Name string `json:"name"` Name string `json:"name"`
@ -128,7 +138,9 @@ type modInfo struct {
ID int `json:"projectFileId"` ID int `json:"projectFileId"`
Name string `json:"projectFileName"` Name string `json:"projectFileName"`
FileType int `json:"fileType"` FileType int `json:"fileType"`
Modloader int `json:"modLoader"`
} `json:"gameVersionLatestFiles"` } `json:"gameVersionLatestFiles"`
ModLoaders []string `json:"modLoaders"`
} }
func getModInfo(modID int) (modInfo, error) { func getModInfo(modID int) (modInfo, error) {
@ -267,19 +279,24 @@ func getFileInfo(modID int, fileID int) (modFileInfo, error) {
return infoRes, nil return infoRes, nil
} }
func getSearch(searchText string, gameVersion string) ([]modInfo, error) { func getSearch(searchText string, gameVersion string, modloaderType int) ([]modInfo, error) {
var infoRes []modInfo var infoRes []modInfo
client := &http.Client{} client := &http.Client{}
textEscaped := url.QueryEscape(searchText) reqURL, err := url.Parse("https://addons-ecs.forgesvc.net/api/v2/addon/search?gameId=432&pageSize=10&categoryId=0&sectionId=6")
var reqURL string if err != nil {
return []modInfo{}, err
}
reqURL.Query().Set("searchFilter", searchText)
if len(gameVersion) > 0 { if len(gameVersion) > 0 {
reqURL = "https://addons-ecs.forgesvc.net/api/v2/addon/search?gameId=432&pageSize=10&categoryId=0&sectionId=6&searchFilter=" + textEscaped + "&gameVersion=" + gameVersion reqURL.Query().Set("gameVersion", gameVersion)
} else { }
reqURL = "https://addons-ecs.forgesvc.net/api/v2/addon/search?gameId=432&pageSize=10&categoryId=0&sectionId=6&searchFilter=" + textEscaped 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 { if err != nil {
return []modInfo{}, err return []modInfo{}, err
} }

View File

@ -31,14 +31,9 @@ type cachedStateStore struct {
Version Version 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)) results := make([]core.UpdateCheck, len(mods))
pack, err := core.LoadPack()
if err != nil {
return results, err
}
for i, mod := range mods { for i, mod := range mods {
rawData, ok := mod.GetParsedUpdateData("modrinth") rawData, ok := mod.GetParsedUpdateData("modrinth")
if !ok { if !ok {