2 Commits

Author SHA1 Message Date
Omni
d9ea1be347 Sync from development - prepare for v0.1.6.1 2025-10-21 21:11:48 +01:00
Omni
a8862475d4 Sync from development - prepare for v0.1.6.1 2025-10-21 21:07:42 +01:00
9 changed files with 217 additions and 74 deletions

View File

@@ -1,28 +1,21 @@
# Jackify Changelog # Jackify Changelog
## v0.1.6 - Advanced Proton Management & Lorerim Support ## v0.1.6.1 - Fix dotnet40 install and expand Game Proton override
**Release Date:** October 21, 2025
### Bug Fixes
- **Fixed dotnet40 Installation Failures**: Resolved widespread .NET Framework installation issues affecting multiple modlists
- **Added Lost Legacy Proton 9 Override**: Automatic ENB compatibility for Lost Legacy modlist
- **Fixed Symlinked Downloads**: Automatically handles symlinked download directories to avoid Wine compatibility issues
---
## v0.1.6 - Lorerim Proton Support
**Release Date:** October 16, 2025 **Release Date:** October 16, 2025
### Major New Features ### New Features
- **Dual Proton Configuration**: Separate Install Proton and Game Proton version selection in Settings - **Lorerim Proton Override**: Automatically selects Proton 9 for Lorerim installations (GE-Proton9-27 preferred)
- **Install Proton**: Optimized for modlist installation and texture processing (Experimental/GE-Proton 10+ recommended for performance) - **Engine Update**: jackify-engine v0.3.17
- **Game Proton**: For game shortcuts (supports any Proton 9+ version)
- Independent configuration allows users to optimize for both installation speed and game compatibility
- **Lorerim Proton Override**: Automatic Proton 9 selection for Lorerim modlist installations
- Priority system: GE-Proton9-27 → Other GE-Proton 9 versions → Valve Proton 9 → user settings fallback
- User notification when override is applied
- Case-insensitive detection for Lorerim modlists
- **Configurable Component Installation Method**: User-selectable toggle in Settings
- **Optimized Mode** (default): Protontricks for dotnet40 (reliable), winetricks for other components (fast)
- **Legacy Mode**: Protontricks for all components (slower but maximum compatibility)
### Engine & Technical Improvements
- **jackify-engine v0.3.17**: Latest engine version with performance improvements
- **Windows 10 Prefix Timing**: Improved timing to match legacy script behavior
- **Self-Updater Enhancement**: Fixed auto-restart checkbox functionality
- **ProtontricksHandler**: Updated constructor calls across codebase for consistency
--- ---

View File

@@ -5,4 +5,4 @@ This package provides both CLI and GUI interfaces for managing
Wabbajack modlists natively on Linux systems. Wabbajack modlists natively on Linux systems.
""" """
__version__ = "0.1.6" __version__ = "0.1.6.1"

View File

@@ -80,7 +80,7 @@ class ModlistHandler:
"lsiv": ["dotnet40"], "lsiv": ["dotnet40"],
"ls4": ["dotnet40"], "ls4": ["dotnet40"],
"lorerim": ["dotnet40"], "lorerim": ["dotnet40"],
"lostlegacy": ["dotnet48"], "lostlegacy": ["dotnet40"],
} }
def __init__(self, steam_path_or_config: Union[Dict, str, Path, None] = None, def __init__(self, steam_path_or_config: Union[Dict, str, Path, None] = None,
@@ -737,6 +737,14 @@ class ModlistHandler:
self.logger.info(f"ModOrganizer.ini backed up to: {backup_path}") self.logger.info(f"ModOrganizer.ini backed up to: {backup_path}")
self.logger.info("Step 6: Backing up ModOrganizer.ini... Done") self.logger.info("Step 6: Backing up ModOrganizer.ini... Done")
# Step 6.5: Handle symlinked downloads directory
if status_callback:
status_callback(f"{self._get_progress_timestamp()} Checking for symlinked downloads directory")
self.logger.info("Step 6.5: Checking for symlinked downloads directory...")
if not self._handle_symlinked_downloads():
self.logger.warning("Warning during symlink handling (non-critical)")
self.logger.info("Step 6.5: Checking for symlinked downloads directory... Done")
# Step 7a: Detect Stock Game/Game Root path # Step 7a: Detect Stock Game/Game Root path
if status_callback: if status_callback:
status_callback(f"{self._get_progress_timestamp()} Detecting stock game path") status_callback(f"{self._get_progress_timestamp()} Detecting stock game path")
@@ -1346,19 +1354,111 @@ class ModlistHandler:
self.logger.warning("Cannot re-enforce Windows 10 mode - prefix path not found") self.logger.warning("Cannot re-enforce Windows 10 mode - prefix path not found")
return return
# Get wine binary path # Use winetricks handler to set Windows 10 mode
wine_binary = PathHandler.get_wine_binary_for_appid(str(self.appid)) winetricks_handler = WinetricksHandler()
wine_binary = winetricks_handler._get_wine_binary_for_prefix(str(prefix_path))
if not wine_binary: if not wine_binary:
self.logger.warning("Cannot re-enforce Windows 10 mode - wine binary not found") self.logger.warning("Cannot re-enforce Windows 10 mode - wine binary not found")
return return
# Use winetricks handler to set Windows 10 mode
winetricks_handler = WinetricksHandler()
winetricks_handler._set_windows_10_mode(str(prefix_path), wine_binary) winetricks_handler._set_windows_10_mode(str(prefix_path), wine_binary)
self.logger.info("Windows 10 mode re-enforced after modlist-specific configurations") self.logger.info("Windows 10 mode re-enforced after modlist-specific configurations")
except Exception as e: except Exception as e:
self.logger.warning(f"Error re-enforcing Windows 10 mode: {e}") self.logger.warning(f"Error re-enforcing Windows 10 mode: {e}")
def _handle_symlinked_downloads(self) -> bool:
"""
Check if downloads_directory in ModOrganizer.ini points to a symlink.
If it does, comment out the line to force MO2 to use default behavior.
Returns:
bool: True on success or no action needed, False on error
"""
try:
import configparser
import os
if not self.modlist_ini or not os.path.exists(self.modlist_ini):
self.logger.warning("ModOrganizer.ini not found for symlink check")
return True # Non-critical
# Read the INI file
config = configparser.ConfigParser(allow_no_value=True, delimiters=['='])
config.optionxform = str # Preserve case sensitivity
try:
# Read file manually to handle BOM
with open(self.modlist_ini, 'r', encoding='utf-8-sig') as f:
config.read_file(f)
except UnicodeDecodeError:
with open(self.modlist_ini, 'r', encoding='latin-1') as f:
config.read_file(f)
# Check if downloads_directory or download_directory exists and is a symlink
downloads_key = None
downloads_path = None
if 'General' in config:
# Check for both possible key names
if 'downloads_directory' in config['General']:
downloads_key = 'downloads_directory'
downloads_path = config['General']['downloads_directory']
elif 'download_directory' in config['General']:
downloads_key = 'download_directory'
downloads_path = config['General']['download_directory']
if downloads_path:
if downloads_path and os.path.exists(downloads_path):
# Check if the path or any parent directory contains symlinks
def has_symlink_in_path(path):
"""Check if path or any parent directory is a symlink"""
current_path = Path(path).resolve()
check_path = Path(path)
# Walk up the path checking each component
for parent in [check_path] + list(check_path.parents):
if parent.is_symlink():
return True, str(parent)
return False, None
has_symlink, symlink_path = has_symlink_in_path(downloads_path)
if has_symlink:
self.logger.info(f"Detected symlink in downloads directory path: {symlink_path} -> {downloads_path}")
self.logger.info("Commenting out downloads_directory to avoid Wine symlink issues")
# Read the file manually to preserve comments and formatting
with open(self.modlist_ini, 'r', encoding='utf-8') as f:
lines = f.readlines()
# Find and comment out the downloads directory line
modified = False
for i, line in enumerate(lines):
if line.strip().startswith(f'{downloads_key}='):
lines[i] = '#' + line # Comment out the line
modified = True
break
if modified:
# Write the modified file back
with open(self.modlist_ini, 'w', encoding='utf-8') as f:
f.writelines(lines)
self.logger.info(f"{downloads_key} line commented out successfully")
else:
self.logger.warning("downloads_directory line not found in file")
else:
self.logger.debug(f"downloads_directory is not a symlink: {downloads_path}")
else:
self.logger.debug("downloads_directory path does not exist or is empty")
else:
self.logger.debug("No downloads_directory found in ModOrganizer.ini")
return True
except Exception as e:
self.logger.error(f"Error handling symlinked downloads: {e}", exc_info=True)
return False
# (Ensure EOF is clean and no extra incorrect methods exist below) # (Ensure EOF is clean and no extra incorrect methods exist below)

View File

@@ -488,7 +488,7 @@ class ProtontricksHandler:
if "ShowDotFiles" not in content: if "ShowDotFiles" not in content:
logger.debug(f"Adding ShowDotFiles entry to {user_reg_path}") logger.debug(f"Adding ShowDotFiles entry to {user_reg_path}")
with open(user_reg_path, 'a', encoding='utf-8') as f: with open(user_reg_path, 'a', encoding='utf-8') as f:
f.write('\n[Software\\Wine] 1603891765\n') f.write('\n[Software\\Wine] 1603891765\n')
f.write('"ShowDotFiles"="Y"\n') f.write('"ShowDotFiles"="Y"\n')
dotfiles_set_success = True # Count file write as success too dotfiles_set_success = True # Count file write as success too
else: else:
@@ -497,7 +497,7 @@ class ProtontricksHandler:
else: else:
logger.warning(f"user.reg not found at {user_reg_path}, creating it.") logger.warning(f"user.reg not found at {user_reg_path}, creating it.")
with open(user_reg_path, 'w', encoding='utf-8') as f: with open(user_reg_path, 'w', encoding='utf-8') as f:
f.write('[Software\\Wine] 1603891765\n') f.write('[Software\\Wine] 1603891765\n')
f.write('"ShowDotFiles"="Y"\n') f.write('"ShowDotFiles"="Y"\n')
dotfiles_set_success = True # Creating file counts as success dotfiles_set_success = True # Creating file counts as success
except Exception as e: except Exception as e:

View File

@@ -262,10 +262,18 @@ class WinetricksHandler:
config_handler = ConfigHandler() config_handler = ConfigHandler()
use_winetricks = config_handler.get('use_winetricks_for_components', True) use_winetricks = config_handler.get('use_winetricks_for_components', True)
# Legacy .NET Framework versions that are problematic in Wine/Proton
legacy_dotnet_versions = ['dotnet40', 'dotnet472', 'dotnet48']
# Check if any legacy .NET Framework versions are present
has_legacy_dotnet = any(comp in components_to_install for comp in legacy_dotnet_versions)
# Choose installation method based on user preference and components # Choose installation method based on user preference and components
if use_winetricks and "dotnet40" in components_to_install: # ALWAYS use hybrid approach when legacy .NET Framework versions are present
self.logger.info("Using optimized approach: protontricks for dotnet40 (reliable), winetricks for other components (fast)") if has_legacy_dotnet:
return self._install_components_hybrid_approach(components_to_install, wineprefix, game_var) legacy_found = [comp for comp in legacy_dotnet_versions if comp in components_to_install]
self.logger.info(f"Using hybrid approach: protontricks for legacy .NET versions {legacy_found} (reliable), {'winetricks' if use_winetricks else 'protontricks'} for other components")
return self._install_components_hybrid_approach(components_to_install, wineprefix, game_var, use_winetricks)
elif not use_winetricks: elif not use_winetricks:
self.logger.info("Using legacy approach: protontricks for all components") self.logger.info("Using legacy approach: protontricks for all components")
return self._install_components_protontricks_only(components_to_install, wineprefix, game_var) return self._install_components_protontricks_only(components_to_install, wineprefix, game_var)
@@ -453,7 +461,7 @@ class WinetricksHandler:
) )
if result.returncode == 0: if result.returncode == 0:
self.logger.info(f"{component} installed successfully") self.logger.info(f"{component} installed successfully")
component_success = True component_success = True
break break
else: else:
@@ -467,13 +475,13 @@ class WinetricksHandler:
try: try:
with open(log_path, 'r') as f: with open(log_path, 'r') as f:
if 'dotnet40' in f.read(): if 'dotnet40' in f.read():
self.logger.info("dotnet40 confirmed in winetricks.log") self.logger.info("dotnet40 confirmed in winetricks.log")
component_success = True component_success = True
break break
except Exception as e: except Exception as e:
self.logger.warning(f"Could not read winetricks.log: {e}") self.logger.warning(f"Could not read winetricks.log: {e}")
self.logger.error(f"{component} failed (attempt {attempt}): {result.stderr.strip()}") self.logger.error(f"{component} failed (attempt {attempt}): {result.stderr.strip()}")
self.logger.debug(f"Full stdout for {component}: {result.stdout.strip()}") self.logger.debug(f"Full stdout for {component}: {result.stdout.strip()}")
except Exception as e: except Exception as e:
@@ -483,63 +491,70 @@ class WinetricksHandler:
self.logger.error(f"Failed to install {component} after {max_attempts} attempts") self.logger.error(f"Failed to install {component} after {max_attempts} attempts")
return False return False
self.logger.info("All components installed successfully using separate sessions") self.logger.info("All components installed successfully using separate sessions")
# Set Windows 10 mode after all component installation (matches legacy script timing) # Set Windows 10 mode after all component installation (matches legacy script timing)
self._set_windows_10_mode(wineprefix, env.get('WINE', '')) self._set_windows_10_mode(wineprefix, env.get('WINE', ''))
return True return True
def _install_components_hybrid_approach(self, components: list, wineprefix: str, game_var: str) -> bool: def _install_components_hybrid_approach(self, components: list, wineprefix: str, game_var: str, use_winetricks: bool = True) -> bool:
""" """
Hybrid approach: Install dotnet40 with protontricks (known to work), Hybrid approach: Install legacy .NET Framework versions with protontricks (reliable),
then install remaining components with winetricks (faster for other components). then install remaining components with winetricks OR protontricks based on user preference.
Args: Args:
components: List of all components to install components: List of all components to install
wineprefix: Wine prefix path wineprefix: Wine prefix path
game_var: Game variable for AppID detection game_var: Game variable for AppID detection
use_winetricks: Whether to use winetricks for non-legacy components
Returns: Returns:
bool: True if all installations succeeded, False otherwise bool: True if all installations succeeded, False otherwise
""" """
self.logger.info("Starting hybrid installation approach") self.logger.info("Starting hybrid installation approach")
# Separate dotnet40 (protontricks) from other components (winetricks) # Legacy .NET Framework versions that need protontricks
protontricks_components = [comp for comp in components if comp == "dotnet40"] legacy_dotnet_versions = ['dotnet40', 'dotnet472', 'dotnet48']
other_components = [comp for comp in components if comp != "dotnet40"]
# Separate legacy .NET (protontricks) from other components (winetricks)
protontricks_components = [comp for comp in components if comp in legacy_dotnet_versions]
other_components = [comp for comp in components if comp not in legacy_dotnet_versions]
self.logger.info(f"Protontricks components: {protontricks_components}") self.logger.info(f"Protontricks components: {protontricks_components}")
self.logger.info(f"Other components: {other_components}") self.logger.info(f"Other components: {other_components}")
# Step 1: Install dotnet40 with protontricks if present # Step 1: Install legacy .NET Framework versions with protontricks if present
if protontricks_components: if protontricks_components:
self.logger.info(f"Installing {protontricks_components} using protontricks...") self.logger.info(f"Installing legacy .NET versions {protontricks_components} using protontricks...")
if not self._install_dotnet40_with_protontricks(wineprefix, game_var): if not self._install_legacy_dotnet_with_protontricks(protontricks_components, wineprefix, game_var):
self.logger.error(f"Failed to install {protontricks_components} with protontricks") self.logger.error(f"Failed to install {protontricks_components} with protontricks")
return False return False
self.logger.info(f"{protontricks_components} installation completed successfully with protontricks") self.logger.info(f"{protontricks_components} installation completed successfully with protontricks")
# Step 2: Install remaining components with winetricks if any # Step 2: Install remaining components if any
if other_components: if other_components:
self.logger.info(f"Installing remaining components with winetricks: {other_components}") if use_winetricks:
self.logger.info(f"Installing remaining components with winetricks: {other_components}")
# Use existing winetricks logic for other components
env = self._prepare_winetricks_environment(wineprefix)
if not env:
return False
return self._install_components_with_winetricks(other_components, wineprefix, env)
else:
self.logger.info(f"Installing remaining components with protontricks: {other_components}")
return self._install_components_protontricks_only(other_components, wineprefix, game_var)
# Use existing winetricks logic for other components self.logger.info("Hybrid component installation completed successfully")
env = self._prepare_winetricks_environment(wineprefix)
if not env:
return False
return self._install_components_with_winetricks(other_components, wineprefix, env)
self.logger.info("✓ Hybrid component installation completed successfully")
# Set Windows 10 mode after all component installation (matches legacy script timing) # Set Windows 10 mode after all component installation (matches legacy script timing)
wine_binary = self._get_wine_binary_for_prefix(wineprefix) wine_binary = self._get_wine_binary_for_prefix(wineprefix)
self._set_windows_10_mode(wineprefix, wine_binary) self._set_windows_10_mode(wineprefix, wine_binary)
return True return True
def _install_dotnet40_with_protontricks(self, wineprefix: str, game_var: str) -> bool: def _install_legacy_dotnet_with_protontricks(self, legacy_components: list, wineprefix: str, game_var: str) -> bool:
""" """
Install dotnet40 using protontricks (known to work reliably). Install legacy .NET Framework versions using protontricks (known to work more reliably).
Args: Args:
legacy_components: List of legacy .NET components to install (dotnet40, dotnet472, dotnet48)
wineprefix: Wine prefix path wineprefix: Wine prefix path
game_var: Game variable for AppID detection game_var: Game variable for AppID detection
@@ -575,21 +590,28 @@ class WinetricksHandler:
# Detect protontricks availability # Detect protontricks availability
if not protontricks_handler.detect_protontricks(): if not protontricks_handler.detect_protontricks():
self.logger.error("Protontricks not available for dotnet40 installation") self.logger.error(f"Protontricks not available for legacy .NET installation: {legacy_components}")
return False return False
# Install dotnet40 using protontricks # Install legacy .NET components using protontricks
success = protontricks_handler.install_wine_components(appid, game_var, ["dotnet40"]) success = protontricks_handler.install_wine_components(appid, game_var, legacy_components)
if success: if success:
self.logger.info("✓ dotnet40 installed successfully with protontricks") self.logger.info(f"Legacy .NET components {legacy_components} installed successfully with protontricks")
# Enable dotfiles and symlinks for the prefix
if protontricks_handler.enable_dotfiles(appid):
self.logger.info("Enabled dotfiles and symlinks support")
else:
self.logger.warning("Failed to enable dotfiles/symlinks (non-critical)")
return True return True
else: else:
self.logger.error("✗ dotnet40 installation failed with protontricks") self.logger.error(f"Legacy .NET components {legacy_components} installation failed with protontricks")
return False return False
except Exception as e: except Exception as e:
self.logger.error(f"Error installing dotnet40 with protontricks: {e}", exc_info=True) self.logger.error(f"Error installing legacy .NET components {legacy_components} with protontricks: {e}", exc_info=True)
return False return False
def _prepare_winetricks_environment(self, wineprefix: str) -> Optional[dict]: def _prepare_winetricks_environment(self, wineprefix: str) -> Optional[dict]:
@@ -699,13 +721,13 @@ class WinetricksHandler:
) )
if result.returncode == 0: if result.returncode == 0:
self.logger.info(f"Winetricks components installed successfully: {components}") self.logger.info(f"Winetricks components installed successfully: {components}")
# Set Windows 10 mode after component installation (matches legacy script timing) # Set Windows 10 mode after component installation (matches legacy script timing)
wine_binary = env.get('WINE', '') wine_binary = env.get('WINE', '')
self._set_windows_10_mode(env.get('WINEPREFIX', ''), wine_binary) self._set_windows_10_mode(env.get('WINEPREFIX', ''), wine_binary)
return True return True
else: else:
self.logger.error(f"Winetricks failed (attempt {attempt}): {result.stderr.strip()}") self.logger.error(f"Winetricks failed (attempt {attempt}): {result.stderr.strip()}")
except Exception as e: except Exception as e:
self.logger.error(f"Error during winetricks run (attempt {attempt}): {e}") self.logger.error(f"Error during winetricks run (attempt {attempt}): {e}")
@@ -729,7 +751,7 @@ class WinetricksHandler:
], env=env, capture_output=True, text=True, timeout=300) ], env=env, capture_output=True, text=True, timeout=300)
if result.returncode == 0: if result.returncode == 0:
self.logger.info("Windows 10 mode set successfully") self.logger.info("Windows 10 mode set successfully")
else: else:
self.logger.warning(f"Could not set Windows 10 mode: {result.stderr}") self.logger.warning(f"Could not set Windows 10 mode: {result.stderr}")
@@ -768,13 +790,13 @@ class WinetricksHandler:
success = protontricks_handler.install_wine_components(appid, game_var, components) success = protontricks_handler.install_wine_components(appid, game_var, components)
if success: if success:
self.logger.info("All components installed successfully with protontricks") self.logger.info("All components installed successfully with protontricks")
# Set Windows 10 mode after component installation # Set Windows 10 mode after component installation
wine_binary = self._get_wine_binary_for_prefix(wineprefix) wine_binary = self._get_wine_binary_for_prefix(wineprefix)
self._set_windows_10_mode(wineprefix, wine_binary) self._set_windows_10_mode(wineprefix, wine_binary)
return True return True
else: else:
self.logger.error("Component installation failed with protontricks") self.logger.error("Component installation failed with protontricks")
return False return False
except Exception as e: except Exception as e:

View File

@@ -57,6 +57,14 @@ class AutomatedPrefixService:
self._store_proton_override_notification("Lorerim", lorerim_proton) self._store_proton_override_notification("Lorerim", lorerim_proton)
return lorerim_proton return lorerim_proton
# Check for Lost Legacy-specific Proton override (needs Proton 9 for ENB compatibility)
if modlist_name and modlist_name.lower() == 'lostlegacy':
lostlegacy_proton = self._get_lorerim_preferred_proton() # Use same logic as Lorerim
if lostlegacy_proton:
logger.info(f"Lost Legacy detected: Using {lostlegacy_proton} instead of user settings (ENB compatibility)")
self._store_proton_override_notification("Lost Legacy", lostlegacy_proton)
return lostlegacy_proton
config_handler = ConfigHandler() config_handler = ConfigHandler()
user_proton_path = config_handler.get_game_proton_path() user_proton_path = config_handler.get_game_proton_path()

View File

@@ -629,7 +629,8 @@ class ModlistService:
'mo2_exe_path': str(context.install_dir / 'ModOrganizer.exe'), 'mo2_exe_path': str(context.install_dir / 'ModOrganizer.exe'),
'resolution': getattr(context, 'resolution', None), 'resolution': getattr(context, 'resolution', None),
'skip_confirmation': True, # Service layer should be non-interactive 'skip_confirmation': True, # Service layer should be non-interactive
'manual_steps_completed': False 'manual_steps_completed': False,
'appid': getattr(context, 'app_id', None) # Fix: Include appid like other configuration paths
} }
# DEBUG: Log what resolution we're passing # DEBUG: Log what resolution we're passing

View File

@@ -382,8 +382,27 @@ class NativeSteamService:
# Find the CompatToolMapping section # Find the CompatToolMapping section
compat_start = config_text.find('"CompatToolMapping"') compat_start = config_text.find('"CompatToolMapping"')
if compat_start == -1: if compat_start == -1:
logger.error("CompatToolMapping section not found in config.vdf") logger.warning("CompatToolMapping section not found in config.vdf, creating it")
return False # Find the Steam section to add CompatToolMapping to
steam_section = config_text.find('"Steam"')
if steam_section == -1:
logger.error("Steam section not found in config.vdf")
return False
# Find the opening brace for Steam section
steam_brace = config_text.find('{', steam_section)
if steam_brace == -1:
logger.error("Steam section opening brace not found")
return False
# Insert CompatToolMapping section right after Steam opening brace
insert_pos = steam_brace + 1
compat_section = '\n\t\t"CompatToolMapping"\n\t\t{\n\t\t}\n'
config_text = config_text[:insert_pos] + compat_section + config_text[insert_pos:]
# Update compat_start position after insertion
compat_start = config_text.find('"CompatToolMapping"')
logger.info("Created CompatToolMapping section in config.vdf")
# Find the closing brace for CompatToolMapping # Find the closing brace for CompatToolMapping
# Look for the opening brace after CompatToolMapping # Look for the opening brace after CompatToolMapping

View File

@@ -447,7 +447,7 @@ class SettingsDialog(QDialog):
self.use_winetricks_checkbox = QCheckBox("Use winetricks for component installation (faster)") self.use_winetricks_checkbox = QCheckBox("Use winetricks for component installation (faster)")
self.use_winetricks_checkbox.setChecked(self.config_handler.get('use_winetricks_for_components', True)) self.use_winetricks_checkbox.setChecked(self.config_handler.get('use_winetricks_for_components', True))
self.use_winetricks_checkbox.setToolTip( self.use_winetricks_checkbox.setToolTip(
"When enabled: Uses winetricks for most components (faster) and protontricks only for dotnet40 (more reliable).\n" "When enabled: Uses winetricks for most components (faster) and protontricks for legacy .NET versions (dotnet40, dotnet472, dotnet48) which are more reliable.\n"
"When disabled: Uses protontricks for all components (legacy behavior, slower but more compatible)." "When disabled: Uses protontricks for all components (legacy behavior, slower but more compatible)."
) )
component_layout.addWidget(self.use_winetricks_checkbox) component_layout.addWidget(self.use_winetricks_checkbox)