2022-12-03 18:56:36 +01:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/csv"
|
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
|
|
|
type App struct {
|
|
|
|
ConfigDirPath string
|
|
|
|
}
|
|
|
|
|
2022-12-03 19:27:53 +01:00
|
|
|
type Extractor func(string, string) error
|
|
|
|
|
2022-12-03 18:56:36 +01:00
|
|
|
type Game struct {
|
|
|
|
ID string
|
|
|
|
Mods map[string]Mod
|
|
|
|
HasUpdated bool
|
|
|
|
}
|
|
|
|
|
2022-12-03 19:27:53 +01:00
|
|
|
func (game *Game) InstallMod(extractor Extractor, archivePath string) error {
|
|
|
|
var gamePath, pathError = game.Path()
|
2022-12-03 18:56:36 +01:00
|
|
|
|
2022-12-03 19:27:53 +01:00
|
|
|
if pathError != nil {
|
|
|
|
return pathError
|
|
|
|
}
|
2022-12-03 18:56:36 +01:00
|
|
|
|
2022-12-03 19:27:53 +01:00
|
|
|
var baseName = filepath.Base(archivePath)
|
|
|
|
var modName = strings.TrimSuffix(baseName, filepath.Ext(baseName))
|
2022-12-03 18:56:36 +01:00
|
|
|
|
2022-12-03 19:27:53 +01:00
|
|
|
if extractError := extractor(archivePath, filepath.Join(
|
|
|
|
gamePath, "mods", modName)); extractError != nil {
|
2022-12-03 18:56:36 +01:00
|
|
|
|
2022-12-03 19:27:53 +01:00
|
|
|
return extractError
|
|
|
|
}
|
2022-12-03 18:56:36 +01:00
|
|
|
|
2022-12-03 19:27:53 +01:00
|
|
|
game.Mods[modName] = Mod{
|
|
|
|
IsEnabled: false,
|
2022-12-03 18:56:36 +01:00
|
|
|
}
|
|
|
|
|
2022-12-03 19:27:53 +01:00
|
|
|
game.HasUpdated = true
|
|
|
|
|
|
|
|
return nil
|
2022-12-03 18:56:36 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
type Mod struct {
|
|
|
|
IsEnabled bool
|
|
|
|
}
|
|
|
|
|
|
|
|
func WithGame(gameName string, action func(*Game) error) error {
|
|
|
|
var supportedGames = []string{"fallout4", "falloutnv", "skyrim"}
|
|
|
|
|
|
|
|
for i := range supportedGames {
|
|
|
|
var supportedGame = supportedGames[i]
|
|
|
|
|
|
|
|
if gameName == supportedGame {
|
|
|
|
var game = Game{
|
|
|
|
ID: supportedGame,
|
|
|
|
Mods: make(map[string]Mod),
|
|
|
|
HasUpdated: false,
|
|
|
|
}
|
|
|
|
|
|
|
|
var gamePath, pathError = game.Path()
|
|
|
|
|
|
|
|
if pathError != nil {
|
|
|
|
return pathError
|
|
|
|
}
|
|
|
|
|
|
|
|
var manifestPath = filepath.Join(gamePath, "mods.csv")
|
|
|
|
|
|
|
|
// Load manifest from disk.
|
|
|
|
{
|
|
|
|
var manifestFile, openError = os.Open(manifestPath)
|
|
|
|
|
|
|
|
if openError == nil {
|
|
|
|
defer manifestFile.Close()
|
|
|
|
|
|
|
|
var manifestReader = csv.NewReader(manifestFile)
|
|
|
|
var recordValues, recordError = manifestReader.Read()
|
|
|
|
|
|
|
|
for recordValues != nil {
|
|
|
|
if recordError != nil {
|
|
|
|
return recordError
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(recordValues) < 2 {
|
|
|
|
return fmt.Errorf("could not read mod manifest data - may be corrupt")
|
|
|
|
}
|
|
|
|
|
|
|
|
var status = recordValues[0]
|
|
|
|
var name = recordValues[1]
|
|
|
|
|
|
|
|
game.Mods[name] = Mod{
|
|
|
|
IsEnabled: status == "*",
|
|
|
|
}
|
|
|
|
|
|
|
|
recordValues, recordError = manifestReader.Read()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if actionError := action(&game); actionError != nil {
|
|
|
|
return actionError
|
|
|
|
}
|
|
|
|
|
|
|
|
// Save manifest back to disk.
|
|
|
|
if game.HasUpdated {
|
|
|
|
var manifestFile, openError = os.OpenFile(manifestPath,
|
|
|
|
os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
|
|
|
|
|
|
|
if openError != nil {
|
|
|
|
return openError
|
|
|
|
}
|
|
|
|
|
|
|
|
defer manifestFile.Close()
|
|
|
|
|
|
|
|
var manifestWriter = csv.NewWriter(manifestFile)
|
|
|
|
|
|
|
|
for name, mod := range game.Mods {
|
|
|
|
var status = ""
|
|
|
|
|
|
|
|
if mod.IsEnabled {
|
|
|
|
status = "*"
|
|
|
|
}
|
|
|
|
|
|
|
|
manifestWriter.Write([]string{status, name})
|
|
|
|
}
|
|
|
|
|
|
|
|
manifestWriter.Flush()
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return fmt.Errorf("%s: game not supported", gameName)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (game Game) Path() (string, error) {
|
|
|
|
var configDirPath, configError = os.UserConfigDir()
|
|
|
|
|
|
|
|
if configError != nil {
|
|
|
|
return "", configError
|
|
|
|
}
|
|
|
|
|
|
|
|
return filepath.Join(configDirPath, "modman", game.ID), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (game *Game) RemoveMods(modNames []string) ([]string, error) {
|
|
|
|
var gamePath, pathError = game.Path()
|
|
|
|
|
|
|
|
if pathError != nil {
|
|
|
|
return nil, pathError
|
|
|
|
}
|
|
|
|
|
|
|
|
var processed = 0
|
|
|
|
|
|
|
|
for i := range modNames {
|
|
|
|
var modName = modNames[i]
|
|
|
|
|
|
|
|
if removeError := os.RemoveAll(filepath.Join(gamePath, modName)); removeError != nil {
|
|
|
|
return nil, removeError
|
|
|
|
}
|
|
|
|
|
|
|
|
delete(game.Mods, modName)
|
|
|
|
|
|
|
|
game.HasUpdated = true
|
|
|
|
processed += 1
|
|
|
|
}
|
|
|
|
|
|
|
|
return modNames[0:processed], nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (game *Game) RenameMod(modName string, newName string) error {
|
|
|
|
var gamePath, pathError = game.Path()
|
|
|
|
|
|
|
|
if pathError != nil {
|
|
|
|
return pathError
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, exists := game.Mods[modName]; !(exists) {
|
|
|
|
return fmt.Errorf("no mod with that name exists")
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, exists := game.Mods[newName]; exists {
|
|
|
|
return fmt.Errorf("a mod with the new name already exists")
|
|
|
|
}
|
|
|
|
|
|
|
|
if renameError := os.Rename(filepath.Join(gamePath, modName),
|
|
|
|
filepath.Join(gamePath, newName)); renameError != nil {
|
|
|
|
|
|
|
|
return renameError
|
|
|
|
}
|
|
|
|
|
|
|
|
game.Mods[newName] = game.Mods[modName]
|
|
|
|
|
|
|
|
delete(game.Mods, modName)
|
|
|
|
|
|
|
|
game.HasUpdated = true
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|