mirror of
https://github.com/packwiz/packwiz.git
synced 2025-12-04 13:34:32 +01:00
Implement pack importing/exporting for downloaded Curseforge packs
Abstract out hash implementations Implement file saving/downloading
This commit is contained in:
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,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user