Add Jumploader to Fabric packs for CurseForge automatically (fixes #5)

This commit is contained in:
comp500 2020-11-28 18:10:00 +00:00
parent a87f7b50f0
commit 877c7d1b02
5 changed files with 247 additions and 118 deletions

View File

@ -3,8 +3,6 @@ package cmd
import ( import (
"bufio" "bufio"
"encoding/json" "encoding/json"
"encoding/xml"
"errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
@ -89,13 +87,13 @@ var initCmd = &cobra.Command{
if len(modLoaderName) == 0 { if len(modLoaderName) == 0 {
modLoaderName = initReadValue("Mod loader [fabric]: ", "fabric") modLoaderName = initReadValue("Mod loader [fabric]: ", "fabric")
} }
_, ok := modLoaders[modLoaderName] _, ok := core.ModLoaders[modLoaderName]
if modLoaderName != "none" && !ok { if modLoaderName != "none" && !ok {
fmt.Println("Given mod loader is not supported! Use \"none\" to specify no modloader, or to configure one manually.") 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: ") fmt.Print("The following mod loaders are supported: ")
keys := make([]string, len(modLoaders)) keys := make([]string, len(core.ModLoaders))
i := 0 i := 0
for k := range modLoaders { for k := range core.ModLoaders {
keys[i] = k keys[i] = k
i++ i++
} }
@ -105,7 +103,7 @@ var initCmd = &cobra.Command{
modLoaderVersions := make(map[string]string) modLoaderVersions := make(map[string]string)
if modLoaderName != "none" { if modLoaderName != "none" {
components := modLoaders[modLoaderName] components := core.ModLoaders[modLoaderName]
for _, component := range components { for _, component := range components {
versions, latestVersion, err := component.VersionListGetter(mcVersion) versions, latestVersion, err := component.VersionListGetter(mcVersion)
@ -223,7 +221,7 @@ func init() {
_ = viper.BindPFlag("init.modloader", initCmd.Flags().Lookup("modloader")) _ = viper.BindPFlag("init.modloader", initCmd.Flags().Lookup("modloader"))
// ok this is epic // ok this is epic
for _, loader := range modLoaders { for _, loader := range core.ModLoaders {
for _, component := range loader { for _, component := range loader {
initCmd.Flags().String(component.Name+"-version", "", "The "+component.FriendlyName+" version to use (omit to define interactively)") 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")) _ = viper.BindPFlag("init."+component.Name+"-version", initCmd.Flags().Lookup(component.Name+"-version"))
@ -289,111 +287,3 @@ func getValidMCVersions() (mcVersionManifest, error) {
}) })
return out, nil 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
}
}

116
core/versionutil.go Normal file
View File

@ -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
}
}

View File

@ -349,6 +349,9 @@ 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,6 +3,7 @@ package curseforge
import ( import (
"archive/zip" "archive/zip"
"bufio" "bufio"
"encoding/json"
"fmt" "fmt"
"github.com/spf13/viper" "github.com/spf13/viper"
"os" "os"
@ -104,6 +105,8 @@ 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
@ -115,6 +118,9 @@ 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())
@ -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") manifestFile, err := exp.Create("manifest.json")
if err != nil { if err != nil {
_ = exp.Close() _ = exp.Close()
@ -146,7 +228,7 @@ var exportCmd = &cobra.Command{
os.Exit(1) os.Exit(1)
} }
err = packinterop.WriteManifestFromPack(pack, cfFileRefs, exportData.ProjectID, manifestFile) err = packinterop.WriteManifestFromPack(pack, cfFileRefs, exportData.ProjectID, exportData.JumploaderForgeVersion, manifestFile)
if err != nil { if err != nil {
_ = exp.Close() _ = exp.Close()
_ = expFile.Close() _ = expFile.Close()
@ -241,6 +323,39 @@ 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

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