mirror of
https://github.com/packwiz/packwiz.git
synced 2025-04-19 13:06:30 +02:00
Added support for importing manual files and rehashing where necessary Moved cache folder to "local" user folder Cleaned up messages, saved index after importing
289 lines
7.8 KiB
Go
289 lines
7.8 KiB
Go
package curseforge
|
|
|
|
import (
|
|
"archive/zip"
|
|
"bufio"
|
|
"fmt"
|
|
"github.com/packwiz/packwiz/cmdshared"
|
|
"github.com/packwiz/packwiz/core"
|
|
"github.com/packwiz/packwiz/curseforge/packinterop"
|
|
"github.com/spf13/cobra"
|
|
"github.com/spf13/viper"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
)
|
|
|
|
// exportCmd represents the export command
|
|
var exportCmd = &cobra.Command{
|
|
Use: "export",
|
|
Short: "Export the current modpack into a .zip for curseforge",
|
|
Args: cobra.NoArgs,
|
|
Run: func(cmd *cobra.Command, args []string) {
|
|
side := viper.GetString("curseforge.export.side")
|
|
if len(side) == 0 || (side != core.UniversalSide && side != core.ServerSide && side != core.ClientSide) {
|
|
fmt.Println("Invalid side!")
|
|
os.Exit(1)
|
|
}
|
|
|
|
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)
|
|
}
|
|
// Do a refresh to ensure files are up to date
|
|
err = index.Refresh()
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
os.Exit(1)
|
|
}
|
|
err = index.Write()
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
os.Exit(1)
|
|
}
|
|
err = pack.UpdateIndexHash()
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
os.Exit(1)
|
|
}
|
|
err = pack.Write()
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
// TODO: should index just expose indexPath itself, through a function?
|
|
indexPath := filepath.Join(filepath.Dir(viper.GetString("pack-file")), filepath.FromSlash(pack.Index.File))
|
|
|
|
fmt.Println("Reading external files...")
|
|
mods, err := index.LoadAllMods()
|
|
if err != nil {
|
|
fmt.Printf("Error reading file: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
i := 0
|
|
// Filter mods by side
|
|
// TODO: opt-in optional disabled filtering?
|
|
for _, mod := range mods {
|
|
if len(mod.Side) == 0 || mod.Side == side || mod.Side == "both" || side == "both" {
|
|
mods[i] = mod
|
|
i++
|
|
}
|
|
}
|
|
mods = mods[:i]
|
|
|
|
var exportData cfExportData
|
|
exportDataUnparsed, ok := pack.Export["curseforge"]
|
|
if ok {
|
|
exportData, err = parseExportData(exportDataUnparsed)
|
|
if err != nil {
|
|
fmt.Printf("Failed to parse export metadata: %s\n", err.Error())
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
fileName := viper.GetString("curseforge.export.output")
|
|
if fileName == "" {
|
|
fileName = pack.GetPackName() + ".zip"
|
|
}
|
|
|
|
expFile, err := os.Create(fileName)
|
|
if err != nil {
|
|
fmt.Printf("Failed to create zip: %s\n", err.Error())
|
|
os.Exit(1)
|
|
}
|
|
exp := zip.NewWriter(expFile)
|
|
|
|
// Add an overrides folder even if there are no files to go in it
|
|
_, err = exp.Create("overrides/")
|
|
if err != nil {
|
|
fmt.Printf("Failed to add overrides folder: %s\n", err.Error())
|
|
os.Exit(1)
|
|
}
|
|
|
|
cfFileRefs := make([]packinterop.AddonFileReference, 0, len(mods))
|
|
nonCfMods := make([]*core.Mod, 0)
|
|
for _, mod := range mods {
|
|
projectRaw, ok := mod.GetParsedUpdateData("curseforge")
|
|
// If the mod has curseforge metadata, add it to cfFileRefs
|
|
if ok {
|
|
p := projectRaw.(cfUpdateData)
|
|
cfFileRefs = append(cfFileRefs, packinterop.AddonFileReference{
|
|
ProjectID: p.ProjectID,
|
|
FileID: p.FileID,
|
|
OptionalDisabled: mod.Option != nil && mod.Option.Optional && !mod.Option.Default,
|
|
})
|
|
} else {
|
|
nonCfMods = append(nonCfMods, mod)
|
|
}
|
|
}
|
|
|
|
// Download external files and save directly into the zip
|
|
if len(nonCfMods) > 0 {
|
|
fmt.Printf("Retrieving %v external files to store in the modpack zip...\n", len(nonCfMods))
|
|
fmt.Println("Disclaimer: you are responsible for ensuring you comply with ALL the licenses, or obtain appropriate permissions, for the files listed below")
|
|
fmt.Println("Note that mods bundled within a CurseForge pack must be in the Approved Non-CurseForge Mods list")
|
|
fmt.Println("packwiz is currently unable to match metadata between mod sites - if any of these are available from CurseForge you should change them to use CurseForge metadata (e.g. by reinstalling them using the cf commands)")
|
|
fmt.Println()
|
|
|
|
session, err := core.CreateDownloadSession(nonCfMods, []string{})
|
|
if err != nil {
|
|
fmt.Printf("Error retrieving external files: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
cmdshared.ListManualDownloads(session)
|
|
|
|
for dl := range session.StartDownloads() {
|
|
if dl.Error != nil {
|
|
fmt.Printf("Download of %s (%s) failed: %v\n", dl.Mod.Name, dl.Mod.FileName, dl.Error)
|
|
continue
|
|
}
|
|
for warning := range dl.Warnings {
|
|
fmt.Printf("Warning for %s (%s): %v\n", dl.Mod.Name, dl.Mod.FileName, warning)
|
|
}
|
|
|
|
path, err := filepath.Rel(filepath.Dir(indexPath), dl.Mod.GetDestFilePath())
|
|
if err != nil {
|
|
fmt.Printf("Error resolving mod file: %v\n", err)
|
|
continue
|
|
}
|
|
modFile, err := exp.Create(filepath.ToSlash(filepath.Join("overrides", path)))
|
|
if err != nil {
|
|
fmt.Printf("Error creating mod file %s: %v\n", path, err)
|
|
continue
|
|
}
|
|
_, err = io.Copy(modFile, dl.File)
|
|
if err != nil {
|
|
fmt.Printf("Error copying file %s: %v\n", path, err)
|
|
continue
|
|
}
|
|
|
|
fmt.Printf("%s (%s) added to zip\n", dl.Mod.Name, dl.Mod.FileName)
|
|
}
|
|
|
|
err = session.SaveIndex()
|
|
if err != nil {
|
|
fmt.Printf("Error saving cache index: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
manifestFile, err := exp.Create("manifest.json")
|
|
if err != nil {
|
|
_ = exp.Close()
|
|
_ = expFile.Close()
|
|
fmt.Println("Error creating manifest: " + err.Error())
|
|
os.Exit(1)
|
|
}
|
|
|
|
err = packinterop.WriteManifestFromPack(pack, cfFileRefs, exportData.ProjectID, manifestFile)
|
|
if err != nil {
|
|
_ = exp.Close()
|
|
_ = expFile.Close()
|
|
fmt.Println("Error creating manifest: " + err.Error())
|
|
os.Exit(1)
|
|
}
|
|
|
|
err = createModlist(exp, mods)
|
|
if err != nil {
|
|
_ = exp.Close()
|
|
_ = expFile.Close()
|
|
fmt.Println("Error creating mod list: " + err.Error())
|
|
os.Exit(1)
|
|
}
|
|
|
|
i = 0
|
|
for _, v := range index.Files {
|
|
if !v.MetaFile {
|
|
// Save all non-metadata files into the zip
|
|
path, err := filepath.Rel(filepath.Dir(indexPath), index.GetFilePath(v))
|
|
if err != nil {
|
|
fmt.Printf("Error resolving file: %s\n", err.Error())
|
|
// TODO: exit(1)?
|
|
continue
|
|
}
|
|
file, err := exp.Create(filepath.ToSlash(filepath.Join("overrides", path)))
|
|
if err != nil {
|
|
fmt.Printf("Error creating file: %s\n", err.Error())
|
|
// TODO: exit(1)?
|
|
continue
|
|
}
|
|
err = index.SaveFile(v, file)
|
|
if err != nil {
|
|
fmt.Printf("Error copying file: %s\n", err.Error())
|
|
// TODO: exit(1)?
|
|
continue
|
|
}
|
|
i++
|
|
}
|
|
}
|
|
|
|
err = exp.Close()
|
|
if err != nil {
|
|
fmt.Println("Error writing export file: " + err.Error())
|
|
os.Exit(1)
|
|
}
|
|
err = expFile.Close()
|
|
if err != nil {
|
|
fmt.Println("Error writing export file: " + err.Error())
|
|
os.Exit(1)
|
|
}
|
|
|
|
fmt.Println("Modpack exported to " + fileName)
|
|
},
|
|
}
|
|
|
|
func createModlist(zw *zip.Writer, mods []*core.Mod) error {
|
|
modlistFile, err := zw.Create("modlist.html")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
w := bufio.NewWriter(modlistFile)
|
|
|
|
_, err = w.WriteString("<ul>\r\n")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, mod := range mods {
|
|
projectRaw, ok := mod.GetParsedUpdateData("curseforge")
|
|
if !ok {
|
|
// TODO: read homepage URL or something similar?
|
|
// TODO: how to handle mods that don't have metadata???
|
|
_, err = w.WriteString("<li>" + mod.Name + "</li>\r\n")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
continue
|
|
}
|
|
project := projectRaw.(cfUpdateData)
|
|
_, err = w.WriteString("<li><a href=\"https://www.curseforge.com/projects/" + strconv.Itoa(project.ProjectID) + "\">" + mod.Name + "</a></li>\r\n")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
_, err = w.WriteString("</ul>\r\n")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return w.Flush()
|
|
}
|
|
|
|
func init() {
|
|
curseforgeCmd.AddCommand(exportCmd)
|
|
|
|
exportCmd.Flags().StringP("side", "s", "client", "The side to export mods with")
|
|
_ = viper.BindPFlag("curseforge.export.side", exportCmd.Flags().Lookup("side"))
|
|
exportCmd.Flags().StringP("output", "o", "", "The file to export the modpack to")
|
|
_ = viper.BindPFlag("curseforge.export.output", exportCmd.Flags().Lookup("output"))
|
|
}
|