mirror of
https://github.com/packwiz/packwiz.git
synced 2025-04-19 13:06:30 +02:00
The mods-folder option is now replaced with two new options: meta-folder and meta-folder-base This allows non-mod files to use the correct directory based on their category; with correct import of resource packs/etc from CurseForge packs, and the ability to override this behaviour. To improve the reliability of packwiz metadata file marking (in the index), new files now use .pw.toml as the extension - any extension can be used, but .pw.toml will now be automatically be marked as a metafile regardless of folder, so you can easily move metadata files around. Existing metadata files will still work (as metafile = true is set in the index); though in the future .pw.toml may be required.
160 lines
4.2 KiB
Go
160 lines
4.2 KiB
Go
package core
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
|
|
"github.com/BurntSushi/toml"
|
|
)
|
|
|
|
// Mod stores metadata about a mod. This is written to a TOML file for each mod.
|
|
type Mod struct {
|
|
metaFile string // The file for the metadata file, used as an ID
|
|
Name string `toml:"name"`
|
|
FileName string `toml:"filename"`
|
|
Side string `toml:"side,omitempty"`
|
|
Download ModDownload `toml:"download"`
|
|
// Update is a map of map of stuff, so you can store arbitrary values on string keys to define updating
|
|
Update map[string]map[string]interface{} `toml:"update"`
|
|
updateData map[string]interface{}
|
|
|
|
Option *ModOption `toml:"option,omitempty"`
|
|
}
|
|
|
|
// ModDownload specifies how to download the mod file
|
|
type ModDownload struct {
|
|
URL string `toml:"url"`
|
|
HashFormat string `toml:"hash-format"`
|
|
Hash string `toml:"hash"`
|
|
}
|
|
|
|
// ModOption specifies optional metadata for this mod file
|
|
type ModOption struct {
|
|
Optional bool `toml:"optional"`
|
|
Description string `toml:"description,omitempty"`
|
|
Default bool `toml:"default,omitempty"`
|
|
}
|
|
|
|
// The three possible values of Side (the side that the mod is on) are "server", "client", and "both".
|
|
//noinspection GoUnusedConst
|
|
const (
|
|
ServerSide = "server"
|
|
ClientSide = "client"
|
|
UniversalSide = "both"
|
|
)
|
|
|
|
// LoadMod attempts to load a mod file from a path
|
|
func LoadMod(modFile string) (Mod, error) {
|
|
var mod Mod
|
|
if _, err := toml.DecodeFile(modFile, &mod); err != nil {
|
|
return Mod{}, err
|
|
}
|
|
mod.updateData = make(map[string]interface{})
|
|
// Horrible reflection library to convert map[string]interface to proper struct
|
|
for k, v := range mod.Update {
|
|
updater, ok := Updaters[k]
|
|
if ok {
|
|
updateData, err := updater.ParseUpdate(v)
|
|
if err != nil {
|
|
return mod, err
|
|
}
|
|
mod.updateData[k] = updateData
|
|
} else {
|
|
return mod, errors.New("Update plugin " + k + " not found!")
|
|
}
|
|
}
|
|
mod.metaFile = modFile
|
|
return mod, nil
|
|
}
|
|
|
|
// SetMetaPath sets the file path of a metadata file
|
|
func (m *Mod) SetMetaPath(metaFile string) string {
|
|
m.metaFile = metaFile
|
|
return m.metaFile
|
|
}
|
|
|
|
// Write saves the mod file, returning a hash format and the value of the hash of the saved file
|
|
func (m Mod) Write() (string, string, error) {
|
|
f, err := os.Create(m.metaFile)
|
|
if err != nil {
|
|
// Attempt to create the containing directory
|
|
err2 := os.MkdirAll(filepath.Dir(m.metaFile), os.ModePerm)
|
|
if err2 == nil {
|
|
f, err = os.Create(m.metaFile)
|
|
}
|
|
if err != nil {
|
|
return "sha256", "", err
|
|
}
|
|
}
|
|
|
|
h, stringer, err := GetHashImpl("sha256")
|
|
if err != nil {
|
|
_ = f.Close()
|
|
return "", "", err
|
|
}
|
|
w := io.MultiWriter(h, f)
|
|
|
|
enc := toml.NewEncoder(w)
|
|
// Disable indentation
|
|
enc.Indent = ""
|
|
err = enc.Encode(m)
|
|
hashString := stringer.HashToString(h.Sum(nil))
|
|
if err != nil {
|
|
_ = f.Close()
|
|
return "sha256", hashString, err
|
|
}
|
|
return "sha256", hashString, f.Close()
|
|
}
|
|
|
|
// GetParsedUpdateData can be used to retrieve updater-specific information after parsing a mod file
|
|
func (m Mod) GetParsedUpdateData(updaterName string) (interface{}, bool) {
|
|
upd, ok := m.updateData[updaterName]
|
|
return upd, ok
|
|
}
|
|
|
|
// GetFilePath is a clumsy hack that I made because Mod already stores it's path anyway
|
|
func (m Mod) GetFilePath() string {
|
|
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, stringer, 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 := stringer.HashToString(h.Sum(nil))
|
|
|
|
// Check if the hash of the downloaded file matches the expected hash.
|
|
if calculatedHash != m.Download.Hash {
|
|
return fmt.Errorf("Hash of downloaded file does not match with expected hash!\n download hash: %s\n expected hash: %s\n", calculatedHash, m.Download.Hash)
|
|
}
|
|
|
|
return nil
|
|
}
|