Skip to content

Commit

Permalink
Launcher: Added support to windows-style vars in arguments. Fixed con…
Browse files Browse the repository at this point in the history
…fig not reverting if launcher was killed unexpectedly. Changed clientExeArgs and serverpathArgs type (and they equivalent config) to string. Introduced setupCommand and revertCommand to execute external commands.
  • Loading branch information
luskaner committed Aug 15, 2024
1 parent 93f6a2c commit 695e257
Show file tree
Hide file tree
Showing 8 changed files with 128 additions and 18 deletions.
8 changes: 7 additions & 1 deletion launcher-agent/internal/watch.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"github.com/luskaner/aoe2DELanServer/battle-server-broadcast"
"github.com/luskaner/aoe2DELanServer/common"
commonProcess "github.com/luskaner/aoe2DELanServer/common/process"
launcherCommonExecutor "github.com/luskaner/aoe2DELanServer/launcher-common/executor"
"golang.org/x/sys/windows"
"time"
)
Expand Down Expand Up @@ -41,8 +42,13 @@ func waitForProcess(processesEntryName windows.ProcessEntry32) bool {
return true
}

func Watch(steamProcess bool, microsoftStoreProcess bool, serverExe string, broadcastBattleServer bool, revertArgs []string, exitCode *int) {
func Watch(steamProcess bool, microsoftStoreProcess bool, serverExe string, broadcastBattleServer bool, revertArgs []string, revertCmd []string, exitCode *int) {
*exitCode = common.ErrSuccess
if len(revertCmd) > 0 {
defer func() {
launcherCommonExecutor.RunCommand(revertCmd)
}()
}
if len(revertArgs) > 0 {
defer func() {
RunConfig(revertArgs)
Expand Down
18 changes: 15 additions & 3 deletions launcher-agent/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@ import (
"github.com/luskaner/aoe2DELanServer/common"
"github.com/luskaner/aoe2DELanServer/common/pidLock"
"github.com/luskaner/aoe2DELanServer/launcher-agent/internal"
launcherCommonExecutor "github.com/luskaner/aoe2DELanServer/launcher-common/executor"
"golang.org/x/sys/windows"
"os"
"os/signal"
"strconv"
)

const revertCmdStart = 6

func main() {
lock := &pidLock.Lock{}
if err := lock.Lock(); err != nil {
Expand All @@ -19,9 +22,15 @@ func main() {
microsoftStoreProcess, _ := strconv.ParseBool(os.Args[2])
serverExe := os.Args[3]
broadcastBattleServer, _ := strconv.ParseBool(os.Args[4])
revertCmdLength, _ := strconv.ParseInt(os.Args[5], 10, 64)
revertCmdEnd := revertCmdStart + revertCmdLength
var revertCmd []string
if revertCmdLength > 0 {
revertCmd = os.Args[revertCmdStart:revertCmdEnd]
}
var revertFlags []string
if len(os.Args) > 5 {
revertFlags = os.Args[5:]
if int64(len(os.Args)) > revertCmdEnd {
revertFlags = os.Args[revertCmdEnd:]
}
var exitCode int
sigs := make(chan os.Signal, 1)
Expand All @@ -33,11 +42,14 @@ func main() {
if len(revertFlags) > 0 {
internal.RunConfig(revertFlags)
}
if len(revertCmd) > 0 {
launcherCommonExecutor.RunCommand(revertCmd)
}
_ = lock.Unlock()
os.Exit(exitCode)
}
}()
internal.Watch(steamProcess, microsoftStoreProcess, serverExe, broadcastBattleServer, revertFlags, &exitCode)
internal.Watch(steamProcess, microsoftStoreProcess, serverExe, broadcastBattleServer, revertFlags, revertCmd, &exitCode)
_ = lock.Unlock()
os.Exit(exitCode)
}
47 changes: 40 additions & 7 deletions launcher/internal/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"os"
"os/signal"
"path/filepath"
"regexp"
"strconv"
"strings"
"time"
Expand All @@ -26,11 +27,14 @@ const autoValue = "auto"
const trueValue = "true"
const falseValue = "false"

var reWinToLinVar = regexp.MustCompile(`%(\w+)%`)
var configPaths = []string{"resources", "."}
var config = &cmdUtils.Config{}

func parseCommandArgs(name string) (args []string, err error) {
args, err = shell.Fields(viper.GetString(name), nil)
cmd := viper.GetString(name)
cmd = reWinToLinVar.ReplaceAllString(cmd, `$$$1`)
args, err = shell.Fields(cmd, nil)
return
}

Expand Down Expand Up @@ -93,6 +97,21 @@ var (
errorCode = internal.ErrInvalidClientArgs
return
}
var setupCommand []string
setupCommand, err = parseCommandArgs("Config.SetupCommand")
if err != nil {
fmt.Println("Failed to parse setup command")
errorCode = internal.ErrInvalidSetupCommand
return
}
var revertCommand []string
revertCommand, err = parseCommandArgs("Config.RevertCommand")
if err != nil {
fmt.Println("Failed to parse revert command")
errorCode = internal.ErrInvalidRevertCommand
return
}
config.SetRevertCommand(revertCommand)
canAddHost := viper.GetBool("Config.CanAddHost")
isolateMetadata := viper.GetBool("Config.IsolateMetadata")
isolateProfiles := viper.GetBool("Config.IsolateProfiles")
Expand All @@ -110,10 +129,7 @@ var (
go func() {
_, ok := <-sigs
if ok {
if config.AgentStarted() {
config.KillAgent()
config.Revert()
}
config.Revert()
_ = lock.Unlock()
os.Exit(errorCode)
}
Expand Down Expand Up @@ -144,6 +160,15 @@ var (
}
// Setup
fmt.Println("Setting up...")
if len(setupCommand) > 0 {
fmt.Printf("Running setup command '%s'...\n", viper.GetString("Config.SetupCommand"))
err = config.RunSetupCommand(setupCommand)
if err != nil {
fmt.Printf("Error: %s\n", err)
errorCode = internal.ErrSetupCommand
return
}
}
if serverStart == "auto" {
announcePorts := viper.GetStringSlice("Server.AnnouncePorts")
portsInt := make([]int, len(announcePorts))
Expand Down Expand Up @@ -223,15 +248,17 @@ func Execute() error {
rootCmd.PersistentFlags().StringP("canBroadcastBattleServer", "b", "auto", `Whether or not to broadcast the game BattleServer to all interfaces in LAN (not just the most priority one)`)
rootCmd.PersistentFlags().BoolP("isolateMetadata", "m", true, "Isolate the metadata cache of the game, otherwise, it will be shared.")
rootCmd.PersistentFlags().BoolP("isolateProfiles", "p", false, "(Experimental) Isolate the users profile of the game, otherwise, it will be shared.")
rootCmd.PersistentFlags().String("setupCommand", "", `Executable to run (including arguments) to run first after the "Setting up..." line. You may use environment variables. Path names need to use double backslashes or be within single quotes.`)
rootCmd.PersistentFlags().String("revertCommand", "", `Executable to run (including arguments) to run after setupCommand, game has exited and everything has been reverted. It may run before if there is an error. You may use environment variables. Path names need to use double backslashes or be within single quotes.`)
rootCmd.PersistentFlags().StringP("serverStart", "a", "auto", `Start the server if needed, "auto" will start a server if one is not already running, "true" (will start a server regardless if one is already running), "false" (will require an already running server).`)
rootCmd.PersistentFlags().StringP("serverStop", "o", "auto", `Stop the server if started, "auto" will stop the server if one was started, "false" (will not stop the server regardless if one was started), "true" (will not stop the server even if it was started).`)
rootCmd.PersistentFlags().StringSliceP("serverAnnouncePorts", "n", []string{strconv.Itoa(common.AnnouncePort)}, `Announce ports to listen to. If not including the default port, default configured servers will not get discovered.`)
rootCmd.PersistentFlags().StringP("server", "s", "", `Hostname of the server to connect to. If not absent, serverStart will be assumed to be false. Ignored otherwise`)
serverExe := common.GetExeFileName(false, common.Server)
rootCmd.PersistentFlags().StringP("serverPath", "e", "auto", fmt.Sprintf(`The executable path of the server, "auto", will be try to execute in this order ".\%s", ".\%s\%s", "..\%s" and finally "..\%s\%s", otherwise set the path (relative or absolute).`, serverExe, common.Server, serverExe, serverExe, common.Server, serverExe))
rootCmd.PersistentFlags().StringP("serverPathArgs", "r", "[]string{}", `The arguments to pass to the server executable if starting it. Execute the server help flag for available arguments. Path names need to use double backslashes or be within single quotes.`)
rootCmd.PersistentFlags().StringP("serverPathArgs", "r", "", `The arguments to pass to the server executable if starting it. Execute the server help flag for available arguments. You may use environment variables. Path names need to use double backslashes or be within single quotes.`)
rootCmd.PersistentFlags().StringP("clientExe", "l", "auto", `The type of game client or the path. "auto" will use the Steam and then the Microsoft Store one if found. Use a path to the game launcher, "steam" or "msstore" to use the default launcher.`)
rootCmd.PersistentFlags().StringP("clientExeArgs", "i", "", `The arguments to pass to the client launcher if it is custom. Path names need to use double backslashes or be within single quotes.`)
rootCmd.PersistentFlags().StringP("clientExeArgs", "i", "", `The arguments to pass to the client launcher if it is custom. You may use environment variables. Path names need to use double backslashes or be within single quotes.`)
if err := viper.BindPFlag("Config.CanAddHost", rootCmd.PersistentFlags().Lookup("canAddHost")); err != nil {
return err
}
Expand All @@ -247,6 +274,12 @@ func Execute() error {
if err := viper.BindPFlag("Config.IsolateProfiles", rootCmd.PersistentFlags().Lookup("isolateProfiles")); err != nil {
return err
}
if err := viper.BindPFlag("Config.SetupCommand", rootCmd.PersistentFlags().Lookup("setupCommand")); err != nil {
return err
}
if err := viper.BindPFlag("Config.RevertCommand", rootCmd.PersistentFlags().Lookup("revertCommand")); err != nil {
return err
}
if err := viper.BindPFlag("Server.Start", rootCmd.PersistentFlags().Lookup("serverStart")); err != nil {
return err
}
Expand Down
5 changes: 3 additions & 2 deletions launcher/internal/cmdUtils/game.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,11 @@ func (c *Config) LaunchAgentAndGame(executable string, args []string, canTrustCe
broadcastBattleServer = true
}
}
if broadcastBattleServer || len(c.serverExe) > 0 || c.RequiresConfigRevert() {
revertCommand := c.RevertCommand()
if len(revertCommand) > 0 || broadcastBattleServer || len(c.serverExe) > 0 || c.RequiresConfigRevert() {
fmt.Println("Starting agent, accept any dialog from 'agent.exe' (including the firewall) if it appears...")
steamProcess, microsoftStoreProcess := executer.GameProcesses()
result := executor.RunAgent(steamProcess, microsoftStoreProcess, c.serverExe, broadcastBattleServer, c.unmapIPs, c.removeUserCert, c.removeLocalCert, c.restoreMetadata, c.restoreProfiles)
result := executor.RunAgent(steamProcess, microsoftStoreProcess, c.serverExe, broadcastBattleServer, revertCommand, c.unmapIPs, c.removeUserCert, c.removeLocalCert, c.restoreMetadata, c.restoreProfiles)
if !result.Success() {
fmt.Println("Failed to start agent.")
errorCode = internal.ErrAgentStart
Expand Down
43 changes: 43 additions & 0 deletions launcher/internal/cmdUtils/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"github.com/luskaner/aoe2DELanServer/common"
commonProcess "github.com/luskaner/aoe2DELanServer/common/process"
launcherExecutor "github.com/luskaner/aoe2DELanServer/launcher-common/executor"
"github.com/luskaner/aoe2DELanServer/launcher/internal/executor"
)

Expand All @@ -16,6 +17,8 @@ type Config struct {
restoreProfiles bool
serverExe string
agentStarted bool
setupCommandRan bool
revertCommand []string
}

func (c *Config) MappedHosts() {
Expand Down Expand Up @@ -48,6 +51,10 @@ func (c *Config) SetServerExe(exe string) {
c.serverExe = exe
}

func (c *Config) SetRevertCommand(cmd []string) {
c.revertCommand = cmd
}

func (c *Config) CfgAgentStarted() bool {
return c.startedAgent
}
Expand All @@ -56,6 +63,10 @@ func (c *Config) RequiresConfigRevert() bool {
return c.unmapIPs || c.removeUserCert || c.removeLocalCert || c.restoreMetadata || c.restoreProfiles
}

func (c *Config) RequiresRunningRevertCommand() bool {
return c.setupCommandRan && len(c.revertCommand) > 0
}

func (c *Config) AgentStarted() bool {
return c.agentStarted
}
Expand All @@ -64,7 +75,17 @@ func (c *Config) ServerExe() string {
return c.serverExe
}

func (c *Config) RevertCommand() []string {
if c.setupCommandRan {
return c.revertCommand
}
return []string{}
}

func (c *Config) Revert() {
if c.AgentStarted() {
c.KillAgent()
}
if serverExe := c.ServerExe(); len(serverExe) > 0 {
fmt.Println("Stopping server...")
if proc, err := commonProcess.Kill(serverExe); err == nil {
Expand All @@ -91,6 +112,15 @@ func (c *Config) Revert() {
}
}
}
if c.RequiresRunningRevertCommand() {
err := c.RunRevertCommand()
if err != nil {
fmt.Println("Failed to run revert command.")
fmt.Println("Error message: " + err.Error())
} else {
fmt.Println("Ran Revert command.")
}
}
}

func GameRunning() bool {
Expand All @@ -100,3 +130,16 @@ func GameRunning() bool {
}
return false
}

func (c *Config) RunSetupCommand(cmd []string) (err error) {
err = launcherExecutor.RunCommand(cmd)
if err == nil {
c.setupCommandRan = true
}
return
}

func (c *Config) RunRevertCommand() (err error) {
err = launcherExecutor.RunCommand(c.revertCommand)
return
}
4 changes: 4 additions & 0 deletions launcher/internal/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,8 @@ const (
ErrAgentStart
ErrInvalidServerArgs
ErrInvalidClientArgs
ErrInvalidSetupCommand
ErrInvalidRevertCommand
ErrSetupCommand
ErrRevertCommand
)
5 changes: 3 additions & 2 deletions launcher/internal/executor/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ import (
"strconv"
)

func RunAgent(steamProcess bool, microsoftStoreProcess bool, serverExe string, broadCastBattleServer bool, unmapIPs bool, removeUserCert bool, removeLocalCert bool, restoreMetadata bool, restoreProfiles bool) (result *executor.ExecResult) {
func RunAgent(steamProcess bool, microsoftStoreProcess bool, serverExe string, broadCastBattleServer bool, revertCommand []string, unmapIPs bool, removeUserCert bool, removeLocalCert bool, restoreMetadata bool, restoreProfiles bool) (result *executor.ExecResult) {
if serverExe == "" {
serverExe = "-"
}
args := []string{strconv.FormatBool(steamProcess), strconv.FormatBool(microsoftStoreProcess), serverExe, strconv.FormatBool(broadCastBattleServer)}
args := []string{strconv.FormatBool(steamProcess), strconv.FormatBool(microsoftStoreProcess), serverExe, strconv.FormatBool(broadCastBattleServer), strconv.FormatUint(uint64(len(revertCommand)), 10)}
args = append(args, revertCommand...)
args = append(args, RevertFlags(unmapIPs, removeUserCert, removeLocalCert, restoreMetadata, restoreProfiles)...)
result = executor.ExecOptions{File: common.GetExeFileName(false, common.LauncherAgent), Pid: true, Args: args}.Exec()
return
Expand Down
16 changes: 13 additions & 3 deletions launcher/resources/config.ini
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ CanBroadcastBattleServer = auto
IsolateMetadata = true
# (Experimental) If true, the launcher will isolate the user profiles of the game, if false, it will be shared
IsolateProfiles = false
# Executable to run (including arguments) to run first after the "Setting up..." line.
# Path names need to use double backslashes or be within single quotes.
# Note: You may use environment variables.
SetupCommand =
# Executable to run (including arguments) to run after SetupCommand, game has exited and everything has been reverted. It may run before if there is an error.
# Path names need to use double backslashes or be within single quotes.
# Note: You may use environment variables.
RevertCommand =

[Server]
# Whether to start the server automatically or 'auto', which will start the server is no server is found in the LAN
Expand All @@ -25,7 +33,8 @@ Start = auto
# 4. "..\server\server.exe"
Executable = auto
# The arguments to pass to the server executable if starting it. Execute the server help flag for available arguments.
# Note: Path names need to use double backslashes or be within single quotes.
# Path names need to use double backslashes or be within single quotes.
# Note: You may use environment variables.
ExecutableArgs =
# The host of the server to connect to if Start = false, if Start = true/auto this will be ignored
# The host may be a DNS name or IP (IPv4 or IPv6) but IPv4 is recommended. 0.0.0.0 means every local interface IP.
Expand All @@ -40,6 +49,7 @@ AnnouncePorts = 31978
# The path to the game launcher, if 'auto', the Steam and then the Microsoft Store one will be launched if found
# Use a path to the game launcher, 'steam' or 'msstore' to use the default launcher
Executable = auto
# The arguments to pass to the client launcher if it is custom
# Note: Path names need to use double backslashes or be within single quotes.
# The arguments to pass to the client launcher if it is custom.
# Path names need to use double backslashes or be within single quotes.
# Note: You may use environment variables.
ExecutableArgs =

0 comments on commit 695e257

Please sign in to comment.