From 9fd6ca731cb8cdde1191de77a163ddbbea337655 Mon Sep 17 00:00:00 2001 From: aomizu Date: Thu, 10 Jul 2025 13:36:21 +0900 Subject: [PATCH] added graphics settings section with more control (shadowLOD and libSilicon can be turned off) --- FyneApp.toml | 2 +- pkg/patching/patching.go | 411 ++++++++++++++++++++++++++++++++++++++- pkg/ui/components.go | 189 +++++++++++++++++- pkg/ui/popup.go | 101 ++++++++-- pkg/ui/status.go | 20 +- pkg/ui/ui.go | 14 ++ pkg/ui/variables.go | 13 ++ pkg/utils/prefs.go | 10 + 8 files changed, 726 insertions(+), 34 deletions(-) diff --git a/FyneApp.toml b/FyneApp.toml index 4fa58e0..083655e 100644 --- a/FyneApp.toml +++ b/FyneApp.toml @@ -3,4 +3,4 @@ Name = "TurtleSilicon" ID = "com.tairasu.turtlesilicon" Version = "1.2.3" - Build = 73 + Build = 76 diff --git a/pkg/patching/patching.go b/pkg/patching/patching.go index 31ec81c..44f0822 100644 --- a/pkg/patching/patching.go +++ b/pkg/patching/patching.go @@ -157,6 +157,33 @@ func PatchTurtleWoW(myWindow fyne.Window, updateAllStatuses func()) { needsWinerosettaUpdate := true needsLibSiliconPatchUpdate := true + // Check user's preference for libSiliconPatch and shadowLOD + prefs, _ := utils.LoadPrefs() + + // Enable by default unless user has explicitly disabled them + shouldEnableLibSiliconPatch := true + shouldEnableShadowLOD := true + + // If user has manually disabled these settings, respect their choice + if prefs.UserDisabledLibSiliconPatch { + shouldEnableLibSiliconPatch = false + debug.Printf("libSiliconPatch disabled by user choice") + } else { + // Enable by default and update preferences + prefs.EnableLibSiliconPatch = true + } + + if prefs.UserDisabledShadowLOD { + shouldEnableShadowLOD = false + debug.Printf("shadowLOD disabled by user choice") + } else { + // Enable by default and update preferences + prefs.SetShadowLOD0 = true + } + + // Save updated preferences + utils.SavePrefs(prefs) + if fileContentBytes, err := os.ReadFile(dllsTextFile); err == nil { fileContent := string(fileContentBytes) if strings.Contains(fileContent, winerosettaEntry) { @@ -168,7 +195,13 @@ func PatchTurtleWoW(myWindow fyne.Window, updateAllStatuses func()) { needsLibSiliconPatchUpdate = false } } else { - debug.Printf("dlls.txt not found, will create a new one with both entries") + debug.Printf("dlls.txt not found, will create a new one") + } + + // Only add libSiliconPatch if user wants it enabled + if !shouldEnableLibSiliconPatch { + needsLibSiliconPatchUpdate = false + debug.Printf("libSiliconPatch disabled by user preference, will not add to dlls.txt") } if needsWinerosettaUpdate || needsLibSiliconPatchUpdate { @@ -196,7 +229,7 @@ func PatchTurtleWoW(myWindow fyne.Window, updateAllStatuses func()) { debug.Printf("Adding %s to dlls.txt", winerosettaEntry) } } - if needsLibSiliconPatchUpdate { + if needsLibSiliconPatchUpdate && shouldEnableLibSiliconPatch { if !strings.Contains(updatedContent, libSiliconPatchEntry+"\n") { updatedContent += libSiliconPatchEntry + "\n" debug.Printf("Adding %s to dlls.txt", libSiliconPatchEntry) @@ -212,12 +245,33 @@ func PatchTurtleWoW(myWindow fyne.Window, updateAllStatuses func()) { } } - // Apply shadowLOD setting to Config.wtf for FPS optimization - if err := applyShadowLODSetting(); err != nil { - debug.Printf("Warning: failed to apply shadowLOD setting to Config.wtf: %v", err) + // If user has disabled libSiliconPatch, make sure it's removed from dlls.txt + if !shouldEnableLibSiliconPatch { + if err := disableLibSiliconPatchInDlls(); err != nil { + debug.Printf("Warning: failed to remove libSiliconPatch from dlls.txt: %v", err) + } + } + + // Always apply vertex animation shaders setting to Config.wtf + if err := applyVertexAnimShadersSetting(); err != nil { + debug.Printf("Warning: failed to apply vertex animation shaders setting to Config.wtf: %v", err) // Continue with patching even if Config.wtf update fails } + // Apply shadowLOD setting to Config.wtf for FPS optimization + // Use shouldEnableShadowLOD which accounts for first-time patching + if shouldEnableShadowLOD { + if err := applyShadowLODSetting(); err != nil { + debug.Printf("Warning: failed to apply shadowLOD setting to Config.wtf: %v", err) + // Continue with patching even if Config.wtf update fails + } + } else { + // If user has disabled shadowLOD, make sure it's removed from Config.wtf + if err := removeShadowLODSetting(); err != nil { + debug.Printf("Warning: failed to remove shadowLOD setting from Config.wtf: %v", err) + } + } + debug.Println("TurtleWoW patching with bundled resources completed successfully.") dialog.ShowInformation("Success", "TurtleWoW patching process completed using bundled resources.", myWindow) updateAllStatuses() @@ -356,10 +410,13 @@ func UnpatchTurtleWoW(myWindow fyne.Window, updateAllStatuses func()) { } } - // Remove shadowLOD setting from Config.wtf - if err := removeShadowLODSetting(); err != nil { - debug.Printf("Warning: failed to remove shadowLOD setting from Config.wtf: %v", err) - // Continue with unpatching even if Config.wtf update fails + // Remove shadowLOD setting from Config.wtf - only if it was applied via graphics settings + prefs, _ := utils.LoadPrefs() + if prefs.SetShadowLOD0 { + if err := removeShadowLODSetting(); err != nil { + debug.Printf("Warning: failed to remove shadowLOD setting from Config.wtf: %v", err) + // Continue with unpatching even if Config.wtf update fails + } } debug.Println("TurtleWoW unpatching completed successfully.") @@ -458,6 +515,20 @@ func updateOrAddConfigSetting(configText, setting, value string) string { return configText } +// removeConfigSetting removes a setting from the config text +func removeConfigSetting(configText, setting string) string { + // Create regex pattern to match the setting + pattern := fmt.Sprintf(`SET\s+%s\s+"[^"]*"[\r\n]*`, regexp.QuoteMeta(setting)) + re := regexp.MustCompile(pattern) + + if re.MatchString(configText) { + configText = re.ReplaceAllString(configText, "") + debug.Printf("Removed setting %s from config", setting) + } + + return configText +} + // CheckShadowLODSetting checks if the shadowLOD setting is correctly applied in Config.wtf func CheckShadowLODSetting() bool { if paths.TurtlewowPath == "" { @@ -533,3 +604,325 @@ func removeShadowLODSetting() error { return nil } + +// applyVertexAnimShadersSetting applies the vertex animation shaders setting to Config.wtf +func applyVertexAnimShadersSetting() error { + if paths.TurtlewowPath == "" { + return fmt.Errorf("TurtleWoW path not set") + } + + configPath := filepath.Join(paths.TurtlewowPath, "WTF", "Config.wtf") + + // Create WTF directory if it doesn't exist + wtfDir := filepath.Dir(configPath) + if err := os.MkdirAll(wtfDir, 0755); err != nil { + return fmt.Errorf("failed to create WTF directory: %v", err) + } + + var configText string + + // Read existing config if it exists + if content, err := os.ReadFile(configPath); err == nil { + configText = string(content) + } else { + debug.Printf("Config.wtf not found, creating new file") + configText = "" + } + + // Apply vertex animation shaders setting + configText = updateOrAddConfigSetting(configText, "M2UseShaders", "1") + + // Write the updated config back to file + if err := os.WriteFile(configPath, []byte(configText), 0644); err != nil { + return fmt.Errorf("failed to write Config.wtf: %v", err) + } + + debug.Printf("Successfully applied vertex animation shaders setting to Config.wtf") + return nil +} + +// ApplyGraphicsSettings applies the selected graphics settings to Config.wtf +func ApplyGraphicsSettings(myWindow fyne.Window) error { + prefs, err := utils.LoadPrefs() + if err != nil { + return fmt.Errorf("failed to load preferences: %v", err) + } + + if paths.TurtlewowPath == "" { + return fmt.Errorf("TurtleWoW path not set") + } + + configPath := filepath.Join(paths.TurtlewowPath, "WTF", "Config.wtf") + + // Create WTF directory if it doesn't exist + wtfDir := filepath.Dir(configPath) + if err := os.MkdirAll(wtfDir, 0755); err != nil { + return fmt.Errorf("failed to create WTF directory: %v", err) + } + + var configText string + + // Read existing config if it exists + if content, err := os.ReadFile(configPath); err == nil { + configText = string(content) + } else { + debug.Printf("Config.wtf not found, creating new file") + configText = "" + } + + // Apply or remove graphics settings based on user preferences + if prefs.ReduceTerrainDistance { + configText = updateOrAddConfigSetting(configText, "farclip", "177") + } else { + configText = removeConfigSetting(configText, "farclip") + } + + if prefs.SetMultisampleTo2x { + configText = updateOrAddConfigSetting(configText, "gxMultisample", "2") + } else { + configText = removeConfigSetting(configText, "gxMultisample") + } + + if prefs.SetShadowLOD0 { + configText = updateOrAddConfigSetting(configText, "shadowLOD", "0") + } else { + configText = removeConfigSetting(configText, "shadowLOD") + } + + // Handle libSiliconPatch.dll in dlls.txt (only if DLL exists) + libSiliconPatchPath := filepath.Join(paths.TurtlewowPath, "libSiliconPatch.dll") + if utils.PathExists(libSiliconPatchPath) { + if prefs.EnableLibSiliconPatch { + if err := enableLibSiliconPatchInDlls(); err != nil { + debug.Printf("Warning: failed to enable libSiliconPatch in dlls.txt: %v", err) + } + } else { + if err := disableLibSiliconPatchInDlls(); err != nil { + debug.Printf("Warning: failed to disable libSiliconPatch in dlls.txt: %v", err) + } + } + } + + // Write the updated config back to file + if err := os.WriteFile(configPath, []byte(configText), 0644); err != nil { + return fmt.Errorf("failed to write Config.wtf: %v", err) + } + + debug.Printf("Successfully applied graphics settings to Config.wtf") + return nil +} + +// CheckGraphicsSettings checks if the graphics settings are correctly applied in Config.wtf +func CheckGraphicsSettings() (bool, bool, bool) { + prefs, _ := utils.LoadPrefs() + + if paths.TurtlewowPath == "" { + return false, false, false + } + + configPath := filepath.Join(paths.TurtlewowPath, "WTF", "Config.wtf") + + if _, err := os.Stat(configPath); os.IsNotExist(err) { + return false, false, false + } + + content, err := os.ReadFile(configPath) + if err != nil { + return false, false, false + } + + configText := string(content) + + terrainCorrect := !prefs.ReduceTerrainDistance || isConfigSettingCorrect(configText, "farclip", "177") + multisampleCorrect := !prefs.SetMultisampleTo2x || isConfigSettingCorrect(configText, "gxMultisample", "2") + shadowCorrect := !prefs.SetShadowLOD0 || isConfigSettingCorrect(configText, "shadowLOD", "0") + + return terrainCorrect, multisampleCorrect, shadowCorrect +} + +// LoadGraphicsSettingsFromConfig reads Config.wtf and updates preferences with current settings +func LoadGraphicsSettingsFromConfig() error { + if paths.TurtlewowPath == "" { + return fmt.Errorf("TurtleWoW path not set") + } + + configPath := filepath.Join(paths.TurtlewowPath, "WTF", "Config.wtf") + + // If Config.wtf doesn't exist, nothing to load + if _, err := os.Stat(configPath); os.IsNotExist(err) { + debug.Printf("Config.wtf not found, using default graphics settings") + return nil + } + + content, err := os.ReadFile(configPath) + if err != nil { + return fmt.Errorf("failed to read Config.wtf: %v", err) + } + + configText := string(content) + + // Load current preferences + prefs, err := utils.LoadPrefs() + if err != nil { + return fmt.Errorf("failed to load preferences: %v", err) + } + + // Check each graphics setting and update preferences + prefs.ReduceTerrainDistance = isConfigSettingCorrect(configText, "farclip", "177") + prefs.SetMultisampleTo2x = isConfigSettingCorrect(configText, "gxMultisample", "2") + prefs.SetShadowLOD0 = isConfigSettingCorrect(configText, "shadowLOD", "0") + + // Check libSiliconPatch status (DLL exists and enabled in dlls.txt) + libSiliconPatchPath := filepath.Join(paths.TurtlewowPath, "libSiliconPatch.dll") + dllsTextFile := filepath.Join(paths.TurtlewowPath, "dlls.txt") + libSiliconPatchExists := utils.PathExists(libSiliconPatchPath) + libSiliconPatchEnabled := false + + if libSiliconPatchExists && utils.PathExists(dllsTextFile) { + if dllsContent, err := os.ReadFile(dllsTextFile); err == nil { + libSiliconPatchEnabled = strings.Contains(string(dllsContent), "libSiliconPatch.dll") + } + } + prefs.EnableLibSiliconPatch = libSiliconPatchExists && libSiliconPatchEnabled + + // Save updated preferences + if err := utils.SavePrefs(prefs); err != nil { + return fmt.Errorf("failed to save preferences: %v", err) + } + + debug.Printf("Loaded graphics settings from Config.wtf: terrain=%v, multisample=%v, shadow=%v, libSiliconPatch=%v", + prefs.ReduceTerrainDistance, prefs.SetMultisampleTo2x, prefs.SetShadowLOD0, prefs.EnableLibSiliconPatch) + + return nil +} + +// CheckGraphicsSettingsPresence checks if libSiliconPatch.dll exists and shadowLOD is applied, updates preferences accordingly +func CheckGraphicsSettingsPresence() { + if paths.TurtlewowPath == "" { + return + } + + libSiliconPatchPath := filepath.Join(paths.TurtlewowPath, "libSiliconPatch.dll") + dllsTextFile := filepath.Join(paths.TurtlewowPath, "dlls.txt") + + // Check if libSiliconPatch.dll exists + libSiliconPatchExists := utils.PathExists(libSiliconPatchPath) + + // Check if it's enabled in dlls.txt + libSiliconPatchEnabled := false + if utils.PathExists(dllsTextFile) { + if fileContentBytes, err := os.ReadFile(dllsTextFile); err == nil { + fileContent := string(fileContentBytes) + libSiliconPatchEnabled = strings.Contains(fileContent, "libSiliconPatch.dll") + } + } + + // Check if shadowLOD is currently applied + shadowLODApplied := CheckShadowLODSetting() + + // Load current preferences + prefs, _ := utils.LoadPrefs() + + // Handle libSiliconPatch preference detection + if libSiliconPatchExists { + if libSiliconPatchEnabled && !prefs.EnableLibSiliconPatch { + // DLL is currently enabled but user preference says disabled - likely first run detection + prefs.EnableLibSiliconPatch = true + debug.Printf("libSiliconPatch detected as enabled, setting user preference to enabled") + } else if !libSiliconPatchEnabled && prefs.EnableLibSiliconPatch { + // DLL exists but not enabled, user preference says enabled - respect user choice + debug.Printf("libSiliconPatch disabled in dlls.txt but user preference is enabled - keeping user preference") + } + } + + // Handle shadowLOD preference detection - enable by default if currently applied + if shadowLODApplied && !prefs.SetShadowLOD0 { + // shadowLOD is currently applied but user preference says disabled - likely first run detection + prefs.SetShadowLOD0 = true + debug.Printf("shadowLOD detected as applied, setting user preference to enabled") + } else if !shadowLODApplied && prefs.SetShadowLOD0 { + // shadowLOD not applied but user preference says enabled - respect user choice + debug.Printf("shadowLOD not applied but user preference is enabled - keeping user preference") + } + + // Save any changes + utils.SavePrefs(prefs) + + debug.Printf("Graphics settings detection: libSiliconPatch exists=%v, enabled_in_dlls=%v, user_setting=%v; shadowLOD applied=%v, user_setting=%v", + libSiliconPatchExists, libSiliconPatchEnabled, prefs.EnableLibSiliconPatch, shadowLODApplied, prefs.SetShadowLOD0) +} + +// enableLibSiliconPatchInDlls adds libSiliconPatch.dll to dlls.txt if not present +func enableLibSiliconPatchInDlls() error { + if paths.TurtlewowPath == "" { + return fmt.Errorf("TurtleWoW path not set") + } + + dllsTextFile := filepath.Join(paths.TurtlewowPath, "dlls.txt") + libSiliconPatchEntry := "libSiliconPatch.dll" + + var fileContentBytes []byte + var err error + if utils.PathExists(dllsTextFile) { + fileContentBytes, err = os.ReadFile(dllsTextFile) + if err != nil { + return fmt.Errorf("failed to read dlls.txt: %v", err) + } + } + + currentContent := string(fileContentBytes) + if strings.Contains(currentContent, libSiliconPatchEntry) { + debug.Printf("libSiliconPatch.dll already present in dlls.txt") + return nil + } + + // Add libSiliconPatch.dll to dlls.txt + if len(currentContent) > 0 && !strings.HasSuffix(currentContent, "\n") { + currentContent += "\n" + } + currentContent += libSiliconPatchEntry + "\n" + + if err := os.WriteFile(dllsTextFile, []byte(currentContent), 0644); err != nil { + return fmt.Errorf("failed to update dlls.txt: %v", err) + } + + debug.Printf("Added libSiliconPatch.dll to dlls.txt") + return nil +} + +// disableLibSiliconPatchInDlls removes libSiliconPatch.dll from dlls.txt +func disableLibSiliconPatchInDlls() error { + if paths.TurtlewowPath == "" { + return fmt.Errorf("TurtleWoW path not set") + } + + dllsTextFile := filepath.Join(paths.TurtlewowPath, "dlls.txt") + + if !utils.PathExists(dllsTextFile) { + debug.Printf("dlls.txt not found, nothing to remove") + return nil + } + + content, err := os.ReadFile(dllsTextFile) + if err != nil { + return fmt.Errorf("failed to read dlls.txt: %v", err) + } + + lines := strings.Split(string(content), "\n") + filteredLines := make([]string, 0, len(lines)) + + for _, line := range lines { + trimmedLine := strings.TrimSpace(line) + if trimmedLine != "libSiliconPatch.dll" { + filteredLines = append(filteredLines, line) + } + } + + updatedContent := strings.Join(filteredLines, "\n") + if err := os.WriteFile(dllsTextFile, []byte(updatedContent), 0644); err != nil { + return fmt.Errorf("failed to update dlls.txt: %v", err) + } + + debug.Printf("Removed libSiliconPatch.dll from dlls.txt") + return nil +} diff --git a/pkg/ui/components.go b/pkg/ui/components.go index 9cf5aae..293ce22 100644 --- a/pkg/ui/components.go +++ b/pkg/ui/components.go @@ -73,7 +73,7 @@ func createOptionsComponents() { debug.Printf("Successfully applied recommended settings") // Show success dialog if we have a window reference if currentWindow != nil { - dialog.ShowInformation("Success", "Recommended graphics settings have been applied to Config.wtf", currentWindow) + dialog.ShowInformation("Success", "Recommended graphics settings have been applied", currentWindow) } // Update button state updateRecommendedSettingsButton() @@ -92,6 +92,9 @@ func createOptionsComponents() { // Create Wine registry Option-as-Alt buttons and status createWineRegistryComponents() + // Create graphics settings components + createGraphicsSettingsComponents() + // Load environment variables from preferences if prefs.EnvironmentVariables != "" { launcher.CustomEnvVars = prefs.EnvironmentVariables @@ -476,3 +479,187 @@ func showRecommendedSettingsHelpPopup() { helpPopup.Show() } + +// createGraphicsSettingsComponents creates all graphics settings checkboxes and buttons +func createGraphicsSettingsComponents() { + // Load preferences for initial values + prefs, _ := utils.LoadPrefs() + + // Create Reduce Terrain Distance setting with help button + reduceTerrainDistanceCheckbox = widget.NewCheck("", func(checked bool) { + prefs, _ := utils.LoadPrefs() + prefs.ReduceTerrainDistance = checked + utils.SavePrefs(prefs) + debug.Printf("Reduce terrain distance: %v", checked) + updateApplyGraphicsSettingsButton() + }) + reduceTerrainDistanceCheckbox.SetChecked(prefs.ReduceTerrainDistance) + + reduceTerrainDistanceHelpButton = widget.NewButton("?", func() { + showGraphicsSettingHelpPopup("Reduce Terrain Distance", "Sets the draw distance to the lowest setting. This will drastically increase your FPS", "High Performance Impact") + }) + reduceTerrainDistanceHelpButton.Importance = widget.MediumImportance + + // Create Set Multisample to 2x setting with help button + setMultisampleTo2xCheckbox = widget.NewCheck("", func(checked bool) { + prefs, _ := utils.LoadPrefs() + prefs.SetMultisampleTo2x = checked + utils.SavePrefs(prefs) + debug.Printf("Set multisample to 2x: %v", checked) + updateApplyGraphicsSettingsButton() + }) + setMultisampleTo2xCheckbox.SetChecked(prefs.SetMultisampleTo2x) + + setMultisampleTo2xHelpButton = widget.NewButton("?", func() { + showGraphicsSettingHelpPopup("Set Multisample to 2x", "Might reduce your FPS slightly on lower end machines, but makes sure the portraits load properly.", "Medium Performance Impact") + }) + setMultisampleTo2xHelpButton.Importance = widget.MediumImportance + + // Create Set Shadow LOD to 0 setting with help button + setShadowLOD0Checkbox = widget.NewCheck("", func(checked bool) { + prefs, _ := utils.LoadPrefs() + prefs.SetShadowLOD0 = checked + + // Track if user manually disabled this setting + if !checked { + prefs.UserDisabledShadowLOD = true + } else { + prefs.UserDisabledShadowLOD = false + } + + utils.SavePrefs(prefs) + debug.Printf("Set shadow LOD to 0: %v (user manually changed)", checked) + updateApplyGraphicsSettingsButton() + }) + setShadowLOD0Checkbox.SetChecked(prefs.SetShadowLOD0) + + setShadowLOD0HelpButton = widget.NewButton("?", func() { + showGraphicsSettingHelpPopup("Set Shadow LOD to 0", "Turns off all shadows. This will give you ~10% more FPS.", "High Performance Impact") + }) + setShadowLOD0HelpButton.Importance = widget.MediumImportance + + // Create Enable libSiliconPatch setting with help button + libSiliconPatchCheckbox = widget.NewCheck("", func(checked bool) { + prefs, _ := utils.LoadPrefs() + prefs.EnableLibSiliconPatch = checked + + // Track if user manually disabled this setting + if !checked { + prefs.UserDisabledLibSiliconPatch = true + } else { + prefs.UserDisabledLibSiliconPatch = false + } + + utils.SavePrefs(prefs) + debug.Printf("Enable libSiliconPatch: %v (user manually changed)", checked) + updateApplyGraphicsSettingsButton() + }) + libSiliconPatchCheckbox.SetChecked(prefs.EnableLibSiliconPatch) + + libSiliconPatchHelpButton = widget.NewButton("?", func() { + showGraphicsSettingHelpPopup("Enable libSiliconPatch", "Hooks into the WoW process and replaces slow X87 instructions with SSE2 instructions that Rosetta can translate much quicker, resulting in an increase in FPS (2x or more). May potentially cause graphical bugs.", "Very High Performance Impact") + }) + libSiliconPatchHelpButton.Importance = widget.MediumImportance + + applyGraphicsSettingsButton = widget.NewButton("Apply Graphics Settings", func() { + err := patching.ApplyGraphicsSettings(currentWindow) + if err != nil { + debug.Printf("Failed to apply graphics settings: %v", err) + if currentWindow != nil { + dialog.ShowError(fmt.Errorf("failed to apply graphics settings: %v", err), currentWindow) + } + } else { + debug.Printf("Successfully applied graphics settings") + if currentWindow != nil { + dialog.ShowInformation("Success", "Graphics settings have been applied", currentWindow) + } + // Refresh checkboxes to reflect current state + refreshGraphicsSettingsCheckboxes() + } + }) + applyGraphicsSettingsButton.Importance = widget.MediumImportance + + // Initialize button state + updateApplyGraphicsSettingsButton() +} + +// updateApplyGraphicsSettingsButton updates the state of the apply graphics settings button +func updateApplyGraphicsSettingsButton() { + if applyGraphicsSettingsButton == nil { + return + } + + // Always enable the button since we need to handle both adding and removing settings + applyGraphicsSettingsButton.Enable() + applyGraphicsSettingsButton.SetText("Apply Changes") +} + +// refreshGraphicsSettingsCheckboxes updates the checkbox states from current preferences +func refreshGraphicsSettingsCheckboxes() { + prefs, _ := utils.LoadPrefs() + + if reduceTerrainDistanceCheckbox != nil { + reduceTerrainDistanceCheckbox.SetChecked(prefs.ReduceTerrainDistance) + } + if setMultisampleTo2xCheckbox != nil { + setMultisampleTo2xCheckbox.SetChecked(prefs.SetMultisampleTo2x) + } + if setShadowLOD0Checkbox != nil { + setShadowLOD0Checkbox.SetChecked(prefs.SetShadowLOD0) + } + if libSiliconPatchCheckbox != nil { + libSiliconPatchCheckbox.SetChecked(prefs.EnableLibSiliconPatch) + } + + // Update the apply button state + updateApplyGraphicsSettingsButton() +} + +// showGraphicsSettingHelpPopup shows a help popup for a specific graphics setting +func showGraphicsSettingHelpPopup(title, description, impact string) { + if currentWindow == nil { + return + } + + // Create help content + helpTitle := widget.NewRichTextFromMarkdown("# " + title) + + descriptionLabel := widget.NewLabel(description) + descriptionLabel.Wrapping = fyne.TextWrapWord + + impactLabel := widget.NewLabel(impact) + impactLabel.TextStyle = fyne.TextStyle{Bold: true} + + // Create OK button + okButton := widget.NewButton("OK", func() { + // This will be set when the popup is created + }) + okButton.Importance = widget.MediumImportance + + // Create help content container + helpContentContainer := container.NewVBox( + container.NewCenter(helpTitle), + widget.NewSeparator(), + descriptionLabel, + widget.NewSeparator(), + impactLabel, + widget.NewSeparator(), + container.NewCenter(okButton), + ) + + // Calculate popup size + windowSize := currentWindow.Content().Size() + popupWidth := windowSize.Width * 2 / 3 + popupHeight := windowSize.Height / 2 + + // Create the help popup + helpPopup := widget.NewModalPopUp(container.NewPadded(helpContentContainer), currentWindow.Canvas()) + helpPopup.Resize(fyne.NewSize(popupWidth, popupHeight)) + + // Set the OK button action to hide the help popup + okButton.OnTapped = func() { + helpPopup.Hide() + } + + helpPopup.Show() +} diff --git a/pkg/ui/popup.go b/pkg/ui/popup.go index 0c9b509..9b2ba0c 100644 --- a/pkg/ui/popup.go +++ b/pkg/ui/popup.go @@ -13,6 +13,8 @@ import ( "howett.net/plist" + "turtlesilicon/pkg/debug" + "turtlesilicon/pkg/patching" "turtlesilicon/pkg/paths" "turtlesilicon/pkg/utils" ) @@ -23,25 +25,84 @@ func showOptionsPopup() { return } - // Create options content with better organization and smaller titles - optionsTitle := widget.NewLabel("Options") - optionsTitle.TextStyle = fyne.TextStyle{Bold: true} - // Create label for recommended settings - recommendedSettingsLabel := widget.NewLabel("Graphics settings:") + // Check graphics settings presence and update preferences before showing UI + patching.CheckGraphicsSettingsPresence() - gameOptionsContainer := container.NewVBox( - optionsTitle, + // Load graphics settings from Config.wtf and update preferences + if err := patching.LoadGraphicsSettingsFromConfig(); err != nil { + debug.Printf("Warning: failed to load graphics settings from Config.wtf: %v", err) + } + + // Refresh checkbox states to reflect current settings + refreshGraphicsSettingsCheckboxes() + + // Create General tab content + generalTitle := widget.NewLabel("General Settings") + generalTitle.TextStyle = fyne.TextStyle{Bold: true} + + generalContainer := container.NewVBox( + generalTitle, widget.NewSeparator(), metalHudCheckbox, showTerminalCheckbox, vanillaTweaksCheckbox, autoDeleteWdbCheckbox, widget.NewSeparator(), - container.NewBorder(nil, nil, recommendedSettingsLabel, container.NewHBox(applyRecommendedSettingsButton, recommendedSettingsHelpButton), nil), - widget.NewSeparator(), container.NewBorder(nil, nil, nil, container.NewHBox(enableOptionAsAltButton, disableOptionAsAltButton), optionAsAltStatusLabel), ) + // Create Graphics tab content + graphicsTitle := widget.NewLabel("Graphics Settings") + graphicsTitle.TextStyle = fyne.TextStyle{Bold: true} + + graphicsDescription := widget.NewLabel("Select graphics settings to apply to Config.wtf:") + graphicsDescription.TextStyle = fyne.TextStyle{Italic: true} + + // Create bold text labels for each setting + terrainLabel := widget.NewLabel("Reduce Terrain Distance") + terrainLabel.TextStyle = fyne.TextStyle{Bold: true} + + multisampleLabel := widget.NewLabel("Set Multisample to 2x") + multisampleLabel.TextStyle = fyne.TextStyle{Bold: true} + + shadowLabel := widget.NewLabel("Set Shadow LOD to 0") + shadowLabel.TextStyle = fyne.TextStyle{Bold: true} + + libSiliconPatchLabel := widget.NewLabel("Enable libSiliconPatch") + libSiliconPatchLabel.TextStyle = fyne.TextStyle{Bold: true} + + // Create setting rows with help buttons between checkbox and label + terrainRow := container.NewHBox( + reduceTerrainDistanceCheckbox, + reduceTerrainDistanceHelpButton, + terrainLabel) + multisampleRow := container.NewHBox( + setMultisampleTo2xCheckbox, + setMultisampleTo2xHelpButton, + multisampleLabel) + shadowRow := container.NewHBox( + setShadowLOD0Checkbox, + setShadowLOD0HelpButton, + shadowLabel) + libSiliconPatchRow := container.NewHBox( + libSiliconPatchCheckbox, + libSiliconPatchHelpButton, + libSiliconPatchLabel) + + graphicsContainer := container.NewVBox( + graphicsTitle, + widget.NewSeparator(), + graphicsDescription, + widget.NewSeparator(), + terrainRow, + multisampleRow, + shadowRow, + libSiliconPatchRow, + widget.NewSeparator(), + container.NewCenter(applyGraphicsSettingsButton), + ) + + // Create Environment Variables tab content envVarsTitle := widget.NewLabel("Environment Variables") envVarsTitle.TextStyle = fyne.TextStyle{Bold: true} envVarsContainer := container.NewVBox( @@ -50,13 +111,15 @@ func showOptionsPopup() { envVarsEntry, ) - // Create a scrollable container for all options - optionsContent := container.NewVBox( - gameOptionsContainer, - envVarsContainer, + // Create tabs + tabs := container.NewAppTabs( + container.NewTabItem("General", container.NewScroll(generalContainer)), + container.NewTabItem("Graphics", container.NewScroll(graphicsContainer)), + container.NewTabItem("Environment", container.NewScroll(envVarsContainer)), ) - scrollContainer := container.NewScroll(optionsContent) + // Set tab location to top + tabs.SetTabLocation(container.TabLocationTop) // Create close button closeButton := widget.NewButton("Close", func() { @@ -65,11 +128,11 @@ func showOptionsPopup() { // Create the popup content with close button popupContent := container.NewBorder( - nil, // top - container.NewCenter(closeButton), // bottom - nil, // left - nil, // right - container.NewPadded(scrollContainer), // center + nil, // top + container.NewCenter(closeButton), // bottom + nil, // left + nil, // right + container.NewPadded(tabs), // center ) // Get the window size and calculate 2/3 size diff --git a/pkg/ui/status.go b/pkg/ui/status.go index 65a139a..9963474 100644 --- a/pkg/ui/status.go +++ b/pkg/ui/status.go @@ -97,7 +97,15 @@ func updateTurtleWoWStatus() { if utils.PathExists(dllsTextFile) { if fileContent, err := os.ReadFile(dllsTextFile); err == nil { contentStr := string(fileContent) - if strings.Contains(contentStr, "winerosetta.dll") && strings.Contains(contentStr, "libSiliconPatch.dll") { + winerosettaPresent := strings.Contains(contentStr, "winerosetta.dll") + + // Check if libSiliconPatch should be present based on user preference + prefs, _ := utils.LoadPrefs() + libSiliconPatchRequired := prefs.EnableLibSiliconPatch + libSiliconPatchPresent := strings.Contains(contentStr, "libSiliconPatch.dll") + + // Validate dlls.txt: winerosetta must be present, libSiliconPatch based on setting + if winerosettaPresent && (!libSiliconPatchRequired || libSiliconPatchPresent) { dllsFileValid = true } } @@ -110,14 +118,18 @@ func updateTurtleWoWStatus() { rosettaX87CorrectSize := utils.CompareFileWithBundledResource(rosettaX87ExePath, "rosettax87/rosettax87") libRuntimeRosettaX87CorrectSize := utils.CompareFileWithBundledResource(libRuntimeRosettaX87Path, "rosettax87/libRuntimeRosettax87") - // Check if shadowLOD setting is applied - shadowLODApplied := patching.CheckShadowLODSetting() + // Check if shadowLOD setting is applied (only if user has enabled it in graphics settings) + prefs, _ := utils.LoadPrefs() + shadowLODRequiredAndApplied := true // Default to true if not required + if prefs.SetShadowLOD0 { + shadowLODRequiredAndApplied = patching.CheckShadowLODSetting() + } if utils.PathExists(winerosettaDllPath) && utils.PathExists(d3d9DllPath) && utils.PathExists(libSiliconPatchDllPath) && utils.DirExists(rosettaX87DirPath) && utils.PathExists(rosettaX87ExePath) && utils.PathExists(libRuntimeRosettaX87Path) && dllsFileValid && winerosettaDllCorrectSize && d3d9DllCorrectSize && libSiliconPatchCorrectSize && - rosettaX87CorrectSize && libRuntimeRosettaX87CorrectSize && shadowLODApplied { + rosettaX87CorrectSize && libRuntimeRosettaX87CorrectSize && shadowLODRequiredAndApplied { paths.PatchesAppliedTurtleWoW = true } } diff --git a/pkg/ui/ui.go b/pkg/ui/ui.go index 3d316fe..6e15b7e 100644 --- a/pkg/ui/ui.go +++ b/pkg/ui/ui.go @@ -1,7 +1,9 @@ package ui import ( + "turtlesilicon/pkg/debug" "turtlesilicon/pkg/paths" + "turtlesilicon/pkg/patching" "turtlesilicon/pkg/utils" "fyne.io/fyne/v2" @@ -35,6 +37,18 @@ func CreateUI(myWindow fyne.Window) fyne.CanvasObject { // Check default CrossOver path paths.CheckDefaultCrossOverPath() + // Check graphics settings presence and set default state + patching.CheckGraphicsSettingsPresence() + + // Load graphics settings from Config.wtf and update UI + if err := patching.LoadGraphicsSettingsFromConfig(); err != nil { + // Log error but continue - this is not critical for app startup + debug.Printf("Warning: failed to load graphics settings from Config.wtf: %v", err) + } else { + // Refresh checkbox states to reflect loaded settings + refreshGraphicsSettingsCheckboxes() + } + // Create header, main content and bottom bar headerContent := createHeaderContainer() mainContent := createMainContent(myWindow) diff --git a/pkg/ui/variables.go b/pkg/ui/variables.go index 75c2114..818c365 100644 --- a/pkg/ui/variables.go +++ b/pkg/ui/variables.go @@ -45,6 +45,19 @@ var ( // Environment variables entry envVarsEntry *widget.Entry + // Graphics settings checkboxes + reduceTerrainDistanceCheckbox *widget.Check + setMultisampleTo2xCheckbox *widget.Check + setShadowLOD0Checkbox *widget.Check + libSiliconPatchCheckbox *widget.Check + applyGraphicsSettingsButton *widget.Button + + // Graphics settings help buttons + reduceTerrainDistanceHelpButton *widget.Button + setMultisampleTo2xHelpButton *widget.Button + setShadowLOD0HelpButton *widget.Button + libSiliconPatchHelpButton *widget.Button + // Window reference for popup functionality currentWindow fyne.Window diff --git a/pkg/utils/prefs.go b/pkg/utils/prefs.go index 9f53661..54ec1a1 100644 --- a/pkg/utils/prefs.go +++ b/pkg/utils/prefs.go @@ -16,6 +16,16 @@ type UserPrefs struct { EnableVanillaTweaks bool `json:"enable_vanilla_tweaks"` RemapOptionAsAlt bool `json:"remap_option_as_alt"` AutoDeleteWdb bool `json:"auto_delete_wdb"` + + // Graphics settings + ReduceTerrainDistance bool `json:"reduce_terrain_distance"` + SetMultisampleTo2x bool `json:"set_multisample_to_2x"` + SetShadowLOD0 bool `json:"set_shadow_lod_0"` + EnableLibSiliconPatch bool `json:"enable_lib_silicon_patch"` + + // Tracking whether user has manually disabled these settings + UserDisabledShadowLOD bool `json:"user_disabled_shadow_lod"` + UserDisabledLibSiliconPatch bool `json:"user_disabled_lib_silicon_patch"` } func getPrefsPath() (string, error) {