diff --git a/pkg/launcher/launcher.go b/pkg/launcher/launcher.go index 4caf30c..e2bb6cb 100644 --- a/pkg/launcher/launcher.go +++ b/pkg/launcher/launcher.go @@ -16,8 +16,9 @@ import ( "fyne.io/fyne/v2/dialog" ) -var EnableMetalHud = true // Default to enabled -var CustomEnvVars = "" // Custom environment variables +var EnableMetalHud = true // Default to enabled +var CustomEnvVars = "" // Custom environment variables +var EnableVanillaTweaks = false // Default to disabled // Terminal state management var ( @@ -132,10 +133,36 @@ func LaunchGame(myWindow fyne.Window) { log.Println("Preparing to launch TurtleSilicon...") + // Determine which WoW executable to use based on vanilla-tweaks preference + var wowExePath string + if EnableVanillaTweaks { + if !CheckForWoWTweakedExecutable() { + // Show dialog asking if user wants us to apply vanilla-tweaks + HandleVanillaTweaksRequest(myWindow, func() { + // After successful patching, continue with launch using the tweaked executable + wowTweakedExePath := GetWoWTweakedExecutablePath() + if wowTweakedExePath != "" { + continueLaunch(myWindow, wowTweakedExePath) + } else { + dialog.ShowError(fmt.Errorf("failed to find WoW-tweaked.exe after patching"), myWindow) + } + }) + return // Exit early since dialog will handle the continuation + } + wowExePath = GetWoWTweakedExecutablePath() + } else { + wowExePath = filepath.Join(paths.TurtlewowPath, "WoW.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) { rosettaInTurtlePath := filepath.Join(paths.TurtlewowPath, "rosettax87") rosettaExecutable := filepath.Join(rosettaInTurtlePath, "rosettax87") wineloader2Path := filepath.Join(paths.CrossoverPath, "Contents", "SharedSupport", "CrossOver", "CrossOver-Hosted Application", "wineloader2") - wowExePath := filepath.Join(paths.TurtlewowPath, "wow.exe") // Corrected to wow.exe if !utils.PathExists(rosettaExecutable) { dialog.ShowError(fmt.Errorf("rosetta executable not found at %s. Ensure TurtleWoW patching was successful", rosettaExecutable), myWindow) @@ -146,7 +173,7 @@ func LaunchGame(myWindow fyne.Window) { return } if !utils.PathExists(wowExePath) { - dialog.ShowError(fmt.Errorf("wow.exe not found at %s. Ensure your TurtleWoW directory is correct", wowExePath), myWindow) + dialog.ShowError(fmt.Errorf("WoW executable not found at %s. Ensure your TurtleWoW directory is correct", wowExePath), myWindow) return } diff --git a/pkg/launcher/vanilla_tweaks.go b/pkg/launcher/vanilla_tweaks.go new file mode 100644 index 0000000..fcdaba6 --- /dev/null +++ b/pkg/launcher/vanilla_tweaks.go @@ -0,0 +1,205 @@ +package launcher + +import ( + "fmt" + "log" + "os" + "os/exec" + "path/filepath" + + "turtlesilicon/pkg/paths" + "turtlesilicon/pkg/utils" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/dialog" +) + +// ApplyVanillaTweaks applies vanilla-tweaks to WoW.exe to create WoW-tweaked.exe +func ApplyVanillaTweaks(myWindow fyne.Window) error { + if paths.TurtlewowPath == "" { + return fmt.Errorf("TurtleWoW path not set") + } + if paths.CrossoverPath == "" { + return fmt.Errorf("CrossOver path not set") + } + + // Get the current working directory (where the app executable is located) + execPath, err := os.Executable() + if err != nil { + return fmt.Errorf("failed to get executable path: %v", err) + } + appDir := filepath.Dir(execPath) + + // Check if we're in development mode + vanillaTweaksPath := filepath.Join(appDir, "winerosetta", "vanilla-tweaks.exe") + if !utils.PathExists(vanillaTweaksPath) { + // Try relative path from current working directory (for development) + workingDir, _ := os.Getwd() + vanillaTweaksPath = filepath.Join(workingDir, "winerosetta", "vanilla-tweaks.exe") + if !utils.PathExists(vanillaTweaksPath) { + return fmt.Errorf("vanilla-tweaks.exe not found") + } + } + + wowExePath := filepath.Join(paths.TurtlewowPath, "WoW.exe") + wineloader2Path := filepath.Join(paths.CrossoverPath, "Contents", "SharedSupport", "CrossOver", "CrossOver-Hosted Application", "wineloader2") + + if !utils.PathExists(wowExePath) { + return fmt.Errorf("WoW.exe not found at %s", wowExePath) + } + if !utils.PathExists(wineloader2Path) { + return fmt.Errorf("wineloader2 not found at %s", wineloader2Path) + } + + // First, copy vanilla-tweaks.exe to the TurtleWoW directory temporarily + tempVanillaTweaksPath := filepath.Join(paths.TurtlewowPath, "vanilla-tweaks.exe") + + // Copy vanilla-tweaks.exe to TurtleWoW directory + log.Printf("Copying vanilla-tweaks.exe from %s to %s", vanillaTweaksPath, tempVanillaTweaksPath) + sourceFile, err := os.Open(vanillaTweaksPath) + if err != nil { + return fmt.Errorf("failed to open vanilla-tweaks.exe: %v", err) + } + defer sourceFile.Close() + + destFile, err := os.Create(tempVanillaTweaksPath) + if err != nil { + return fmt.Errorf("failed to create temporary vanilla-tweaks.exe: %v", err) + } + defer destFile.Close() + + _, err = destFile.ReadFrom(sourceFile) + if err != nil { + return fmt.Errorf("failed to copy vanilla-tweaks.exe: %v", err) + } + + // Ensure the copied file is executable + if err := os.Chmod(tempVanillaTweaksPath, 0755); err != nil { + log.Printf("Warning: failed to set executable permission on vanilla-tweaks.exe: %v", err) + } + + // Build the command to apply vanilla-tweaks using the correct format: + // cd "path" && "wineloader2" ./vanilla-tweaks.exe ./WoW.exe + shellCmd := fmt.Sprintf(`cd %s && %s ./vanilla-tweaks.exe ./WoW.exe`, + utils.QuotePathForShell(paths.TurtlewowPath), + utils.QuotePathForShell(wineloader2Path)) + + log.Printf("Applying vanilla-tweaks with command: %s", shellCmd) + + // Execute the command + cmd := exec.Command("sh", "-c", shellCmd) + output, err := cmd.CombinedOutput() + + log.Printf("vanilla-tweaks command output: %s", string(output)) + + // Clean up the temporary vanilla-tweaks.exe file + if cleanupErr := os.Remove(tempVanillaTweaksPath); cleanupErr != nil { + log.Printf("Warning: failed to clean up temporary vanilla-tweaks.exe: %v", cleanupErr) + } + + // Always check if the output file was created, regardless of exit code + // Some Wine programs report error exit codes even when they succeed + foundPath := GetWoWTweakedExecutablePath() + if foundPath == "" { + // Only report error if no output file was created + if err != nil { + log.Printf("vanilla-tweaks command failed: %v", err) + return fmt.Errorf("failed to apply vanilla-tweaks: %v\nOutput: %s", err, string(output)) + } else { + return fmt.Errorf("vanilla-tweaks completed but WoW-tweaked.exe was not created\nOutput: %s", string(output)) + } + } + + // If we found the file but there was an error code, log it as a warning + if err != nil { + log.Printf("vanilla-tweaks reported error but output file was created: %v", err) + } + + log.Println("vanilla-tweaks applied successfully") + return nil +} + +// CheckForVanillaTweaksExecutable checks if vanilla-tweaks.exe exists and is accessible +func CheckForVanillaTweaksExecutable() bool { + // Get the current working directory (where the app executable is located) + execPath, err := os.Executable() + if err != nil { + return false + } + appDir := filepath.Dir(execPath) + + // Check if we're in development mode (running from VSCode) + vanillaTweaksPath := filepath.Join(appDir, "winerosetta", "vanilla-tweaks.exe") + if utils.PathExists(vanillaTweaksPath) { + return true + } + + // Try relative path from current working directory (for development) + workingDir, _ := os.Getwd() + vanillaTweaksPath = filepath.Join(workingDir, "winerosetta", "vanilla-tweaks.exe") + return utils.PathExists(vanillaTweaksPath) +} + +// GetVanillaTweaksExecutablePath returns the path to vanilla-tweaks.exe if it exists +func GetVanillaTweaksExecutablePath() (string, error) { + // Get the current working directory (where the app executable is located) + execPath, err := os.Executable() + if err != nil { + return "", fmt.Errorf("failed to get executable path: %v", err) + } + appDir := filepath.Dir(execPath) + + // Check if we're in development mode (running from VSCode) + vanillaTweaksPath := filepath.Join(appDir, "winerosetta", "vanilla-tweaks.exe") + if utils.PathExists(vanillaTweaksPath) { + return vanillaTweaksPath, nil + } + + // Try relative path from current working directory (for development) + workingDir, _ := os.Getwd() + vanillaTweaksPath = filepath.Join(workingDir, "winerosetta", "vanilla-tweaks.exe") + if utils.PathExists(vanillaTweaksPath) { + return vanillaTweaksPath, nil + } + + return "", fmt.Errorf("vanilla-tweaks.exe not found") +} + +// CheckForWoWTweakedExecutable checks if WoW_tweaked.exe exists in the TurtleWoW directory +func CheckForWoWTweakedExecutable() bool { + if paths.TurtlewowPath == "" { + return false + } + + testPath := filepath.Join(paths.TurtlewowPath, "WoW_tweaked.exe") + return utils.PathExists(testPath) +} + +// GetWoWTweakedExecutablePath returns the path to the WoW_tweaked.exe file if it exists +func GetWoWTweakedExecutablePath() string { + if paths.TurtlewowPath == "" { + return "" + } + + testPath := filepath.Join(paths.TurtlewowPath, "WoW_tweaked.exe") + if utils.PathExists(testPath) { + return testPath + } + return "" +} + +// HandleVanillaTweaksRequest handles the case when vanilla-tweaks is enabled but WoW-tweaked.exe doesn't exist +func HandleVanillaTweaksRequest(myWindow fyne.Window, callback func()) { + dialog.ShowConfirm("Vanilla-tweaks not found", + "WoW-tweaked.exe was not found in your TurtleWoW directory.\n\nWould you like TurtleSilicon to automatically apply vanilla-tweaks for you?", + func(confirmed bool) { + if confirmed { + if err := ApplyVanillaTweaks(myWindow); err != nil { + dialog.ShowError(fmt.Errorf("failed to apply vanilla-tweaks: %v", err), myWindow) + return + } + // After successful patching, execute the callback + callback() + } + }, myWindow) +} diff --git a/pkg/ui/ui.go b/pkg/ui/ui.go index 37fbd9f..2eac8c9 100644 --- a/pkg/ui/ui.go +++ b/pkg/ui/ui.go @@ -36,6 +36,7 @@ var ( stopServiceButton *widget.Button metalHudCheckbox *widget.Check showTerminalCheckbox *widget.Check + vanillaTweaksCheckbox *widget.Check envVarsEntry *widget.Entry pulsingActive = false ) @@ -277,6 +278,17 @@ func CreateUI(myWindow fyne.Window) fyne.CanvasObject { }) showTerminalCheckbox.SetChecked(prefs.ShowTerminalNormally) + vanillaTweaksCheckbox = widget.NewCheck("Enable vanilla-tweaks", func(checked bool) { + launcher.EnableVanillaTweaks = checked + // Save to preferences + prefs, _ := utils.LoadPrefs() + prefs.EnableVanillaTweaks = checked + utils.SavePrefs(prefs) + log.Printf("Vanilla-tweaks enabled: %v", launcher.EnableVanillaTweaks) + }) + vanillaTweaksCheckbox.SetChecked(prefs.EnableVanillaTweaks) + launcher.EnableVanillaTweaks = prefs.EnableVanillaTweaks + // Load environment variables from preferences if prefs.EnvironmentVariables != "" { launcher.CustomEnvVars = prefs.EnvironmentVariables @@ -344,14 +356,14 @@ func CreateUI(myWindow fyne.Window) fyne.CanvasObject { UpdateAllStatuses() // Initial UI state update // Set up periodic status updates to keep service status in sync - go func() { - for { - time.Sleep(5 * time.Second) // Check every 5 seconds - fyne.DoAndWait(func() { - UpdateAllStatuses() - }) - } - }() + // go func() { + // for { + // time.Sleep(5 * time.Second) // Check every 5 seconds + // fyne.DoAndWait(func() { + // UpdateAllStatuses() + // }) + // } + // }() // Create GitHub link githubURL := "https://github.com/tairasu/TurtleSilicon" @@ -362,19 +374,22 @@ func CreateUI(myWindow fyne.Window) fyne.CanvasObject { githubLink := widget.NewHyperlink("GitHub Repository", parsedURL) githubContainer := container.NewCenter(githubLink) - return container.NewVBox( - logoContainer, - pathSelectionForm, - patchOperationsLayout, - container.NewHBox( - metalHudCheckbox, - showTerminalCheckbox, + return container.NewPadded( + container.NewVBox( + logoContainer, + pathSelectionForm, + patchOperationsLayout, + container.NewGridWithColumns(3, + metalHudCheckbox, + showTerminalCheckbox, + vanillaTweaksCheckbox, + ), + widget.NewSeparator(), + widget.NewLabel("Environment Variables:"), + envVarsEntry, + container.NewPadded(launchButton), + widget.NewSeparator(), + githubContainer, ), - widget.NewSeparator(), - widget.NewLabel("Environment Variables:"), - envVarsEntry, - container.NewPadded(launchButton), - widget.NewSeparator(), - githubContainer, ) } diff --git a/pkg/utils/prefs.go b/pkg/utils/prefs.go index 5cff097..63f0da3 100644 --- a/pkg/utils/prefs.go +++ b/pkg/utils/prefs.go @@ -13,6 +13,7 @@ type UserPrefs struct { EnvironmentVariables string `json:"environment_variables"` SaveSudoPassword bool `json:"save_sudo_password"` ShowTerminalNormally bool `json:"show_terminal_normally"` + EnableVanillaTweaks bool `json:"enable_vanilla_tweaks"` } func getPrefsPath() (string, error) { diff --git a/winerosetta/vanilla-tweaks.exe b/winerosetta/vanilla-tweaks.exe new file mode 100755 index 0000000..b1e92f4 Binary files /dev/null and b/winerosetta/vanilla-tweaks.exe differ