diff --git a/cmd/init.go b/cmd/init.go index 68a9dba..8f607d7 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -3,8 +3,6 @@ package cmd import ( "bufio" "encoding/json" - "encoding/xml" - "errors" "fmt" "io/ioutil" "net/http" @@ -89,13 +87,13 @@ var initCmd = &cobra.Command{ if len(modLoaderName) == 0 { modLoaderName = initReadValue("Mod loader [fabric]: ", "fabric") } - _, ok := modLoaders[modLoaderName] + _, ok := core.ModLoaders[modLoaderName] if modLoaderName != "none" && !ok { fmt.Println("Given mod loader is not supported! Use \"none\" to specify no modloader, or to configure one manually.") fmt.Print("The following mod loaders are supported: ") - keys := make([]string, len(modLoaders)) + keys := make([]string, len(core.ModLoaders)) i := 0 - for k := range modLoaders { + for k := range core.ModLoaders { keys[i] = k i++ } @@ -105,7 +103,7 @@ var initCmd = &cobra.Command{ modLoaderVersions := make(map[string]string) if modLoaderName != "none" { - components := modLoaders[modLoaderName] + components := core.ModLoaders[modLoaderName] for _, component := range components { versions, latestVersion, err := component.VersionListGetter(mcVersion) @@ -223,7 +221,7 @@ func init() { _ = viper.BindPFlag("init.modloader", initCmd.Flags().Lookup("modloader")) // ok this is epic - for _, loader := range modLoaders { + for _, loader := range core.ModLoaders { for _, component := range loader { initCmd.Flags().String(component.Name+"-version", "", "The "+component.FriendlyName+" version to use (omit to define interactively)") _ = viper.BindPFlag("init."+component.Name+"-version", initCmd.Flags().Lookup(component.Name+"-version")) @@ -289,111 +287,3 @@ func getValidMCVersions() (mcVersionManifest, error) { }) return out, nil } - -type mavenMetadata struct { - XMLName xml.Name `xml:"metadata"` - GroupID string `xml:"groupId"` - ArtifactID string `xml:"artifactId"` - Versioning struct { - Release string `xml:"release"` - Versions struct { - Version []string `xml:"version"` - } `xml:"versions"` - LastUpdated string `xml:"lastUpdated"` - } `xml:"versioning"` -} - -type modLoaderComponent struct { - Name string - FriendlyName string - VersionListGetter func(mcVersion string) ([]string, string, error) -} - -var modLoaders = map[string][]modLoaderComponent{ - "fabric": { - { - Name: "fabric", - FriendlyName: "Fabric loader", - VersionListGetter: fetchMavenVersionList("https://maven.fabricmc.net/net/fabricmc/fabric-loader/maven-metadata.xml"), - }, - // There's no need to specify yarn version - yarn isn't used outside a dev environment, and intermediary corresponds to game version anyway - //{ - // Name: "yarn", - // FriendlyName: "Yarn (mappings)", - // VersionListGetter: fetchMavenVersionPrefixedList("https://maven.fabricmc.net/net/fabricmc/yarn/maven-metadata.xml", "Yarn"), - //}, - }, - "forge": { - { - Name: "forge", - FriendlyName: "Forge", - VersionListGetter: fetchMavenVersionPrefixedListStrip("https://files.minecraftforge.net/maven/net/minecraftforge/forge/maven-metadata.xml", "Forge"), - }, - }, - "liteloader": { - { - Name: "liteloader", - FriendlyName: "LiteLoader", - VersionListGetter: fetchMavenVersionPrefixedList("http://repo.mumfrey.com/content/repositories/snapshots/com/mumfrey/liteloader/maven-metadata.xml", "LiteLoader"), - }, - }, -} - -func fetchMavenVersionList(url string) func(mcVersion string) ([]string, string, error) { - return func(mcVersion string) ([]string, string, error) { - res, err := http.Get(url) - if err != nil { - return []string{}, "", err - } - dec := xml.NewDecoder(res.Body) - out := mavenMetadata{} - err = dec.Decode(&out) - if err != nil { - return []string{}, "", err - } - return out.Versioning.Versions.Version, out.Versioning.Release, nil - } -} - -func fetchMavenVersionPrefixedListStrip(url string, friendlyName string) func(mcVersion string) ([]string, string, error) { - noStrip := fetchMavenVersionPrefixedList(url, friendlyName) - return func(mcVersion string) ([]string, string, error) { - versions, latestVersion, err := noStrip(mcVersion) - if err != nil { - return nil, "", err - } - for k, v := range versions { - versions[k] = strings.TrimPrefix(v, mcVersion+"-") - } - latestVersion = strings.TrimPrefix(latestVersion, mcVersion+"-") - return versions, latestVersion, nil - } -} - -func fetchMavenVersionPrefixedList(url string, friendlyName string) func(mcVersion string) ([]string, string, error) { - return func(mcVersion string) ([]string, string, error) { - res, err := http.Get(url) - if err != nil { - return []string{}, "", err - } - dec := xml.NewDecoder(res.Body) - out := mavenMetadata{} - err = dec.Decode(&out) - if err != nil { - return []string{}, "", err - } - allowedVersions := make([]string, 0, len(out.Versioning.Versions.Version)) - for _, v := range out.Versioning.Versions.Version { - if strings.HasPrefix(v, mcVersion) { - allowedVersions = append(allowedVersions, v) - } - } - if len(allowedVersions) == 0 { - return []string{}, "", errors.New("no " + friendlyName + " versions available for this Minecraft version") - } - if strings.HasPrefix(out.Versioning.Release, mcVersion) { - return allowedVersions, out.Versioning.Release, nil - } - return allowedVersions, allowedVersions[len(allowedVersions)-1], nil - } -} diff --git a/core/versionutil.go b/core/versionutil.go new file mode 100644 index 0000000..e4d63a8 --- /dev/null +++ b/core/versionutil.go @@ -0,0 +1,116 @@ +package core + +import ( + "encoding/xml" + "errors" + "net/http" + "strings" +) + +type MavenMetadata struct { + XMLName xml.Name `xml:"metadata"` + GroupID string `xml:"groupId"` + ArtifactID string `xml:"artifactId"` + Versioning struct { + Release string `xml:"release"` + Versions struct { + Version []string `xml:"version"` + } `xml:"versions"` + LastUpdated string `xml:"lastUpdated"` + } `xml:"versioning"` +} + +type ModLoaderComponent struct { + Name string + FriendlyName string + VersionListGetter func(mcVersion string) ([]string, string, error) +} + +var ModLoaders = map[string][]ModLoaderComponent{ + "fabric": { + { + Name: "fabric", + FriendlyName: "Fabric loader", + VersionListGetter: FetchMavenVersionList("https://maven.fabricmc.net/net/fabricmc/fabric-loader/maven-metadata.xml"), + }, + // There's no need to specify yarn version - yarn isn't used outside a dev environment, and intermediary corresponds to game version anyway + //{ + // Name: "yarn", + // FriendlyName: "Yarn (mappings)", + // VersionListGetter: fetchMavenVersionPrefixedList("https://maven.fabricmc.net/net/fabricmc/yarn/maven-metadata.xml", "Yarn"), + //}, + }, + "forge": { + { + Name: "forge", + FriendlyName: "Forge", + VersionListGetter: FetchMavenVersionPrefixedListStrip("https://files.minecraftforge.net/maven/net/minecraftforge/forge/maven-metadata.xml", "Forge"), + }, + }, + "liteloader": { + { + Name: "liteloader", + FriendlyName: "LiteLoader", + VersionListGetter: FetchMavenVersionPrefixedList("http://repo.mumfrey.com/content/repositories/snapshots/com/mumfrey/liteloader/maven-metadata.xml", "LiteLoader"), + }, + }, +} + +func FetchMavenVersionList(url string) func(mcVersion string) ([]string, string, error) { + return func(mcVersion string) ([]string, string, error) { + res, err := http.Get(url) + if err != nil { + return []string{}, "", err + } + dec := xml.NewDecoder(res.Body) + out := MavenMetadata{} + err = dec.Decode(&out) + if err != nil { + return []string{}, "", err + } + return out.Versioning.Versions.Version, out.Versioning.Release, nil + } +} + +func FetchMavenVersionPrefixedListStrip(url string, friendlyName string) func(mcVersion string) ([]string, string, error) { + noStrip := FetchMavenVersionPrefixedList(url, friendlyName) + return func(mcVersion string) ([]string, string, error) { + versions, latestVersion, err := noStrip(mcVersion) + if err != nil { + return nil, "", err + } + for k, v := range versions { + versions[k] = strings.TrimPrefix(v, mcVersion+"-") + } + latestVersion = strings.TrimPrefix(latestVersion, mcVersion+"-") + return versions, latestVersion, nil + } +} + +func FetchMavenVersionPrefixedList(url string, friendlyName string) func(mcVersion string) ([]string, string, error) { + return func(mcVersion string) ([]string, string, error) { + res, err := http.Get(url) + if err != nil { + return []string{}, "", err + } + dec := xml.NewDecoder(res.Body) + out := MavenMetadata{} + err = dec.Decode(&out) + if err != nil { + return []string{}, "", err + } + allowedVersions := make([]string, 0, len(out.Versioning.Versions.Version)) + for _, v := range out.Versioning.Versions.Version { + if strings.HasPrefix(v, mcVersion) { + allowedVersions = append(allowedVersions, v) + } + } + if len(allowedVersions) == 0 { + return []string{}, "", errors.New("no " + friendlyName + " versions available for this Minecraft version") + } + if strings.HasPrefix(out.Versioning.Release, mcVersion) { + return allowedVersions, out.Versioning.Release, nil + } + return allowedVersions, allowedVersions[len(allowedVersions)-1], nil + } +} diff --git a/curseforge/curseforge.go b/curseforge/curseforge.go index 0f1312b..3dbab0e 100644 --- a/curseforge/curseforge.go +++ b/curseforge/curseforge.go @@ -348,7 +348,10 @@ func (u cfUpdater) DoUpdate(mods []*core.Mod, cachedState []interface{}) error { } 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) { diff --git a/curseforge/export.go b/curseforge/export.go index 693b22c..7eca332 100644 --- a/curseforge/export.go +++ b/curseforge/export.go @@ -3,6 +3,7 @@ package curseforge import ( "archive/zip" "bufio" + "encoding/json" "fmt" "github.com/spf13/viper" "os" @@ -104,6 +105,8 @@ 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 @@ -115,6 +118,9 @@ 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()) @@ -138,6 +144,82 @@ 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() @@ -146,7 +228,7 @@ var exportCmd = &cobra.Command{ os.Exit(1) } - err = packinterop.WriteManifestFromPack(pack, cfFileRefs, exportData.ProjectID, manifestFile) + err = packinterop.WriteManifestFromPack(pack, cfFileRefs, exportData.ProjectID, exportData.JumploaderForgeVersion, manifestFile) if err != nil { _ = exp.Close() _ = expFile.Close() @@ -241,6 +323,39 @@ 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/packinterop/translation.go b/curseforge/packinterop/translation.go index 03b4236..d7e83fc 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, out io.Writer) error { +func WriteManifestFromPack(pack core.Pack, fileRefs []AddonFileReference, projectID int, jumploaderForgeVersion string, out io.Writer) error { files := make([]struct { ProjectID int `json:"projectID"` FileID int `json:"fileID"` @@ -90,6 +90,11 @@ func WriteManifestFromPack(pack core.Pack, fileRefs []AddonFileReference, projec ID: "forge-" + forgeVersion, Primary: true, }) + } else if len(jumploaderForgeVersion) > 0 { + modLoaders = append(modLoaders, modLoaderDef{ + ID: "forge-" + jumploaderForgeVersion, + Primary: true, + }) } manifest := cursePackMeta{