mirror of
https://github.com/packwiz/packwiz.git
synced 2025-04-19 21:16:30 +02:00
Implement pack importing/exporting for downloaded Curseforge packs
Abstract out hash implementations Implement file saving/downloading
This commit is contained in:
parent
73f6184b3d
commit
5dfe23e51d
23
core/hash.go
Normal file
23
core/hash.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/sha512"
|
||||||
|
"errors"
|
||||||
|
"hash"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetHashImpl gets an implementation of hash.Hash for the given hash type string
|
||||||
|
func GetHashImpl(hashType string) (hash.Hash, error) {
|
||||||
|
switch hashType {
|
||||||
|
case "sha256":
|
||||||
|
return sha256.New(), nil
|
||||||
|
case "sha512":
|
||||||
|
return sha512.New(), nil
|
||||||
|
case "md5":
|
||||||
|
return md5.New(), nil
|
||||||
|
}
|
||||||
|
// TODO: implement murmur2
|
||||||
|
return nil, errors.New("hash implementation not found")
|
||||||
|
}
|
@ -1,8 +1,8 @@
|
|||||||
package core
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/sha256"
|
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@ -130,7 +130,10 @@ func (in *Index) updateFile(path string) error {
|
|||||||
// Hash usage strategy (may change):
|
// Hash usage strategy (may change):
|
||||||
// Just use SHA256, overwrite existing hash regardless of what it is
|
// Just use SHA256, overwrite existing hash regardless of what it is
|
||||||
// May update later to continue using the same hash that was already being used
|
// May update later to continue using the same hash that was already being used
|
||||||
h := sha256.New()
|
h, err := GetHashImpl("sha256")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if _, err := io.Copy(h, f); err != nil {
|
if _, err := io.Copy(h, f); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -310,3 +313,37 @@ func (in Index) GetAllMods() []string {
|
|||||||
}
|
}
|
||||||
return list
|
return list
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetFilePath attempts to get the path of the destination index file as it is stored on disk
|
||||||
|
func (in Index) GetFilePath(f IndexFile) string {
|
||||||
|
return filepath.Join(filepath.Dir(in.indexFile), filepath.FromSlash(f.File))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveFile attempts to read the file from disk
|
||||||
|
func (in Index) SaveFile(f IndexFile, dest io.Writer) error {
|
||||||
|
hashFormat := f.HashFormat
|
||||||
|
if hashFormat == "" {
|
||||||
|
hashFormat = in.HashFormat
|
||||||
|
}
|
||||||
|
src, err := os.Open(in.GetFilePath(f))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h, err := GetHashImpl(hashFormat)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
w := io.MultiWriter(h, dest)
|
||||||
|
_, err = io.Copy(w, src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
calculatedHash := hex.EncodeToString(h.Sum(nil))
|
||||||
|
if calculatedHash != f.Hash {
|
||||||
|
return errors.New("hash of saved file is invalid")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
42
core/mod.go
42
core/mod.go
@ -1,12 +1,13 @@
|
|||||||
package core
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/sha256"
|
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/BurntSushi/toml"
|
"github.com/BurntSushi/toml"
|
||||||
)
|
)
|
||||||
@ -37,6 +38,7 @@ type ModDownload struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// The three possible values of Side (the side that the mod is on) are "server", "client", and "both".
|
// The three possible values of Side (the side that the mod is on) are "server", "client", and "both".
|
||||||
|
//noinspection GoUnusedConst
|
||||||
const (
|
const (
|
||||||
ServerSide = "server"
|
ServerSide = "server"
|
||||||
ClientSide = "client"
|
ClientSide = "client"
|
||||||
@ -88,7 +90,10 @@ func (m Mod) Write() (string, string, error) {
|
|||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
h := sha256.New()
|
h, err := GetHashImpl("sha256")
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
w := io.MultiWriter(h, f)
|
w := io.MultiWriter(h, f)
|
||||||
|
|
||||||
enc := toml.NewEncoder(w)
|
enc := toml.NewEncoder(w)
|
||||||
@ -109,3 +114,36 @@ func (m Mod) GetParsedUpdateData(updaterName string) (interface{}, bool) {
|
|||||||
func (m Mod) GetFilePath() string {
|
func (m Mod) GetFilePath() string {
|
||||||
return m.metaFile
|
return m.metaFile
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetDestFilePath returns the path of the destination file of the mod
|
||||||
|
func (m Mod) GetDestFilePath() string {
|
||||||
|
return filepath.Join(filepath.Dir(m.metaFile), filepath.FromSlash(m.FileName))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DownloadFile attempts to resolve and download the file
|
||||||
|
func (m Mod) DownloadFile(dest io.Writer) error {
|
||||||
|
resp, err := http.Get(m.Download.URL)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if resp.StatusCode != 200 {
|
||||||
|
_ = resp.Body.Close()
|
||||||
|
return errors.New("invalid status code " + strconv.Itoa(resp.StatusCode))
|
||||||
|
}
|
||||||
|
h, err := GetHashImpl(m.Download.HashFormat)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
w := io.MultiWriter(h, dest)
|
||||||
|
_, err = io.Copy(w, resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
calculatedHash := hex.EncodeToString(h.Sum(nil))
|
||||||
|
if calculatedHash != m.Download.Hash {
|
||||||
|
return errors.New("hash of saved file is invalid")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package core
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/sha256"
|
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
@ -65,7 +64,10 @@ func (pack *Pack) UpdateIndexHash() error {
|
|||||||
// Hash usage strategy (may change):
|
// Hash usage strategy (may change):
|
||||||
// Just use SHA256, overwrite existing hash regardless of what it is
|
// Just use SHA256, overwrite existing hash regardless of what it is
|
||||||
// May update later to continue using the same hash that was already being used
|
// May update later to continue using the same hash that was already being used
|
||||||
h := sha256.New()
|
h, err := GetHashImpl("sha256")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if _, err := io.Copy(h, f); err != nil {
|
if _, err := io.Copy(h, f); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,15 @@
|
|||||||
package curseforge
|
package curseforge
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"archive/zip"
|
||||||
|
"bufio"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/spf13/viper"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/comp500/packwiz/curseforge/packinterop"
|
||||||
|
|
||||||
"github.com/comp500/packwiz/core"
|
"github.com/comp500/packwiz/core"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@ -12,7 +19,7 @@ import (
|
|||||||
var exportCmd = &cobra.Command{
|
var exportCmd = &cobra.Command{
|
||||||
Use: "export",
|
Use: "export",
|
||||||
Short: "Export the current modpack into a .zip for curseforge",
|
Short: "Export the current modpack into a .zip for curseforge",
|
||||||
// TODO: argument for file name
|
// TODO: arguments for file name, author? projectID?
|
||||||
Args: cobra.NoArgs,
|
Args: cobra.NoArgs,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
fmt.Println("Loading modpack...")
|
fmt.Println("Loading modpack...")
|
||||||
@ -27,6 +34,155 @@ var exportCmd = &cobra.Command{
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = index
|
// TODO: should index just expose indexPath itself, through a function?
|
||||||
|
indexPath := filepath.Join(filepath.Dir(viper.GetString("pack-file")), filepath.FromSlash(pack.Index.File))
|
||||||
|
|
||||||
|
// TODO: filter mods for optional/server/etc
|
||||||
|
mods := loadMods(index)
|
||||||
|
|
||||||
|
expFile, err := os.Create("export.zip")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
defer expFile.Close()
|
||||||
|
exp := zip.NewWriter(expFile)
|
||||||
|
defer exp.Close()
|
||||||
|
|
||||||
|
cfFileRefs := make([]packinterop.AddonFileReference, 0, len(mods))
|
||||||
|
for _, mod := range mods {
|
||||||
|
projectRaw, ok := mod.GetParsedUpdateData("curseforge")
|
||||||
|
// If the mod has curseforge metadata, add it to cfFileRefs
|
||||||
|
// TODO: how to handle files with CF metadata, but with different download path?
|
||||||
|
if ok {
|
||||||
|
p := projectRaw.(cfUpdateData)
|
||||||
|
cfFileRefs = append(cfFileRefs, packinterop.AddonFileReference{ProjectID: p.ProjectID, FileID: p.FileID})
|
||||||
|
} else {
|
||||||
|
// If the mod doesn't have the metadata, save it into the zip
|
||||||
|
path, err := filepath.Rel(filepath.Dir(indexPath), mod.GetDestFilePath())
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error resolving mod file: %s\n", err.Error())
|
||||||
|
// TODO: exit(1)?
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
modFile, err := exp.Create(filepath.ToSlash(filepath.Join("overrides", path)))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error creating mod file: %s\n", err.Error())
|
||||||
|
// TODO: exit(1)?
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
err = mod.DownloadFile(modFile)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error downloading mod file: %s\n", err.Error())
|
||||||
|
// TODO: exit(1)?
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
manifestFile, err := exp.Create("manifest.json")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error creating manifest: " + err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = packinterop.WriteManifestFromPack(pack, cfFileRefs, manifestFile)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error creating manifest: " + err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = createModlist(exp, mods)
|
||||||
|
if err != nil {
|
||||||
|
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++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Modpack exported to export.zip!")
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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?
|
||||||
|
_, err = w.WriteString("<li>" + mod.Name + "</li>\r\n")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
project := projectRaw.(cfUpdateData)
|
||||||
|
projIDString := strconv.Itoa(project.ProjectID)
|
||||||
|
_, err = w.WriteString("<li><a href=\"https://minecraft.curseforge.com/mc-mods/" + projIDString + "\">" + 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 loadMods(index core.Index) []core.Mod {
|
||||||
|
modPaths := index.GetAllMods()
|
||||||
|
mods := make([]core.Mod, len(modPaths))
|
||||||
|
i := 0
|
||||||
|
fmt.Println("Reading mod files...")
|
||||||
|
for _, v := range modPaths {
|
||||||
|
modData, err := core.LoadMod(v)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error reading mod file: %s\n", err.Error())
|
||||||
|
// TODO: exit(1)?
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
mods[i] = modData
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return mods[:i]
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
curseforgeCmd.AddCommand(exportCmd)
|
||||||
|
}
|
||||||
|
@ -4,9 +4,9 @@ import (
|
|||||||
"archive/zip"
|
"archive/zip"
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/comp500/packwiz/curseforge/packinterop"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
@ -19,43 +19,20 @@ import (
|
|||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO: this file is a mess, I need to refactor it
|
|
||||||
// TODO: test modpack importing before proceeding with further implementation
|
|
||||||
|
|
||||||
type importPackFile interface {
|
|
||||||
Name() string
|
|
||||||
Open() (io.ReadCloser, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type importPackMetadata interface {
|
|
||||||
Name() string
|
|
||||||
Versions() map[string]string
|
|
||||||
Mods() []struct {
|
|
||||||
ModID int
|
|
||||||
FileID int
|
|
||||||
}
|
|
||||||
GetFiles() ([]importPackFile, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type importPackSource interface {
|
|
||||||
GetFile(path string) (importPackFile, error)
|
|
||||||
//TODO: was GetFileList(base string), is it needed?
|
|
||||||
GetFileList() ([]importPackFile, error)
|
|
||||||
GetPackFile() importPackFile
|
|
||||||
}
|
|
||||||
|
|
||||||
// importCmd represents the import command
|
// importCmd represents the import command
|
||||||
var importCmd = &cobra.Command{
|
var importCmd = &cobra.Command{
|
||||||
Use: "import [modpack]",
|
Use: "import [modpack]",
|
||||||
Short: "Import an installed curseforge modpack, from a download URL or a downloaded pack zip, or an installed metadata json file",
|
Short: "Import a curseforge modpack, from a download URL or a downloaded pack zip, or an installed metadata json file",
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
inputFile := args[0]
|
inputFile := args[0]
|
||||||
var packImport importPackMetadata
|
var packImport packinterop.ImportPackMetadata
|
||||||
|
|
||||||
|
// TODO: refactor/extract file checking?
|
||||||
if strings.HasPrefix(inputFile, "http") {
|
if strings.HasPrefix(inputFile, "http") {
|
||||||
fmt.Println("it do be a http doe")
|
// TODO: implement
|
||||||
os.Exit(0)
|
fmt.Println("HTTP not supported (yet)")
|
||||||
|
os.Exit(1)
|
||||||
} else {
|
} else {
|
||||||
// Attempt to read from file
|
// Attempt to read from file
|
||||||
var f *os.File
|
var f *os.File
|
||||||
@ -140,8 +117,7 @@ var importCmd = &cobra.Command{
|
|||||||
// Search the zip for minecraftinstance.json or manifest.json
|
// Search the zip for minecraftinstance.json or manifest.json
|
||||||
var metaFile *zip.File
|
var metaFile *zip.File
|
||||||
for _, v := range zr.File {
|
for _, v := range zr.File {
|
||||||
fileName := filepath.Base(v.Name)
|
if v.Name == "minecraftinstance.json" || v.Name == "manifest.json" {
|
||||||
if fileName == "minecraftinstance.json" || fileName == "manifest.json" {
|
|
||||||
metaFile = v
|
metaFile = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -151,17 +127,9 @@ var importCmd = &cobra.Command{
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
packImport = readMetadata(zipPackSource{
|
packImport = packinterop.ReadMetadata(packinterop.GetZipPackSource(metaFile, zr))
|
||||||
MetaFile: metaFile,
|
|
||||||
Reader: zr,
|
|
||||||
})
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
packImport = readMetadata(diskPackSource{
|
packImport = packinterop.ReadMetadata(packinterop.GetDiskPackSource(buf, filepath.ToSlash(filepath.Base(inputFile)), filepath.Dir(inputFile)))
|
||||||
MetaSource: buf,
|
|
||||||
MetaName: inputFile, // TODO: is this always the correct file?
|
|
||||||
BasePath: filepath.Dir(inputFile),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -255,13 +223,10 @@ var importCmd = &cobra.Command{
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: just use mods-folder directly? does texture pack importing affect this?
|
||||||
ref, err := filepath.Abs(filepath.Join(filepath.Dir(core.ResolveMod(modInfoValue.Slug)), fileInfo.FileName))
|
ref, err := filepath.Abs(filepath.Join(filepath.Dir(core.ResolveMod(modInfoValue.Slug)), fileInfo.FileName))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
referencedModPaths = append(referencedModPaths, ref)
|
referencedModPaths = append(referencedModPaths, ref)
|
||||||
if len(ref) == 0 {
|
|
||||||
fmt.Println(core.ResolveMod(modInfoValue.Slug))
|
|
||||||
fmt.Println(filepath.Dir(core.ResolveMod(modInfoValue.Slug)))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Imported mod \"%s\" successfully!\n", modInfoValue.Name)
|
fmt.Printf("Imported mod \"%s\" successfully!\n", modInfoValue.Name)
|
||||||
@ -280,10 +245,7 @@ var importCmd = &cobra.Command{
|
|||||||
successes = 0
|
successes = 0
|
||||||
indexFolder := filepath.Dir(filepath.Join(filepath.Dir(viper.GetString("pack-file")), filepath.FromSlash(pack.Index.File)))
|
indexFolder := filepath.Dir(filepath.Join(filepath.Dir(viper.GetString("pack-file")), filepath.FromSlash(pack.Index.File)))
|
||||||
for _, v := range filesList {
|
for _, v := range filesList {
|
||||||
filePath := v.Name()
|
filePath := filepath.Join(indexFolder, filepath.FromSlash(v.Name()))
|
||||||
if !filepath.IsAbs(filePath) {
|
|
||||||
filePath = filepath.Join(indexFolder, v.Name())
|
|
||||||
}
|
|
||||||
filePathAbs, err := filepath.Abs(filePath)
|
filePathAbs, err := filepath.Abs(filePath)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
found := false
|
found := false
|
||||||
@ -298,12 +260,12 @@ var importCmd = &cobra.Command{
|
|||||||
successes++
|
successes++
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if filepath.Base(filePathAbs) == "minecraftinstance.json" {
|
if v.Name() == "minecraftinstance.json" {
|
||||||
fmt.Println("Ignored file \"minecraftinstance.json\"")
|
fmt.Println("Ignored file \"minecraftinstance.json\"")
|
||||||
successes++
|
successes++
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if filepath.Base(filePathAbs) == "manifest.json" {
|
if v.Name() == "manifest.json" {
|
||||||
fmt.Println("Ignored file \"manifest.json\"")
|
fmt.Println("Ignored file \"manifest.json\"")
|
||||||
successes++
|
successes++
|
||||||
continue
|
continue
|
||||||
@ -376,240 +338,3 @@ var importCmd = &cobra.Command{
|
|||||||
func init() {
|
func init() {
|
||||||
curseforgeCmd.AddCommand(importCmd)
|
curseforgeCmd.AddCommand(importCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
type diskFile struct {
|
|
||||||
NameInternal string
|
|
||||||
Path string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f diskFile) Name() string {
|
|
||||||
return f.NameInternal
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f diskFile) Open() (io.ReadCloser, error) {
|
|
||||||
return os.Open(f.Path)
|
|
||||||
}
|
|
||||||
|
|
||||||
type zipReaderFile struct {
|
|
||||||
NameInternal string
|
|
||||||
*zip.File
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f zipReaderFile) Name() string {
|
|
||||||
return f.NameInternal
|
|
||||||
}
|
|
||||||
|
|
||||||
type readerFile struct {
|
|
||||||
NameInternal string
|
|
||||||
Reader *io.ReadCloser
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f readerFile) Name() string {
|
|
||||||
return f.NameInternal
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f readerFile) Open() (io.ReadCloser, error) {
|
|
||||||
return *f.Reader, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func diskFilesFromPath(base string) ([]importPackFile, error) {
|
|
||||||
list := make([]importPackFile, 0)
|
|
||||||
err := filepath.Walk(base, func(path string, info os.FileInfo, err error) error {
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if info.IsDir() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
name, err := filepath.Rel(base, path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
list = append(list, diskFile{name, path})
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return list, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type diskPackSource struct {
|
|
||||||
MetaSource *bufio.Reader
|
|
||||||
MetaName string
|
|
||||||
BasePath string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s diskPackSource) GetFile(path string) (importPackFile, error) {
|
|
||||||
return diskFile{s.BasePath, path}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s diskPackSource) GetFileList() ([]importPackFile, error) {
|
|
||||||
return diskFilesFromPath(s.BasePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s diskPackSource) GetPackFile() importPackFile {
|
|
||||||
rc := ioutil.NopCloser(s.MetaSource)
|
|
||||||
return readerFile{s.MetaName, &rc}
|
|
||||||
}
|
|
||||||
|
|
||||||
type zipPackSource struct {
|
|
||||||
MetaFile *zip.File
|
|
||||||
Reader *zip.Reader
|
|
||||||
cachedFileList []importPackFile
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s zipPackSource) GetFile(path string) (importPackFile, error) {
|
|
||||||
if s.cachedFileList == nil {
|
|
||||||
s.cachedFileList = make([]importPackFile, len(s.Reader.File))
|
|
||||||
for i, v := range s.Reader.File {
|
|
||||||
s.cachedFileList[i] = zipReaderFile{v.Name, v}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, v := range s.cachedFileList {
|
|
||||||
if v.Name() == path {
|
|
||||||
return v, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return zipReaderFile{}, errors.New("file not found in zip")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s zipPackSource) GetFileList() ([]importPackFile, error) {
|
|
||||||
if s.cachedFileList == nil {
|
|
||||||
s.cachedFileList = make([]importPackFile, len(s.Reader.File))
|
|
||||||
for i, v := range s.Reader.File {
|
|
||||||
s.cachedFileList[i] = zipReaderFile{v.Name, v}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return s.cachedFileList, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s zipPackSource) GetPackFile() importPackFile {
|
|
||||||
return zipReaderFile{s.MetaFile.Name, s.MetaFile}
|
|
||||||
}
|
|
||||||
|
|
||||||
type twitchInstalledPackMeta struct {
|
|
||||||
NameInternal string `json:"name"`
|
|
||||||
Path string `json:"installPath"`
|
|
||||||
// TODO: javaArgsOverride?
|
|
||||||
// TODO: allocatedMemory?
|
|
||||||
MCVersion string `json:"gameVersion"`
|
|
||||||
Modloader struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
MavenVersionString string `json:"mavenVersionString"`
|
|
||||||
} `json:"baseModLoader"`
|
|
||||||
ModpackOverrides []string `json:"modpackOverrides"`
|
|
||||||
ModsInternal []struct {
|
|
||||||
ID int `json:"addonID"`
|
|
||||||
File struct {
|
|
||||||
// I've given up on using this cached data, just going to re-request it
|
|
||||||
ID int `json:"id"`
|
|
||||||
} `json:"installedFile"`
|
|
||||||
} `json:"installedAddons"`
|
|
||||||
// Used to determine if modpackOverrides should be used or not
|
|
||||||
IsUnlocked bool `json:"isUnlocked"`
|
|
||||||
srcFile string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m twitchInstalledPackMeta) Name() string {
|
|
||||||
return m.NameInternal
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m twitchInstalledPackMeta) Versions() map[string]string {
|
|
||||||
vers := make(map[string]string)
|
|
||||||
vers["minecraft"] = m.MCVersion
|
|
||||||
if strings.HasPrefix(m.Modloader.Name, "forge") {
|
|
||||||
if len(m.Modloader.MavenVersionString) > 0 {
|
|
||||||
vers["forge"] = strings.TrimPrefix(m.Modloader.MavenVersionString, "net.minecraftforge:forge:")
|
|
||||||
} else {
|
|
||||||
vers["forge"] = m.MCVersion + "-" + strings.TrimPrefix(m.Modloader.Name, "forge-")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return vers
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m twitchInstalledPackMeta) Mods() []struct {
|
|
||||||
ModID int
|
|
||||||
FileID int
|
|
||||||
} {
|
|
||||||
list := make([]struct {
|
|
||||||
ModID int
|
|
||||||
FileID int
|
|
||||||
}, len(m.ModsInternal))
|
|
||||||
for i, v := range m.ModsInternal {
|
|
||||||
list[i] = struct {
|
|
||||||
ModID int
|
|
||||||
FileID int
|
|
||||||
}{
|
|
||||||
ModID: v.ID,
|
|
||||||
FileID: v.File.ID,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return list
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m twitchInstalledPackMeta) GetFiles() ([]importPackFile, error) {
|
|
||||||
dir := filepath.Dir(m.srcFile)
|
|
||||||
if _, err := os.Stat(m.Path); err == nil {
|
|
||||||
dir = m.Path
|
|
||||||
}
|
|
||||||
if m.IsUnlocked {
|
|
||||||
return diskFilesFromPath(dir)
|
|
||||||
}
|
|
||||||
list := make([]importPackFile, len(m.ModpackOverrides))
|
|
||||||
for i, v := range m.ModpackOverrides {
|
|
||||||
list[i] = diskFile{
|
|
||||||
Path: filepath.Join(dir, v),
|
|
||||||
NameInternal: v,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return list, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func readMetadata(s importPackSource) importPackMetadata {
|
|
||||||
var packImport importPackMetadata
|
|
||||||
metaFile := s.GetPackFile()
|
|
||||||
rdr, err := metaFile.Open()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error reading file: %s\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the whole file (as we are going to parse it multiple times)
|
|
||||||
fileData, err := ioutil.ReadAll(rdr)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error reading file: %s\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine what format the file is
|
|
||||||
var jsonFile map[string]interface{}
|
|
||||||
err = json.Unmarshal(fileData, &jsonFile)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error parsing JSON: %s\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
isManifest := false
|
|
||||||
if v, ok := jsonFile["manifestType"]; ok {
|
|
||||||
isManifest = v.(string) == "minecraftModpack"
|
|
||||||
}
|
|
||||||
if isManifest {
|
|
||||||
fmt.Println("it do be a manifest doe")
|
|
||||||
os.Exit(0)
|
|
||||||
// TODO: implement manifest parsing
|
|
||||||
} else {
|
|
||||||
// Replace FileNameOnDisk with fileNameOnDisk
|
|
||||||
fileData = bytes.ReplaceAll(fileData, []byte("FileNameOnDisk"), []byte("fileNameOnDisk"))
|
|
||||||
packMeta := twitchInstalledPackMeta{}
|
|
||||||
err = json.Unmarshal(fileData, &packMeta)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error parsing JSON: %s\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
packMeta.srcFile = metaFile.Name()
|
|
||||||
packImport = packMeta
|
|
||||||
}
|
|
||||||
|
|
||||||
return packImport
|
|
||||||
}
|
|
||||||
|
81
curseforge/packinterop/disk.go
Normal file
81
curseforge/packinterop/disk.go
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
package packinterop
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
type diskFile struct {
|
||||||
|
NameInternal string
|
||||||
|
Path string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f diskFile) Name() string {
|
||||||
|
return f.NameInternal
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f diskFile) Open() (io.ReadCloser, error) {
|
||||||
|
return os.Open(f.Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
type readerFile struct {
|
||||||
|
NameInternal string
|
||||||
|
Reader *io.ReadCloser
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f readerFile) Name() string {
|
||||||
|
return f.NameInternal
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f readerFile) Open() (io.ReadCloser, error) {
|
||||||
|
return *f.Reader, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type diskPackSource struct {
|
||||||
|
MetaSource *bufio.Reader
|
||||||
|
MetaName string
|
||||||
|
BasePath string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s diskPackSource) GetFile(path string) (ImportPackFile, error) {
|
||||||
|
return diskFile{path, filepath.Join(s.BasePath, filepath.FromSlash(path))}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s diskPackSource) GetFileList() ([]ImportPackFile, error) {
|
||||||
|
list := make([]ImportPackFile, 0)
|
||||||
|
err := filepath.Walk(s.BasePath, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if info.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Get the name of the file, relative to the pack folder
|
||||||
|
name, err := filepath.Rel(s.BasePath, path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
list = append(list, diskFile{filepath.ToSlash(name), path})
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return list, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s diskPackSource) GetPackFile() ImportPackFile {
|
||||||
|
rc := ioutil.NopCloser(s.MetaSource)
|
||||||
|
return readerFile{s.MetaName, &rc}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetDiskPackSource(metaSource *bufio.Reader, metaName string, basePath string) ImportPackSource {
|
||||||
|
return diskPackSource{
|
||||||
|
MetaSource: metaSource,
|
||||||
|
MetaName: metaName,
|
||||||
|
BasePath: basePath,
|
||||||
|
}
|
||||||
|
}
|
25
curseforge/packinterop/interfaces.go
Normal file
25
curseforge/packinterop/interfaces.go
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
package packinterop
|
||||||
|
|
||||||
|
import "io"
|
||||||
|
|
||||||
|
type ImportPackFile interface {
|
||||||
|
Name() string
|
||||||
|
Open() (io.ReadCloser, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ImportPackMetadata interface {
|
||||||
|
Name() string
|
||||||
|
Versions() map[string]string
|
||||||
|
// TODO: use AddonFileReference?
|
||||||
|
Mods() []struct {
|
||||||
|
ModID int
|
||||||
|
FileID int
|
||||||
|
}
|
||||||
|
GetFiles() ([]ImportPackFile, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ImportPackSource interface {
|
||||||
|
GetFile(path string) (ImportPackFile, error)
|
||||||
|
GetFileList() ([]ImportPackFile, error)
|
||||||
|
GetPackFile() ImportPackFile
|
||||||
|
}
|
104
curseforge/packinterop/manifest.go
Normal file
104
curseforge/packinterop/manifest.go
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
package packinterop
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
type cursePackMeta struct {
|
||||||
|
Minecraft struct {
|
||||||
|
Version string `json:"version"`
|
||||||
|
ModLoaders []modLoaderDef `json:"modLoaders"`
|
||||||
|
} `json:"minecraft"`
|
||||||
|
ManifestType string `json:"manifestType"`
|
||||||
|
ManifestVersion int `json:"manifestVersion"`
|
||||||
|
NameInternal string `json:"name"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
Author string `json:"author"`
|
||||||
|
ProjectID int `json:"projectID"`
|
||||||
|
Files []struct {
|
||||||
|
ProjectID int `json:"projectID"`
|
||||||
|
FileID int `json:"fileID"`
|
||||||
|
Required bool `json:"required"`
|
||||||
|
} `json:"files"`
|
||||||
|
Overrides string `json:"overrides"`
|
||||||
|
importSrc ImportPackSource
|
||||||
|
}
|
||||||
|
|
||||||
|
type modLoaderDef struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Primary bool `json:"primary"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c cursePackMeta) Name() string {
|
||||||
|
return c.NameInternal
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c cursePackMeta) Versions() map[string]string {
|
||||||
|
vers := make(map[string]string)
|
||||||
|
vers["minecraft"] = c.Minecraft.Version
|
||||||
|
for _, v := range c.Minecraft.ModLoaders {
|
||||||
|
// Seperate dash-separated modloader/version pairs
|
||||||
|
parts := strings.SplitN(v.ID, "-", 2)
|
||||||
|
if len(parts) == 2 {
|
||||||
|
vers[parts[0]] = parts[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if val, ok := vers["forge"]; ok {
|
||||||
|
// Remove the minecraft version prefix, if it exists
|
||||||
|
vers["forge"] = strings.TrimPrefix(val, c.Minecraft.Version+"-")
|
||||||
|
}
|
||||||
|
return vers
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c cursePackMeta) Mods() []struct {
|
||||||
|
ModID int
|
||||||
|
FileID int
|
||||||
|
} {
|
||||||
|
list := make([]struct {
|
||||||
|
ModID int
|
||||||
|
FileID int
|
||||||
|
}, len(c.Files))
|
||||||
|
for i, v := range c.Files {
|
||||||
|
list[i] = struct {
|
||||||
|
ModID int
|
||||||
|
FileID int
|
||||||
|
}{
|
||||||
|
ModID: v.ProjectID,
|
||||||
|
FileID: v.FileID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
|
type cursePackOverrideWrapper struct {
|
||||||
|
name string
|
||||||
|
ImportPackFile
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w cursePackOverrideWrapper) Name() string {
|
||||||
|
return w.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c cursePackMeta) GetFiles() ([]ImportPackFile, error) {
|
||||||
|
// Only import files from overrides directory
|
||||||
|
if len(c.Overrides) > 0 {
|
||||||
|
fullList, err := c.importSrc.GetFileList()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
overridesList := make([]ImportPackFile, 0, len(fullList))
|
||||||
|
overridesPath := c.Overrides
|
||||||
|
if !strings.HasSuffix(overridesPath, "/") {
|
||||||
|
overridesPath = c.Overrides + "/"
|
||||||
|
}
|
||||||
|
// Wrap files, removing overrides/ from the start
|
||||||
|
for _, v := range fullList {
|
||||||
|
if strings.HasPrefix(v.Name(), overridesPath) {
|
||||||
|
overridesList = append(overridesList, cursePackOverrideWrapper{
|
||||||
|
name: strings.TrimPrefix(v.Name(), overridesPath),
|
||||||
|
ImportPackFile: v,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return overridesList, nil
|
||||||
|
}
|
||||||
|
return []ImportPackFile{}, nil
|
||||||
|
}
|
85
curseforge/packinterop/minecraftinstance.go
Normal file
85
curseforge/packinterop/minecraftinstance.go
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
package packinterop
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type twitchInstalledPackMeta struct {
|
||||||
|
NameInternal string `json:"name"`
|
||||||
|
Path string `json:"installPath"`
|
||||||
|
// TODO: javaArgsOverride?
|
||||||
|
// TODO: allocatedMemory?
|
||||||
|
MCVersion string `json:"gameVersion"`
|
||||||
|
Modloader struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
MavenVersionString string `json:"mavenVersionString"`
|
||||||
|
} `json:"baseModLoader"`
|
||||||
|
ModpackOverrides []string `json:"modpackOverrides"`
|
||||||
|
ModsInternal []struct {
|
||||||
|
ID int `json:"addonID"`
|
||||||
|
File struct {
|
||||||
|
// I've given up on using this cached data, just going to re-request it
|
||||||
|
ID int `json:"id"`
|
||||||
|
} `json:"installedFile"`
|
||||||
|
} `json:"installedAddons"`
|
||||||
|
// Used to determine if modpackOverrides should be used or not
|
||||||
|
IsUnlocked bool `json:"isUnlocked"`
|
||||||
|
importSrc ImportPackSource
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m twitchInstalledPackMeta) Name() string {
|
||||||
|
return m.NameInternal
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m twitchInstalledPackMeta) Versions() map[string]string {
|
||||||
|
vers := make(map[string]string)
|
||||||
|
vers["minecraft"] = m.MCVersion
|
||||||
|
if strings.HasPrefix(m.Modloader.Name, "forge") {
|
||||||
|
if len(m.Modloader.MavenVersionString) > 0 {
|
||||||
|
vers["forge"] = strings.TrimPrefix(m.Modloader.MavenVersionString, "net.minecraftforge:forge:")
|
||||||
|
} else {
|
||||||
|
vers["forge"] = strings.TrimPrefix(m.Modloader.Name, "forge-")
|
||||||
|
}
|
||||||
|
// Remove the minecraft version prefix, if it exists
|
||||||
|
vers["forge"] = strings.TrimPrefix(vers["forge"], m.MCVersion+"-")
|
||||||
|
}
|
||||||
|
return vers
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m twitchInstalledPackMeta) Mods() []struct {
|
||||||
|
ModID int
|
||||||
|
FileID int
|
||||||
|
} {
|
||||||
|
list := make([]struct {
|
||||||
|
ModID int
|
||||||
|
FileID int
|
||||||
|
}, len(m.ModsInternal))
|
||||||
|
for i, v := range m.ModsInternal {
|
||||||
|
list[i] = struct {
|
||||||
|
ModID int
|
||||||
|
FileID int
|
||||||
|
}{
|
||||||
|
ModID: v.ID,
|
||||||
|
FileID: v.File.ID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m twitchInstalledPackMeta) GetFiles() ([]ImportPackFile, error) {
|
||||||
|
// If the modpack is unlocked, import all the files
|
||||||
|
// Otherwise import just the modpack overrides
|
||||||
|
if m.IsUnlocked {
|
||||||
|
return m.importSrc.GetFileList()
|
||||||
|
}
|
||||||
|
list := make([]ImportPackFile, len(m.ModpackOverrides))
|
||||||
|
var err error
|
||||||
|
for i, v := range m.ModpackOverrides {
|
||||||
|
list[i], err = m.importSrc.GetFile(filepath.ToSlash(v))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list, nil
|
||||||
|
}
|
119
curseforge/packinterop/translation.go
Normal file
119
curseforge/packinterop/translation.go
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
package packinterop
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/comp500/packwiz/core"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ReadMetadata(s ImportPackSource) ImportPackMetadata {
|
||||||
|
var packImport ImportPackMetadata
|
||||||
|
metaFile := s.GetPackFile()
|
||||||
|
rdr, err := metaFile.Open()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error reading file: %s\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the whole file (as we are going to parse it multiple times)
|
||||||
|
fileData, err := ioutil.ReadAll(rdr)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error reading file: %s\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine what format the file is
|
||||||
|
var jsonFile map[string]interface{}
|
||||||
|
err = json.Unmarshal(fileData, &jsonFile)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error parsing JSON: %s\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
isManifest := false
|
||||||
|
if v, ok := jsonFile["manifestType"]; ok {
|
||||||
|
isManifest = v.(string) == "minecraftModpack"
|
||||||
|
}
|
||||||
|
if isManifest {
|
||||||
|
packMeta := cursePackMeta{importSrc: s}
|
||||||
|
err = json.Unmarshal(fileData, &packMeta)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error parsing JSON: %s\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
packImport = packMeta
|
||||||
|
} else {
|
||||||
|
// Replace FileNameOnDisk with fileNameOnDisk
|
||||||
|
fileData = bytes.ReplaceAll(fileData, []byte("FileNameOnDisk"), []byte("fileNameOnDisk"))
|
||||||
|
packMeta := twitchInstalledPackMeta{importSrc: s}
|
||||||
|
err = json.Unmarshal(fileData, &packMeta)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error parsing JSON: %s\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
packImport = packMeta
|
||||||
|
}
|
||||||
|
|
||||||
|
return packImport
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddonFileReference is a pair of Project ID and File ID to reference a single file on CurseForge
|
||||||
|
type AddonFileReference struct {
|
||||||
|
ProjectID int
|
||||||
|
FileID int
|
||||||
|
}
|
||||||
|
|
||||||
|
func WriteManifestFromPack(pack core.Pack, fileRefs []AddonFileReference, out io.Writer) error {
|
||||||
|
// TODO: should Required be false sometimes?
|
||||||
|
files := make([]struct {
|
||||||
|
ProjectID int `json:"projectID"`
|
||||||
|
FileID int `json:"fileID"`
|
||||||
|
Required bool `json:"required"`
|
||||||
|
}, len(fileRefs))
|
||||||
|
for i, fr := range fileRefs {
|
||||||
|
files[i] = struct {
|
||||||
|
ProjectID int `json:"projectID"`
|
||||||
|
FileID int `json:"fileID"`
|
||||||
|
Required bool `json:"required"`
|
||||||
|
}{ProjectID: fr.ProjectID, FileID: fr.FileID, Required: true}
|
||||||
|
}
|
||||||
|
|
||||||
|
modLoaders := make([]modLoaderDef, 0, 1)
|
||||||
|
forgeVersion, ok := pack.Versions["forge"]
|
||||||
|
if ok {
|
||||||
|
modLoaders = append(modLoaders, modLoaderDef{
|
||||||
|
ID: "forge-" + forgeVersion,
|
||||||
|
Primary: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
manifest := cursePackMeta{
|
||||||
|
Minecraft: struct {
|
||||||
|
Version string `json:"version"`
|
||||||
|
ModLoaders []modLoaderDef `json:"modLoaders"`
|
||||||
|
}{
|
||||||
|
Version: pack.Versions["minecraft"],
|
||||||
|
ModLoaders: modLoaders,
|
||||||
|
},
|
||||||
|
ManifestType: "minecraftModpack",
|
||||||
|
ManifestVersion: 1,
|
||||||
|
NameInternal: pack.Name,
|
||||||
|
Version: "", // TODO: store or take this?
|
||||||
|
Author: "", // TODO: store or take this?
|
||||||
|
ProjectID: 0, // TODO: store or take this?
|
||||||
|
Files: files,
|
||||||
|
Overrides: "overrides",
|
||||||
|
}
|
||||||
|
|
||||||
|
w := json.NewEncoder(out)
|
||||||
|
w.SetIndent("", " ") // Match CF export
|
||||||
|
err := w.Encode(manifest)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
57
curseforge/packinterop/zip.go
Normal file
57
curseforge/packinterop/zip.go
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
package packinterop
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/zip"
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type zipReaderFile struct {
|
||||||
|
NameInternal string
|
||||||
|
*zip.File
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f zipReaderFile) Name() string {
|
||||||
|
return f.NameInternal
|
||||||
|
}
|
||||||
|
|
||||||
|
type zipPackSource struct {
|
||||||
|
MetaFile *zip.File
|
||||||
|
Reader *zip.Reader
|
||||||
|
cachedFileList []ImportPackFile
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s zipPackSource) GetFile(path string) (ImportPackFile, error) {
|
||||||
|
if s.cachedFileList == nil {
|
||||||
|
s.cachedFileList = make([]ImportPackFile, len(s.Reader.File))
|
||||||
|
for i, v := range s.Reader.File {
|
||||||
|
s.cachedFileList[i] = zipReaderFile{v.Name, v}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, v := range s.cachedFileList {
|
||||||
|
if v.Name() == path {
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return zipReaderFile{}, errors.New("file not found in zip")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s zipPackSource) GetFileList() ([]ImportPackFile, error) {
|
||||||
|
if s.cachedFileList == nil {
|
||||||
|
s.cachedFileList = make([]ImportPackFile, len(s.Reader.File))
|
||||||
|
for i, v := range s.Reader.File {
|
||||||
|
s.cachedFileList[i] = zipReaderFile{v.Name, v}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s.cachedFileList, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s zipPackSource) GetPackFile() ImportPackFile {
|
||||||
|
return zipReaderFile{s.MetaFile.Name, s.MetaFile}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetZipPackSource(metaFile *zip.File, reader *zip.Reader) ImportPackSource {
|
||||||
|
return zipPackSource{
|
||||||
|
MetaFile: metaFile,
|
||||||
|
Reader: reader,
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user