2022-12-02 02:24:13 +01:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2022-12-03 19:27:53 +01:00
|
|
|
"archive/zip"
|
2022-12-03 18:56:36 +01:00
|
|
|
"encoding/json"
|
2022-12-02 02:24:13 +01:00
|
|
|
"fmt"
|
2022-12-03 19:27:53 +01:00
|
|
|
"io"
|
2022-12-02 02:24:13 +01:00
|
|
|
"os"
|
2022-12-03 19:27:53 +01:00
|
|
|
"path/filepath"
|
2022-12-02 02:24:13 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
type Command struct {
|
|
|
|
Name string
|
|
|
|
Description string
|
2022-12-05 14:13:58 +01:00
|
|
|
Action CommandAction
|
2022-12-02 02:24:13 +01:00
|
|
|
Arguments []string
|
|
|
|
IsVarargs bool
|
|
|
|
}
|
|
|
|
|
2022-12-05 14:13:58 +01:00
|
|
|
type CommandAction func([]string, []string) (string, error)
|
|
|
|
|
2022-12-02 02:24:13 +01:00
|
|
|
var commands = []Command{
|
|
|
|
{
|
|
|
|
Name: "install",
|
|
|
|
Description: "Install one or more mod archives into modman",
|
2022-12-03 18:56:36 +01:00
|
|
|
Arguments: []string{"game name", "archive path"},
|
2022-12-02 02:24:13 +01:00
|
|
|
IsVarargs: true,
|
|
|
|
|
2022-12-05 14:13:58 +01:00
|
|
|
Action: func(requiredArguments []string, providedArguments []string) (string, error) {
|
|
|
|
var argumentCount = len(providedArguments)
|
2022-12-02 02:24:13 +01:00
|
|
|
|
2022-12-05 14:13:58 +01:00
|
|
|
if argumentCount < len(requiredArguments) {
|
|
|
|
return "", fmt.Errorf("expected game name folowed by at least one archive path")
|
2022-12-03 18:56:36 +01:00
|
|
|
}
|
2022-12-02 02:24:13 +01:00
|
|
|
|
2022-12-05 14:13:58 +01:00
|
|
|
var archivePaths = providedArguments[1:]
|
2022-12-02 02:24:13 +01:00
|
|
|
|
2022-12-05 14:13:58 +01:00
|
|
|
if gameError := WithGame(providedArguments[0], func(game *Game) error {
|
2022-12-03 19:27:53 +01:00
|
|
|
for i := range archivePaths {
|
|
|
|
var archivePath = archivePaths[i]
|
|
|
|
var extension = filepath.Ext(archivePath)
|
2022-12-02 02:24:13 +01:00
|
|
|
|
2022-12-03 19:27:53 +01:00
|
|
|
if len(extension) == 0 {
|
2022-12-05 14:13:58 +01:00
|
|
|
return fmt.Errorf("missing file extension: `%s`", archivePath)
|
2022-12-03 19:27:53 +01:00
|
|
|
}
|
|
|
|
|
2022-12-05 14:13:58 +01:00
|
|
|
var extractor, extractorExists = extractors[extension[1:]]
|
2022-12-03 19:27:53 +01:00
|
|
|
|
|
|
|
if !(extractorExists) {
|
2022-12-05 14:13:58 +01:00
|
|
|
return fmt.Errorf("unsupported file format: `%s`", archivePath)
|
2022-12-03 19:27:53 +01:00
|
|
|
}
|
|
|
|
|
2022-12-05 14:13:58 +01:00
|
|
|
if installError := game.InstallMod(
|
|
|
|
extractor, archivePath); installError != nil {
|
2022-12-03 18:56:36 +01:00
|
|
|
|
2022-12-05 14:13:58 +01:00
|
|
|
return installError
|
|
|
|
}
|
2022-12-03 19:27:53 +01:00
|
|
|
}
|
2022-12-03 18:56:36 +01:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}); gameError != nil {
|
|
|
|
return "", gameError
|
2022-12-02 02:24:13 +01:00
|
|
|
}
|
|
|
|
|
2022-12-05 14:13:58 +01:00
|
|
|
return "mods installed", nil
|
2022-12-02 02:24:13 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
Name: "remove",
|
|
|
|
Description: "Remove one or more mods from modman",
|
2022-12-03 18:56:36 +01:00
|
|
|
Arguments: []string{"game name", "mod name"},
|
2022-12-02 02:24:13 +01:00
|
|
|
IsVarargs: true,
|
|
|
|
|
2022-12-05 14:13:58 +01:00
|
|
|
Action: func(requiredArguments []string, providedArguments []string) (string, error) {
|
|
|
|
if len(providedArguments) < len(requiredArguments) {
|
|
|
|
return "", fmt.Errorf("expected %s followed by one or more %ss",
|
|
|
|
requiredArguments[0], requiredArguments[1])
|
2022-12-03 18:56:36 +01:00
|
|
|
}
|
|
|
|
|
2022-12-05 14:13:58 +01:00
|
|
|
if gameError := WithGame(providedArguments[0], func(game *Game) error {
|
|
|
|
return game.RemoveMods(providedArguments[1:])
|
2022-12-03 18:56:36 +01:00
|
|
|
}); gameError != nil {
|
|
|
|
return "", gameError
|
|
|
|
}
|
|
|
|
|
2022-12-05 14:13:58 +01:00
|
|
|
return "removed mods", nil
|
2022-12-02 02:24:13 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
Name: "rename",
|
|
|
|
Description: "Rename a mod within modman",
|
2022-12-03 18:56:36 +01:00
|
|
|
Arguments: []string{"game name", "mod name", "new name"},
|
2022-12-02 02:24:13 +01:00
|
|
|
IsVarargs: false,
|
|
|
|
|
2022-12-05 14:13:58 +01:00
|
|
|
Action: func(requiredArguments []string, providedArguments []string) (string, error) {
|
|
|
|
if len(providedArguments) != len(requiredArguments) {
|
|
|
|
return "", fmt.Errorf("expected %s followed by %s and %s",
|
|
|
|
requiredArguments[0], requiredArguments[1], requiredArguments[2])
|
2022-12-03 18:56:36 +01:00
|
|
|
}
|
|
|
|
|
2022-12-05 14:13:58 +01:00
|
|
|
if gameError := WithGame(providedArguments[0], func(game *Game) error {
|
|
|
|
if removeError := game.RenameMod(providedArguments[1],
|
|
|
|
providedArguments[2]); removeError != nil {
|
|
|
|
|
2022-12-03 18:56:36 +01:00
|
|
|
return removeError
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}); gameError != nil {
|
|
|
|
return "", gameError
|
|
|
|
}
|
|
|
|
|
|
|
|
return "", nil
|
2022-12-02 02:24:13 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
2022-12-03 18:56:36 +01:00
|
|
|
Name: "manifest",
|
|
|
|
Description: "Retrieve a manifest of all installed mods",
|
|
|
|
Arguments: []string{"game name", "format"},
|
2022-12-02 02:24:13 +01:00
|
|
|
IsVarargs: false,
|
|
|
|
|
2022-12-05 14:13:58 +01:00
|
|
|
Action: func(requiredArguments []string, providedArguments []string) (string, error) {
|
|
|
|
if len(providedArguments) != len(requiredArguments) {
|
|
|
|
return "", fmt.Errorf("expected %s followed by %s",
|
|
|
|
requiredArguments[0], requiredArguments[1])
|
2022-12-03 18:56:36 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
var modManifest = ""
|
|
|
|
|
2022-12-05 14:13:58 +01:00
|
|
|
if gameError := WithGame(providedArguments[0], func(game *Game) error {
|
|
|
|
var format = providedArguments[1]
|
2022-12-03 18:56:36 +01:00
|
|
|
var formatter, formatterExists = formatters[format]
|
|
|
|
|
|
|
|
if !(formatterExists) {
|
|
|
|
return fmt.Errorf("unsupported format: `%s`", format)
|
|
|
|
}
|
|
|
|
|
2022-12-05 14:13:58 +01:00
|
|
|
var formattedManifest, formatError = formatter(game.ModOrder)
|
2022-12-03 18:56:36 +01:00
|
|
|
|
|
|
|
if formatError != nil {
|
|
|
|
return formatError
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: Reconsider if always casting formatted data to string for output is a good
|
|
|
|
// idea.
|
|
|
|
modManifest = string(formattedManifest)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}); gameError != nil {
|
|
|
|
return "", gameError
|
|
|
|
}
|
|
|
|
|
|
|
|
return modManifest, nil
|
2022-12-02 02:24:13 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
Name: "deploy",
|
|
|
|
Description: "Deploy all installed and enabled mods",
|
2022-12-03 18:56:36 +01:00
|
|
|
Arguments: []string{"game name"},
|
2022-12-02 02:24:13 +01:00
|
|
|
IsVarargs: false,
|
|
|
|
|
2022-12-05 14:13:58 +01:00
|
|
|
Action: func(requiredArguments []string, arguments []string) (string, error) {
|
|
|
|
if len(arguments) != len(requiredArguments) {
|
|
|
|
return "", fmt.Errorf("expected %s", requiredArguments[0])
|
|
|
|
}
|
|
|
|
|
|
|
|
if gameError := WithGame(arguments[0], func(game *Game) error {
|
|
|
|
var deployError = game.Deploy()
|
|
|
|
|
|
|
|
if deployError != nil {
|
|
|
|
return deployError
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}); gameError != nil {
|
|
|
|
return "", gameError
|
|
|
|
}
|
|
|
|
|
|
|
|
return "deployed", nil
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
Name: "disable",
|
|
|
|
Description: "Disable one or more installed mods",
|
|
|
|
Arguments: []string{"game name", "mod name"},
|
|
|
|
IsVarargs: true,
|
|
|
|
|
|
|
|
Action: func(requiredArguments []string, arguments []string) (string, error) {
|
|
|
|
if len(arguments) < len(requiredArguments) {
|
|
|
|
return "", fmt.Errorf("expected %s followed by one or more %ss",
|
|
|
|
requiredArguments[0], requiredArguments[1])
|
|
|
|
}
|
|
|
|
|
|
|
|
if gameError := WithGame(arguments[0], func(game *Game) error {
|
|
|
|
if enableError := game.SwitchMods(false, arguments[1:]); enableError != nil {
|
|
|
|
return enableError
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}); gameError != nil {
|
|
|
|
return "", gameError
|
|
|
|
}
|
|
|
|
|
|
|
|
return "enabled", nil
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
Name: "enable",
|
|
|
|
Description: "Enable one or more installed mods",
|
|
|
|
Arguments: []string{"game name", "mod name"},
|
|
|
|
IsVarargs: true,
|
|
|
|
|
|
|
|
Action: func(requiredArguments []string, arguments []string) (string, error) {
|
|
|
|
if len(arguments) < len(requiredArguments) {
|
|
|
|
return "", fmt.Errorf("expected %s followed by one or more %ss",
|
|
|
|
requiredArguments[0], requiredArguments[1])
|
|
|
|
}
|
|
|
|
|
|
|
|
if gameError := WithGame(arguments[0], func(game *Game) error {
|
|
|
|
if enableError := game.SwitchMods(true, arguments[1:]); enableError != nil {
|
|
|
|
return enableError
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}); gameError != nil {
|
|
|
|
return "", gameError
|
|
|
|
}
|
|
|
|
|
|
|
|
return "enabled", nil
|
2022-12-02 02:24:13 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2022-12-03 19:27:53 +01:00
|
|
|
var extractors = map[string]Extractor{
|
|
|
|
"zip": func(archivePath string, destinationPath string) error {
|
|
|
|
var zipReader, openReaderError = zip.OpenReader(archivePath)
|
|
|
|
|
|
|
|
if openReaderError != nil {
|
|
|
|
return openReaderError
|
|
|
|
}
|
|
|
|
|
|
|
|
defer func() {
|
|
|
|
if closeError := zipReader.Close(); closeError != nil {
|
|
|
|
panic(closeError.Error())
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
if mkdirError := os.MkdirAll(destinationPath, 0755); mkdirError != nil {
|
|
|
|
return mkdirError
|
|
|
|
}
|
|
|
|
|
|
|
|
for i := range zipReader.File {
|
|
|
|
var file = zipReader.File[i]
|
|
|
|
var fileReader, fileOpenError = file.Open()
|
|
|
|
|
|
|
|
if fileOpenError != nil {
|
|
|
|
return fileOpenError
|
|
|
|
}
|
|
|
|
|
|
|
|
defer func() {
|
|
|
|
if closeError := fileReader.Close(); closeError != nil {
|
|
|
|
panic(closeError.Error())
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
var path = filepath.Join(destinationPath, file.Name)
|
|
|
|
|
|
|
|
if file.FileInfo().IsDir() {
|
|
|
|
if mkdirError := os.MkdirAll(path, file.Mode()); mkdirError != nil {
|
|
|
|
return mkdirError
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if mkdirError := os.MkdirAll(filepath.Dir(path), file.Mode()); mkdirError != nil {
|
|
|
|
return mkdirError
|
|
|
|
}
|
|
|
|
|
|
|
|
var extractedFile, fileOpenError = os.OpenFile(path,
|
|
|
|
os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode())
|
|
|
|
|
|
|
|
if fileOpenError != nil {
|
|
|
|
return fileOpenError
|
|
|
|
}
|
|
|
|
|
|
|
|
defer func() {
|
|
|
|
if fileOpenError := extractedFile.Close(); fileOpenError != nil {
|
|
|
|
panic(fileOpenError)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
var _, copyError = io.Copy(extractedFile, fileReader)
|
|
|
|
|
|
|
|
if copyError != nil {
|
|
|
|
return copyError
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2022-12-03 18:56:36 +01:00
|
|
|
var formatters = map[string]func(any) ([]byte, error){
|
|
|
|
"json": func(data any) ([]byte, error) {
|
|
|
|
var marshalledJson, marshalError = json.Marshal(data)
|
|
|
|
|
|
|
|
if marshalError != nil {
|
|
|
|
return nil, marshalError
|
|
|
|
}
|
|
|
|
|
|
|
|
return marshalledJson, nil
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2022-12-02 02:24:13 +01:00
|
|
|
func main() {
|
|
|
|
var argCount = len(os.Args)
|
|
|
|
|
|
|
|
if argCount == 1 {
|
|
|
|
fmt.Println("Modman v0.1")
|
|
|
|
fmt.Println("Enter one of the following commands following modman:")
|
|
|
|
|
|
|
|
for i := range commands {
|
|
|
|
var command = commands[i]
|
|
|
|
|
|
|
|
fmt.Print("\t", command.Name)
|
|
|
|
|
|
|
|
for j := range command.Arguments {
|
|
|
|
fmt.Print(" [", command.Arguments[j], "]")
|
|
|
|
}
|
|
|
|
|
|
|
|
if command.IsVarargs {
|
|
|
|
fmt.Println("...")
|
|
|
|
} else {
|
|
|
|
fmt.Println()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
var commandName = os.Args[1]
|
|
|
|
|
|
|
|
for i := range commands {
|
|
|
|
var command = commands[i]
|
|
|
|
|
|
|
|
if command.Name == commandName {
|
2022-12-05 14:13:58 +01:00
|
|
|
var response, actionError = command.Action(command.Arguments, os.Args[2:])
|
2022-12-02 02:24:13 +01:00
|
|
|
|
2022-12-03 18:56:36 +01:00
|
|
|
if actionError != nil {
|
|
|
|
fmt.Fprintln(os.Stderr, actionError.Error())
|
2022-12-02 02:24:13 +01:00
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
|
2022-12-03 18:56:36 +01:00
|
|
|
if len(response) != 0 {
|
|
|
|
fmt.Println(response)
|
|
|
|
}
|
2022-12-02 02:24:13 +01:00
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
2022-12-03 18:56:36 +01:00
|
|
|
|
|
|
|
fmt.Fprintf(os.Stderr, "unknown command: `%s`\n", commandName)
|
2022-12-02 02:24:13 +01:00
|
|
|
}
|