254 lines
7.0 KiB
Go
254 lines
7.0 KiB
Go
package launcher
|
|
|
|
import (
|
|
"bufio"
|
|
"epochsilicon/pkg/log"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"sync"
|
|
|
|
"epochsilicon/pkg/paths" // Corrected import path
|
|
"epochsilicon/pkg/utils" // Corrected import path
|
|
|
|
"fyne.io/fyne/v2"
|
|
"fyne.io/fyne/v2/dialog"
|
|
)
|
|
|
|
var EnableMetalHud = false // Default to disabled
|
|
var CustomEnvVars = "" // Custom environment variables
|
|
var EnableVanillaTweaks = false // Default to disabled
|
|
var AutoDeleteWdb = false // Default to disabled
|
|
|
|
// Terminal state management
|
|
var (
|
|
currentGameProcess *exec.Cmd
|
|
isGameRunning bool
|
|
gameMutex sync.Mutex
|
|
)
|
|
|
|
// runGameIntegrated runs the game with integrated terminal output
|
|
func runGameIntegrated(parentWindow fyne.Window, shellCmd string) error {
|
|
gameMutex.Lock()
|
|
defer gameMutex.Unlock()
|
|
|
|
if isGameRunning {
|
|
return fmt.Errorf("game is already running")
|
|
}
|
|
|
|
isGameRunning = true
|
|
|
|
// Parse the shell command to extract components
|
|
// The shellCmd format is: cd <path> && <envVars> <rosettaExec> <wineloader> <wowExe>
|
|
log.Debugf("Parsing shell command: %s", shellCmd)
|
|
|
|
// Create the command without context cancellation
|
|
cmd := exec.Command("sh", "-c", shellCmd)
|
|
|
|
// Set up stdout and stderr pipes
|
|
stdout, err := cmd.StdoutPipe()
|
|
if err != nil {
|
|
isGameRunning = false
|
|
return err
|
|
}
|
|
stderr, err := cmd.StderrPipe()
|
|
if err != nil {
|
|
isGameRunning = false
|
|
return err
|
|
}
|
|
|
|
currentGameProcess = cmd
|
|
|
|
// Start the process
|
|
if err := cmd.Start(); err != nil {
|
|
isGameRunning = false
|
|
return err
|
|
}
|
|
|
|
// Monitor output in goroutines
|
|
go func() {
|
|
scanner := bufio.NewScanner(stdout)
|
|
for scanner.Scan() {
|
|
line := scanner.Text()
|
|
log.Debugf("GAME STDOUT: %s", line)
|
|
}
|
|
}()
|
|
|
|
go func() {
|
|
scanner := bufio.NewScanner(stderr)
|
|
for scanner.Scan() {
|
|
line := scanner.Text()
|
|
log.Debugf("GAME STDERR: %s", line)
|
|
}
|
|
}()
|
|
|
|
// Wait for the process to complete in a goroutine
|
|
go func() {
|
|
defer func() {
|
|
gameMutex.Lock()
|
|
isGameRunning = false
|
|
currentGameProcess = nil
|
|
gameMutex.Unlock()
|
|
}()
|
|
|
|
if err := cmd.Wait(); err != nil {
|
|
log.Debugf("Game process ended with error: %v", err)
|
|
} else {
|
|
log.Debug("Game process ended successfully")
|
|
}
|
|
}()
|
|
|
|
return nil
|
|
}
|
|
|
|
func LaunchGame(myWindow fyne.Window) {
|
|
log.Debug("Launch Game button clicked")
|
|
|
|
if paths.CrossoverPath == "" {
|
|
dialog.ShowError(fmt.Errorf("CrossOver path not set. Please set it in the patcher."), myWindow)
|
|
return
|
|
}
|
|
if paths.EpochPath == "" {
|
|
dialog.ShowError(fmt.Errorf("Epoch path not set. Please set it in the patcher."), myWindow)
|
|
return
|
|
}
|
|
if !paths.PatchesAppliedEpoch || !paths.PatchesAppliedCrossOver {
|
|
confirmed := false
|
|
dialog.ShowConfirm("Warning", "Not all patches confirmed applied. Continue with launch?", func(c bool) {
|
|
confirmed = c
|
|
}, myWindow)
|
|
if !confirmed {
|
|
return
|
|
}
|
|
}
|
|
|
|
// Check if game is already running
|
|
gameMutex.Lock()
|
|
if isGameRunning {
|
|
gameMutex.Unlock()
|
|
dialog.ShowInformation("Game Already Running", "The game is already running.", myWindow)
|
|
return
|
|
}
|
|
gameMutex.Unlock()
|
|
|
|
log.Debug("Preparing to launch EpochSilicon...")
|
|
|
|
wowExePath := filepath.Join(paths.EpochPath, "Project-Epoch.exe")
|
|
|
|
// Continue with normal launch process
|
|
continueLaunch(myWindow, wowExePath)
|
|
}
|
|
|
|
// continueLaunch continues the game launch process with the specified executable
|
|
func continueLaunch(myWindow fyne.Window, wowExePath string) {
|
|
rosettaInEpochPath := filepath.Join(paths.EpochPath, "rosettax87")
|
|
rosettaExecutable := filepath.Join(rosettaInEpochPath, "rosettax87")
|
|
wineloader2Path := filepath.Join(paths.CrossoverPath, "Contents", "SharedSupport", "CrossOver", "CrossOver-Hosted Application", "wineloader2")
|
|
|
|
if !utils.PathExists(rosettaExecutable) {
|
|
dialog.ShowError(fmt.Errorf("rosetta executable not found at %s. Ensure Epoch patching was successful", rosettaExecutable), myWindow)
|
|
return
|
|
}
|
|
if !utils.PathExists(wineloader2Path) {
|
|
dialog.ShowError(fmt.Errorf("patched wineloader2 not found at %s. Ensure CrossOver patching was successful", wineloader2Path), myWindow)
|
|
return
|
|
}
|
|
if !utils.PathExists(wowExePath) {
|
|
dialog.ShowError(fmt.Errorf("WoW executable not found at %s. Ensure your Epoch directory is correct", wowExePath), myWindow)
|
|
return
|
|
}
|
|
|
|
// Auto-delete WDB directory if enabled
|
|
if AutoDeleteWdb {
|
|
wdbPath := filepath.Join(paths.EpochPath, "WDB")
|
|
if utils.DirExists(wdbPath) {
|
|
log.Debugf("Auto-deleting WDB directory: %s", wdbPath)
|
|
if err := os.RemoveAll(wdbPath); err != nil {
|
|
log.Debugf("Warning: failed to auto-delete WDB directory: %v", err)
|
|
// Don't block the launch, just log the error
|
|
} else {
|
|
log.Debugf("Successfully auto-deleted WDB directory")
|
|
}
|
|
} else {
|
|
log.Debugf("WDB directory not found, nothing to delete")
|
|
}
|
|
}
|
|
|
|
// Since RosettaX87 service is already running, we can directly launch WoW
|
|
log.Debug("RosettaX87 service is running. Proceeding to launch WoW.")
|
|
|
|
if paths.CrossoverPath == "" || paths.EpochPath == "" {
|
|
dialog.ShowError(fmt.Errorf("CrossOver path or Epoch path is not set. Cannot launch WoW."), myWindow)
|
|
return
|
|
}
|
|
|
|
mtlHudValue := "0"
|
|
if EnableMetalHud {
|
|
mtlHudValue = "1"
|
|
}
|
|
|
|
// Prepare environment variables
|
|
envVars := fmt.Sprintf(`WINEDLLOVERRIDES="d3d9=n,b" MTL_HUD_ENABLED=%s MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS=1 DXVK_ASYNC=1`, mtlHudValue)
|
|
if CustomEnvVars != "" {
|
|
envVars = CustomEnvVars + " " + envVars
|
|
}
|
|
|
|
shellCmd := fmt.Sprintf(`cd %s && %s %s %s %s`,
|
|
utils.QuotePathForShell(paths.EpochPath),
|
|
envVars,
|
|
utils.QuotePathForShell(rosettaExecutable),
|
|
utils.QuotePathForShell(wineloader2Path),
|
|
utils.QuotePathForShell(wowExePath))
|
|
|
|
// Check user preference for terminal display
|
|
prefs, _ := utils.LoadPrefs()
|
|
|
|
if prefs.ShowTerminalNormally {
|
|
// Use the old method with external Terminal.app
|
|
escapedShellCmd := utils.EscapeStringForAppleScript(shellCmd)
|
|
cmd2Script := fmt.Sprintf("tell application \"Terminal\" to do script \"%s\"", escapedShellCmd)
|
|
|
|
log.Debug("Executing WoW launch command via AppleScript...")
|
|
if !utils.RunOsascript(cmd2Script, myWindow) {
|
|
return
|
|
}
|
|
|
|
log.Debug("Launch command executed. Check the new terminal window.")
|
|
} else {
|
|
// Use integrated terminal
|
|
log.Debugf("Shell command for integrated terminal: %s", shellCmd)
|
|
log.Debug("Executing WoW launch command with integrated terminal...")
|
|
if err := runGameIntegrated(myWindow, shellCmd); err != nil {
|
|
dialog.ShowError(fmt.Errorf("failed to launch game: %v", err), myWindow)
|
|
return
|
|
}
|
|
log.Debug("Game launched with integrated terminal. Check the application logs for output.")
|
|
}
|
|
}
|
|
|
|
// IsGameRunning returns true if the game is currently running
|
|
func IsGameRunning() bool {
|
|
gameMutex.Lock()
|
|
defer gameMutex.Unlock()
|
|
return isGameRunning
|
|
}
|
|
|
|
// StopGame forcefully stops the running game
|
|
func StopGame() error {
|
|
gameMutex.Lock()
|
|
defer gameMutex.Unlock()
|
|
|
|
if !isGameRunning || currentGameProcess == nil {
|
|
return fmt.Errorf("no game process is running")
|
|
}
|
|
|
|
// Try to terminate gracefully first
|
|
if err := currentGameProcess.Process.Signal(os.Interrupt); err != nil {
|
|
// If that fails, force kill
|
|
return currentGameProcess.Process.Kill()
|
|
}
|
|
|
|
return nil
|
|
}
|