diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a15c01..3817d97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,31 @@ # Jackify Changelog +## v0.1.6 - Advanced Proton Management & Lorerim Support +**Release Date:** October 16, 2025 + +### Major New Features +- **Dual Proton Configuration**: Separate Install Proton and Game Proton version selection in Settings + - **Install Proton**: Optimized for modlist installation and texture processing (Experimental/GE-Proton 10+ recommended for performance) + - **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 + +--- + ## v0.1.5.3 - Critical Bug Fixes **Release Date:** October 2, 2025 @@ -67,7 +93,8 @@ - **ModOrganizer.ini Path Format**: Fixed missing backslash in gamePath format for proper Windows path structure - **SD Card Binary Paths**: Corrected binary paths to use D: drive mapping instead of raw Linux paths for SD card installs - **Proton Fallback Logic**: Enhanced fallback when user-selected Proton version is missing or invalid -- **Settings Persistence**: Improved configuration saving with verification and logging + +#Y- **Settings Persistence**: Improved configuration saving with verification and logging - **System Wine Elimination**: Comprehensive audit ensures Jackify never uses system wine installations - **Winetricks Reliability**: Fixed vcrun2022 installation failures and wine app crashes - **Enderal Registry Injection**: Switched from launch options to registry injection approach diff --git a/jackify/__init__.py b/jackify/__init__.py index ea8d3fb..ab8b42a 100644 --- a/jackify/__init__.py +++ b/jackify/__init__.py @@ -5,4 +5,4 @@ This package provides both CLI and GUI interfaces for managing Wabbajack modlists natively on Linux systems. """ -__version__ = "0.1.5.3" +__version__ = "0.1.6" diff --git a/jackify/backend/core/modlist_operations.py b/jackify/backend/core/modlist_operations.py index 239ed44..6d8b13f 100644 --- a/jackify/backend/core/modlist_operations.py +++ b/jackify/backend/core/modlist_operations.py @@ -156,7 +156,7 @@ class ModlistInstallCLI: from ..models.configuration import SystemInfo self.system_info = SystemInfo(is_steamdeck=steamdeck) - self.protontricks_handler = ProtontricksHandler(steamdeck=self.steamdeck) + self.protontricks_handler = ProtontricksHandler(self.steamdeck) self.shortcut_handler = ShortcutHandler(steamdeck=self.steamdeck) self.context = {} # Use standard logging (no file handler) diff --git a/jackify/backend/handlers/config_handler.py b/jackify/backend/handlers/config_handler.py index 75907af..eeb0b0f 100644 --- a/jackify/backend/handlers/config_handler.py +++ b/jackify/backend/handlers/config_handler.py @@ -38,7 +38,9 @@ class ConfigHandler: "default_download_parent_dir": None, # Parent directory for downloads "modlist_install_base_dir": os.path.expanduser("~/Games"), # Configurable base directory for modlist installations "modlist_downloads_base_dir": os.path.expanduser("~/Games/Modlist_Downloads"), # Configurable base directory for downloads - "jackify_data_dir": None # Configurable Jackify data directory (default: ~/Jackify) + "jackify_data_dir": None, # Configurable Jackify data directory (default: ~/Jackify) + "use_winetricks_for_components": True, # True = use winetricks (faster), False = use protontricks for all (legacy) + "game_proton_path": None # Proton version for game shortcuts (can be any Proton 9+), separate from install proton } # Load configuration if exists @@ -498,20 +500,44 @@ class ConfigHandler: def get_proton_path(self): """ - Retrieve the saved Proton path from configuration + Retrieve the saved Install Proton path from configuration (for jackify-engine) Always reads fresh from disk to pick up changes from Settings dialog Returns: - str: Saved Proton path or 'auto' if not saved + str: Saved Install Proton path or 'auto' if not saved """ try: # Reload config from disk to pick up changes from Settings dialog self._load_config() proton_path = self.settings.get("proton_path", "auto") - logger.debug(f"Retrieved fresh proton_path from config: {proton_path}") + logger.debug(f"Retrieved fresh install proton_path from config: {proton_path}") return proton_path except Exception as e: - logger.error(f"Error retrieving proton_path: {e}") + logger.error(f"Error retrieving install proton_path: {e}") + return "auto" + + def get_game_proton_path(self): + """ + Retrieve the saved Game Proton path from configuration (for game shortcuts) + Falls back to install Proton path if game Proton not set + Always reads fresh from disk to pick up changes from Settings dialog + + Returns: + str: Saved Game Proton path, Install Proton path, or 'auto' if not saved + """ + try: + # Reload config from disk to pick up changes from Settings dialog + self._load_config() + game_proton_path = self.settings.get("game_proton_path") + + # If game proton not set or set to same_as_install, use install proton + if not game_proton_path or game_proton_path == "same_as_install": + game_proton_path = self.settings.get("proton_path", "auto") + + logger.debug(f"Retrieved fresh game proton_path from config: {game_proton_path}") + return game_proton_path + except Exception as e: + logger.error(f"Error retrieving game proton_path: {e}") return "auto" def get_proton_version(self): diff --git a/jackify/backend/handlers/modlist_handler.py b/jackify/backend/handlers/modlist_handler.py index 63f316b..8f7930b 100644 --- a/jackify/backend/handlers/modlist_handler.py +++ b/jackify/backend/handlers/modlist_handler.py @@ -79,6 +79,7 @@ class ModlistHandler: "livingskyrim": ["dotnet40"], "lsiv": ["dotnet40"], "ls4": ["dotnet40"], + "lorerim": ["dotnet40"], "lostlegacy": ["dotnet48"], } @@ -158,7 +159,7 @@ class ModlistHandler: self.stock_game_path = None # Initialize Handlers (should happen regardless of how paths were provided) - self.protontricks_handler = ProtontricksHandler(steamdeck=self.steamdeck, logger=self.logger) + self.protontricks_handler = ProtontricksHandler(self.steamdeck, logger=self.logger) # Initialize winetricks handler for wine component installation from .winetricks_handler import WinetricksHandler self.winetricks_handler = WinetricksHandler(logger=self.logger) @@ -347,7 +348,8 @@ class ModlistHandler: # Store engine_installed flag for conditional path manipulation self.engine_installed = modlist_info.get('engine_installed', False) self.logger.debug(f" Engine Installed: {self.engine_installed}") - + + # Call internal detection methods to populate more state if not self._detect_game_variables(): self.logger.warning("Failed to auto-detect game type after setting context.") @@ -687,39 +689,27 @@ class ModlistHandler: # All modlists now use their own AppID for wine components target_appid = self.appid - # Use winetricks for wine component installation (faster than protontricks) + # Use user's preferred component installation method (respects settings toggle) wineprefix = self.protontricks_handler.get_wine_prefix_path(target_appid) if not wineprefix: - self.logger.error("Failed to get WINEPREFIX path for winetricks.") + self.logger.error("Failed to get WINEPREFIX path for component installation.") print("Error: Could not determine wine prefix location.") return False - # Try winetricks first (preferred method with current fix) - winetricks_success = False + # Use the winetricks handler which respects the user's toggle setting try: - self.logger.info("Attempting Wine component installation using winetricks...") - winetricks_success = self.winetricks_handler.install_wine_components(wineprefix, self.game_var_full, specific_components=components) - if winetricks_success: - self.logger.info("Winetricks installation completed successfully") - except Exception as e: - self.logger.warning(f"Winetricks installation failed with exception: {e}") - winetricks_success = False - - # Fallback to protontricks if winetricks failed - if not winetricks_success: - self.logger.warning("Winetricks failed, falling back to protontricks for Wine component installation...") - try: - protontricks_success = self.protontricks_handler.install_wine_components(target_appid, self.game_var_full, specific_components=components) - if protontricks_success: - self.logger.info("Protontricks fallback installation completed successfully") - else: - self.logger.error("Both winetricks and protontricks failed to install Wine components.") - print("Error: Failed to install necessary Wine components using both winetricks and protontricks.") - return False - except Exception as e: - self.logger.error(f"Protontricks fallback also failed with exception: {e}") - print("Error: Failed to install necessary Wine components using both winetricks and protontricks.") + self.logger.info("Installing Wine components using user's preferred method...") + success = self.winetricks_handler.install_wine_components(wineprefix, self.game_var_full, specific_components=components) + if success: + self.logger.info("Wine component installation completed successfully") + else: + self.logger.error("Wine component installation failed") + print("Error: Failed to install necessary Wine components.") return False + except Exception as e: + self.logger.error(f"Wine component installation failed with exception: {e}") + print("Error: Failed to install necessary Wine components.") + return False self.logger.info("Step 4: Installing Wine components... Done") # Step 5: Ensure permissions of Modlist directory @@ -824,10 +814,10 @@ class ModlistHandler: vanilla_game_dir = None if self.steam_library and self.game_var_full: vanilla_game_dir = str(Path(self.steam_library) / "steamapps" / "common" / self.game_var_full) - - if not self.resolution_handler.update_ini_resolution( - modlist_dir=self.modlist_dir, - game_var=self.game_var_full, + + if not ResolutionHandler.update_ini_resolution( + modlist_dir=self.modlist_dir, + game_var=self.game_var_full, set_res=self.selected_resolution, vanilla_game_dir=vanilla_game_dir ): @@ -930,6 +920,10 @@ class ModlistHandler: # status_callback("Configuration completed successfully!") self.logger.info("Configuration steps completed successfully.") + + # Step 14: Re-enforce Windows 10 mode after modlist-specific configurations (matches legacy script line 1333) + self._re_enforce_windows_10_mode() + return True # Return True on success def _detect_steam_library_info(self) -> bool: @@ -1332,4 +1326,39 @@ class ModlistHandler: self.logger.debug("No special game type detected - standard workflow will be used") return None + def _re_enforce_windows_10_mode(self): + """ + Re-enforce Windows 10 mode after modlist-specific configurations. + This matches the legacy script behavior (line 1333) where Windows 10 mode + is re-applied after modlist-specific steps to ensure consistency. + """ + try: + if not hasattr(self, 'appid') or not self.appid: + self.logger.warning("Cannot re-enforce Windows 10 mode - no AppID available") + return + + from ..handlers.winetricks_handler import WinetricksHandler + from ..handlers.path_handler import PathHandler + + # Get prefix path for the AppID + prefix_path = PathHandler.find_compat_data(str(self.appid)) + if not prefix_path: + self.logger.warning("Cannot re-enforce Windows 10 mode - prefix path not found") + return + + # Get wine binary path + wine_binary = PathHandler.get_wine_binary_for_appid(str(self.appid)) + if not wine_binary: + self.logger.warning("Cannot re-enforce Windows 10 mode - wine binary not found") + return + + # Use winetricks handler to set Windows 10 mode + winetricks_handler = WinetricksHandler() + winetricks_handler._set_windows_10_mode(str(prefix_path), wine_binary) + + self.logger.info("✓ Windows 10 mode re-enforced after modlist-specific configurations") + + except Exception as e: + self.logger.warning(f"Error re-enforcing Windows 10 mode: {e}") + # (Ensure EOF is clean and no extra incorrect methods exist below) \ No newline at end of file diff --git a/jackify/backend/handlers/modlist_install_cli.py b/jackify/backend/handlers/modlist_install_cli.py index cd1dc99..8d56f19 100644 --- a/jackify/backend/handlers/modlist_install_cli.py +++ b/jackify/backend/handlers/modlist_install_cli.py @@ -68,7 +68,7 @@ class ModlistInstallCLI: def __init__(self, menu_handler: MenuHandler, steamdeck: bool = False): self.menu_handler = menu_handler self.steamdeck = steamdeck - self.protontricks_handler = ProtontricksHandler(steamdeck=steamdeck) + self.protontricks_handler = ProtontricksHandler(steamdeck) self.shortcut_handler = ShortcutHandler(steamdeck=steamdeck) self.context = {} # Use standard logging (no file handler) diff --git a/jackify/backend/handlers/shortcut_handler.py b/jackify/backend/handlers/shortcut_handler.py index c444223..370e2c8 100644 --- a/jackify/backend/handlers/shortcut_handler.py +++ b/jackify/backend/handlers/shortcut_handler.py @@ -41,7 +41,7 @@ class ShortcutHandler: self._last_shortcuts_backup = None # Track the last backup path self._safe_shortcuts_backup = None # Track backup made just before restart # Initialize ProtontricksHandler here, passing steamdeck status - self.protontricks_handler = ProtontricksHandler(steamdeck=self.steamdeck) + self.protontricks_handler = ProtontricksHandler(self.steamdeck) def _enable_tab_completion(self): """Enable tab completion for file paths using the shared completer""" @@ -964,7 +964,7 @@ class ShortcutHandler: self.logger.info(f"Attempting to find current AppID for shortcut: '{shortcut_name}' (exe_path: '{exe_path}')") try: from .protontricks_handler import ProtontricksHandler # Local import - pt_handler = ProtontricksHandler(steamdeck=self.steamdeck) + pt_handler = ProtontricksHandler(self.steamdeck) if not pt_handler.detect_protontricks(): self.logger.error("Protontricks not detected") return None diff --git a/jackify/backend/handlers/winetricks_handler.py b/jackify/backend/handlers/winetricks_handler.py index 8000e13..c1bff7a 100644 --- a/jackify/backend/handlers/winetricks_handler.py +++ b/jackify/backend/handlers/winetricks_handler.py @@ -257,10 +257,18 @@ class WinetricksHandler: components_to_install = self._reorder_components_for_installation(all_components) self.logger.info(f"WINEPREFIX: {wineprefix}, Game: {game_var}, Ordered Components: {components_to_install}") - # Hybrid approach: Use protontricks for dotnet40 only, winetricks for everything else - if "dotnet40" in components_to_install: - self.logger.info("dotnet40 detected - using hybrid approach: protontricks for dotnet40, winetricks for others") + # Check user preference for component installation method + from ..handlers.config_handler import ConfigHandler + config_handler = ConfigHandler() + use_winetricks = config_handler.get('use_winetricks_for_components', True) + + # Choose installation method based on user preference and components + if use_winetricks and "dotnet40" in components_to_install: + self.logger.info("Using optimized approach: protontricks for dotnet40 (reliable), winetricks for other components (fast)") return self._install_components_hybrid_approach(components_to_install, wineprefix, game_var) + elif not use_winetricks: + self.logger.info("Using legacy approach: protontricks for all components") + return self._install_components_protontricks_only(components_to_install, wineprefix, game_var) # For non-dotnet40 installations, install all components together (faster) max_attempts = 3 @@ -289,6 +297,8 @@ class WinetricksHandler: self.logger.debug(f"Winetricks output: {result.stdout}") if result.returncode == 0: self.logger.info("Wine Component installation command completed successfully.") + # Set Windows 10 mode after component installation (matches legacy script timing) + self._set_windows_10_mode(wineprefix, env.get('WINE', '')) return True else: # Special handling for dotnet40 verification issue (mimics protontricks behavior) @@ -415,14 +425,8 @@ class WinetricksHandler: self.logger.error("Failed to prepare prefix for dotnet40") return False else: - # For non-dotnet40 components, ensure we're in Windows 10 mode + # For non-dotnet40 components, install in standard mode (Windows 10 will be set after all components) self.logger.debug(f"Installing {component} in standard mode") - try: - subprocess.run([ - self.winetricks_path, '-q', 'win10' - ], env=env, capture_output=True, text=True, timeout=300) - except Exception as e: - self.logger.warning(f"Could not set win10 mode for {component}: {e}") # Install this component max_attempts = 3 @@ -480,6 +484,8 @@ class WinetricksHandler: return False self.logger.info("✓ All components installed successfully using separate sessions") + # Set Windows 10 mode after all component installation (matches legacy script timing) + self._set_windows_10_mode(wineprefix, env.get('WINE', '')) return True def _install_components_hybrid_approach(self, components: list, wineprefix: str, game_var: str) -> bool: @@ -524,6 +530,9 @@ class WinetricksHandler: 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) + wine_binary = self._get_wine_binary_for_prefix(wineprefix) + self._set_windows_10_mode(wineprefix, wine_binary) return True def _install_dotnet40_with_protontricks(self, wineprefix: str, game_var: str) -> bool: @@ -562,7 +571,7 @@ class WinetricksHandler: # Determine if we're on Steam Deck (for protontricks handler) steamdeck = os.path.exists('/home/deck') - protontricks_handler = ProtontricksHandler(steamdeck=steamdeck, logger=self.logger) + protontricks_handler = ProtontricksHandler(steamdeck, logger=self.logger) # Detect protontricks availability if not protontricks_handler.detect_protontricks(): @@ -691,6 +700,9 @@ class WinetricksHandler: if result.returncode == 0: self.logger.info(f"✓ Winetricks components installed successfully: {components}") + # Set Windows 10 mode after component installation (matches legacy script timing) + wine_binary = env.get('WINE', '') + self._set_windows_10_mode(env.get('WINEPREFIX', ''), wine_binary) return True else: self.logger.error(f"✗ Winetricks failed (attempt {attempt}): {result.stderr.strip()}") @@ -701,6 +713,140 @@ class WinetricksHandler: self.logger.error(f"Failed to install components with winetricks after {max_attempts} attempts") return False + def _set_windows_10_mode(self, wineprefix: str, wine_binary: str): + """ + Set Windows 10 mode for the prefix after component installation (matches legacy script timing). + This should be called AFTER all Wine components are installed, not before. + """ + try: + env = os.environ.copy() + env['WINEPREFIX'] = wineprefix + env['WINE'] = wine_binary + + self.logger.info("Setting Windows 10 mode after component installation (matching legacy script)") + result = subprocess.run([ + self.winetricks_path, '-q', 'win10' + ], env=env, capture_output=True, text=True, timeout=300) + + if result.returncode == 0: + self.logger.info("✓ Windows 10 mode set successfully") + else: + self.logger.warning(f"Could not set Windows 10 mode: {result.stderr}") + + except Exception as e: + self.logger.warning(f"Error setting Windows 10 mode: {e}") + + def _install_components_protontricks_only(self, components: list, wineprefix: str, game_var: str) -> bool: + """ + Legacy approach: Install all components using protontricks only. + This matches the behavior of the original bash script. + """ + try: + self.logger.info(f"Installing all components with protontricks (legacy method): {components}") + + # Import protontricks handler + from ..handlers.protontricks_handler import ProtontricksHandler + + # Determine if we're on Steam Deck (for protontricks handler) + steamdeck = os.path.exists('/home/deck') + protontricks_handler = ProtontricksHandler(steamdeck, logger=self.logger) + + # Get AppID from wineprefix + appid = self._extract_appid_from_wineprefix(wineprefix) + if not appid: + self.logger.error("Could not extract AppID from wineprefix for protontricks installation") + return False + + self.logger.info(f"Using AppID {appid} for protontricks installation") + + # Detect protontricks availability + if not protontricks_handler.detect_protontricks(): + self.logger.error("Protontricks not available for component installation") + return False + + # Install all components using protontricks + success = protontricks_handler.install_wine_components(appid, game_var, components) + + if success: + self.logger.info("✓ All components installed successfully with protontricks") + # Set Windows 10 mode after component installation + wine_binary = self._get_wine_binary_for_prefix(wineprefix) + self._set_windows_10_mode(wineprefix, wine_binary) + return True + else: + self.logger.error("✗ Component installation failed with protontricks") + return False + + except Exception as e: + self.logger.error(f"Error installing components with protontricks: {e}", exc_info=True) + return False + + def _extract_appid_from_wineprefix(self, wineprefix: str) -> Optional[str]: + """ + Extract AppID from wineprefix path. + + Args: + wineprefix: Wine prefix path + + Returns: + AppID as string, or None if extraction fails + """ + try: + if 'compatdata' in wineprefix: + # Standard Steam compatdata structure + path_parts = Path(wineprefix).parts + for i, part in enumerate(path_parts): + if part == 'compatdata' and i + 1 < len(path_parts): + potential_appid = path_parts[i + 1] + if potential_appid.isdigit(): + return potential_appid + self.logger.error(f"Could not extract AppID from wineprefix path: {wineprefix}") + return None + except Exception as e: + self.logger.error(f"Error extracting AppID from wineprefix: {e}") + return None + + def _get_wine_binary_for_prefix(self, wineprefix: str) -> str: + """ + Get the wine binary path for a given prefix. + + Args: + wineprefix: Wine prefix path + + Returns: + Wine binary path as string + """ + try: + from ..handlers.config_handler import ConfigHandler + from ..handlers.wine_utils import WineUtils + + config = ConfigHandler() + user_proton_path = config.get_proton_path() + + # If user selected a specific Proton, try that first + wine_binary = None + if user_proton_path != 'auto': + if os.path.exists(user_proton_path): + resolved_proton_path = os.path.realpath(user_proton_path) + valve_proton_wine = os.path.join(resolved_proton_path, 'dist', 'bin', 'wine') + ge_proton_wine = os.path.join(resolved_proton_path, 'files', 'bin', 'wine') + + if os.path.exists(valve_proton_wine): + wine_binary = valve_proton_wine + elif os.path.exists(ge_proton_wine): + wine_binary = ge_proton_wine + + # Fall back to auto-detection if user selection failed or is 'auto' + if not wine_binary: + best_proton = WineUtils.select_best_proton() + if best_proton: + wine_binary = WineUtils.find_proton_binary(best_proton['name']) + + return wine_binary if wine_binary else "" + except Exception as e: + self.logger.error(f"Error getting wine binary for prefix: {e}") + return "" + def _cleanup_wine_processes(self): """ Internal method to clean up wine processes during component installation diff --git a/jackify/backend/services/automated_prefix_service.py b/jackify/backend/services/automated_prefix_service.py index d1fa04e..3b5714b 100644 --- a/jackify/backend/services/automated_prefix_service.py +++ b/jackify/backend/services/automated_prefix_service.py @@ -39,14 +39,26 @@ class AutomatedPrefixService: from jackify.shared.timing import get_timestamp return get_timestamp() - def _get_user_proton_version(self): - """Get user's preferred Proton version from config, with fallback to auto-detection""" + def _get_user_proton_version(self, modlist_name: str = None): + """Get user's preferred Proton version from config, with fallback to auto-detection + + Args: + modlist_name: Optional modlist name for special handling (e.g., Lorerim) + """ try: from jackify.backend.handlers.config_handler import ConfigHandler from jackify.backend.handlers.wine_utils import WineUtils + # Check for Lorerim-specific Proton override first + if modlist_name and modlist_name.lower() == 'lorerim': + lorerim_proton = self._get_lorerim_preferred_proton() + if lorerim_proton: + logger.info(f"Lorerim detected: Using {lorerim_proton} instead of user settings") + self._store_proton_override_notification("Lorerim", lorerim_proton) + return lorerim_proton + config_handler = ConfigHandler() - user_proton_path = config_handler.get_proton_path() + user_proton_path = config_handler.get_game_proton_path() if user_proton_path == 'auto': # Use enhanced fallback logic with GE-Proton preference @@ -125,8 +137,8 @@ class AutomatedPrefixService: logger.warning(f"Could not generate STEAM_COMPAT_MOUNTS, using default: {e}") launch_options = "%command%" - # Get user's preferred Proton version - proton_version = self._get_user_proton_version() + # Get user's preferred Proton version (with Lorerim-specific override) + proton_version = self._get_user_proton_version(shortcut_name) # Create shortcut with Proton using native service success, app_id = steam_service.create_shortcut_with_proton( @@ -487,7 +499,7 @@ exit""" try: # Use the existing protontricks handler from jackify.backend.handlers.protontricks_handler import ProtontricksHandler - protontricks_handler = ProtontricksHandler(steamdeck=steamdeck or False) + protontricks_handler = ProtontricksHandler(steamdeck or False) result = protontricks_handler.run_protontricks('-l') if result.returncode == 0: @@ -1556,6 +1568,9 @@ echo Prefix creation complete. if progress_callback: progress_callback(f"{self._get_progress_timestamp()} Steam Configuration complete!") + # Show Proton override notification if applicable + self._show_proton_override_notification(progress_callback) + logger.info(" Simple automated prefix creation workflow completed successfully") return True, prefix_path, actual_appid @@ -1869,6 +1884,11 @@ echo Prefix creation complete. if progress_callback: progress_callback(f"{last_timestamp} Steam integration complete") progress_callback("") # Blank line after Steam integration complete + + # Show Proton override notification if applicable + self._show_proton_override_notification(progress_callback) + + if progress_callback: progress_callback("") # Extra blank line to span across Configuration Summary progress_callback("") # And one more to create space before Prefix Configuration @@ -2705,7 +2725,7 @@ echo Prefix creation complete. from jackify.backend.handlers.wine_utils import WineUtils config = ConfigHandler() - user_proton_path = config.get_proton_path() + user_proton_path = config.get_game_proton_path() # If user selected a specific Proton, try that first if user_proton_path != 'auto': @@ -3005,4 +3025,92 @@ echo Prefix creation complete. logger.debug(f"{config['name']} not found in Steam libraries") logger.info("Game registry injection completed") - + + def _get_lorerim_preferred_proton(self): + """Get Lorerim's preferred Proton 9 version with specific priority order""" + try: + from jackify.backend.handlers.wine_utils import WineUtils + + # Get all available Proton versions + available_versions = WineUtils.scan_all_proton_versions() + + if not available_versions: + logger.warning("No Proton versions found for Lorerim override") + return None + + # Priority order for Lorerim: + # 1. GEProton9-27 (specific version) + # 2. Other GEProton-9 versions (latest first) + # 3. Valve Proton 9 (any version) + + preferred_candidates = [] + + for version in available_versions: + version_name = version['name'] + + # Priority 1: GEProton9-27 specifically + if version_name == 'GE-Proton9-27': + logger.info(f"Lorerim: Found preferred GE-Proton9-27") + return version_name + + # Priority 2: Other GE-Proton 9 versions + elif version_name.startswith('GE-Proton9-'): + preferred_candidates.append(('ge_proton_9', version_name, version)) + + # Priority 3: Valve Proton 9 + elif 'Proton 9' in version_name: + preferred_candidates.append(('valve_proton_9', version_name, version)) + + # Return best candidate if any found + if preferred_candidates: + # Sort by priority (GE-Proton first, then by name for latest) + preferred_candidates.sort(key=lambda x: (x[0], x[1]), reverse=True) + best_candidate = preferred_candidates[0] + logger.info(f"Lorerim: Selected {best_candidate[1]} as best Proton 9 option") + return best_candidate[1] + + logger.warning("Lorerim: No suitable Proton 9 versions found, will use user settings") + return None + + except Exception as e: + logger.error(f"Error detecting Lorerim Proton preference: {e}") + return None + + def _store_proton_override_notification(self, modlist_name: str, proton_version: str): + """Store Proton override information for end-of-install notification""" + try: + # Store override info for later display + if not hasattr(self, '_proton_overrides'): + self._proton_overrides = [] + + self._proton_overrides.append({ + 'modlist': modlist_name, + 'proton_version': proton_version, + 'reason': f'{modlist_name} requires Proton 9 for optimal compatibility' + }) + + logger.debug(f"Stored Proton override notification: {modlist_name} → {proton_version}") + + except Exception as e: + logger.error(f"Failed to store Proton override notification: {e}") + + def _show_proton_override_notification(self, progress_callback=None): + """Display any Proton override notifications to the user""" + try: + if hasattr(self, '_proton_overrides') and self._proton_overrides: + for override in self._proton_overrides: + notification_msg = f"PROTON OVERRIDE: {override['modlist']} configured to use {override['proton_version']} for optimal compatibility" + + if progress_callback: + progress_callback("") + progress_callback(f"{self._get_progress_timestamp()} {notification_msg}") + + logger.info(notification_msg) + + # Clear notifications after display + self._proton_overrides = [] + + except Exception as e: + logger.error(f"Failed to show Proton override notification: {e}") + + diff --git a/jackify/backend/services/modlist_service.py b/jackify/backend/services/modlist_service.py index 765cc9f..f7c0381 100644 --- a/jackify/backend/services/modlist_service.py +++ b/jackify/backend/services/modlist_service.py @@ -631,6 +631,10 @@ class ModlistService: 'skip_confirmation': True, # Service layer should be non-interactive 'manual_steps_completed': False } + + # DEBUG: Log what resolution we're passing + logger.info(f"DEBUG: config_context resolution = {config_context['resolution']}") + logger.info(f"DEBUG: context.resolution = {getattr(context, 'resolution', 'NOT_SET')}") # Run the complete configuration phase success = modlist_menu.run_modlist_configuration_phase(config_context) diff --git a/jackify/backend/services/native_steam_service.py b/jackify/backend/services/native_steam_service.py index 8120062..68b51e7 100644 --- a/jackify/backend/services/native_steam_service.py +++ b/jackify/backend/services/native_steam_service.py @@ -345,16 +345,22 @@ class NativeSteamService: def set_proton_version(self, app_id: int, proton_version: str = "proton_experimental") -> bool: """ Set the Proton version for a specific app using ONLY config.vdf like steam-conductor does. - + Args: - app_id: The unsigned AppID + app_id: The unsigned AppID proton_version: The Proton version to set - + Returns: True if successful """ + # Ensure Steam user detection is completed first + if not self.steam_path: + if not self.find_steam_user(): + logger.error("Cannot set Proton version: Steam user detection failed") + return False + logger.info(f"Setting Proton version '{proton_version}' for AppID {app_id} using STL-compatible format") - + try: # Step 1: Write to the main config.vdf for CompatToolMapping config_path = self.steam_path / "config" / "config.vdf" diff --git a/jackify/backend/services/protontricks_detection_service.py b/jackify/backend/services/protontricks_detection_service.py index 3c85ade..54ad05a 100644 --- a/jackify/backend/services/protontricks_detection_service.py +++ b/jackify/backend/services/protontricks_detection_service.py @@ -39,7 +39,7 @@ class ProtontricksDetectionService: def _get_protontricks_handler(self) -> ProtontricksHandler: """Get or create ProtontricksHandler instance""" if self._protontricks_handler is None: - self._protontricks_handler = ProtontricksHandler(steamdeck=self.steamdeck) + self._protontricks_handler = ProtontricksHandler(self.steamdeck) return self._protontricks_handler def detect_protontricks(self, use_cache: bool = True) -> Tuple[bool, str, str]: diff --git a/jackify/engine/Wabbajack.CLI.Builder.dll b/jackify/engine/Wabbajack.CLI.Builder.dll index 5112bb1..2743521 100644 Binary files a/jackify/engine/Wabbajack.CLI.Builder.dll and b/jackify/engine/Wabbajack.CLI.Builder.dll differ diff --git a/jackify/engine/Wabbajack.Common.dll b/jackify/engine/Wabbajack.Common.dll index 56d10da..feb94cb 100644 Binary files a/jackify/engine/Wabbajack.Common.dll and b/jackify/engine/Wabbajack.Common.dll differ diff --git a/jackify/engine/Wabbajack.Compiler.dll b/jackify/engine/Wabbajack.Compiler.dll index 1364278..9303d66 100644 Binary files a/jackify/engine/Wabbajack.Compiler.dll and b/jackify/engine/Wabbajack.Compiler.dll differ diff --git a/jackify/engine/Wabbajack.Compression.BSA.dll b/jackify/engine/Wabbajack.Compression.BSA.dll index 987a82b..e292420 100644 Binary files a/jackify/engine/Wabbajack.Compression.BSA.dll and b/jackify/engine/Wabbajack.Compression.BSA.dll differ diff --git a/jackify/engine/Wabbajack.Compression.Zip.dll b/jackify/engine/Wabbajack.Compression.Zip.dll index 837a02f..c11d007 100644 Binary files a/jackify/engine/Wabbajack.Compression.Zip.dll and b/jackify/engine/Wabbajack.Compression.Zip.dll differ diff --git a/jackify/engine/Wabbajack.Configuration.dll b/jackify/engine/Wabbajack.Configuration.dll index 33fd275..b7a2a37 100644 Binary files a/jackify/engine/Wabbajack.Configuration.dll and b/jackify/engine/Wabbajack.Configuration.dll differ diff --git a/jackify/engine/Wabbajack.DTOs.dll b/jackify/engine/Wabbajack.DTOs.dll index 7491a1e..b70cab5 100644 Binary files a/jackify/engine/Wabbajack.DTOs.dll and b/jackify/engine/Wabbajack.DTOs.dll differ diff --git a/jackify/engine/Wabbajack.Downloaders.Bethesda.dll b/jackify/engine/Wabbajack.Downloaders.Bethesda.dll index a9e4fc4..eec2526 100644 Binary files a/jackify/engine/Wabbajack.Downloaders.Bethesda.dll and b/jackify/engine/Wabbajack.Downloaders.Bethesda.dll differ diff --git a/jackify/engine/Wabbajack.Downloaders.Dispatcher.dll b/jackify/engine/Wabbajack.Downloaders.Dispatcher.dll index a0d7fb7..47d3bf6 100644 Binary files a/jackify/engine/Wabbajack.Downloaders.Dispatcher.dll and b/jackify/engine/Wabbajack.Downloaders.Dispatcher.dll differ diff --git a/jackify/engine/Wabbajack.Downloaders.GameFile.dll b/jackify/engine/Wabbajack.Downloaders.GameFile.dll index 1ba2866..94aca67 100644 Binary files a/jackify/engine/Wabbajack.Downloaders.GameFile.dll and b/jackify/engine/Wabbajack.Downloaders.GameFile.dll differ diff --git a/jackify/engine/Wabbajack.Downloaders.GoogleDrive.dll b/jackify/engine/Wabbajack.Downloaders.GoogleDrive.dll index 889d3f1..0462e3f 100644 Binary files a/jackify/engine/Wabbajack.Downloaders.GoogleDrive.dll and b/jackify/engine/Wabbajack.Downloaders.GoogleDrive.dll differ diff --git a/jackify/engine/Wabbajack.Downloaders.Http.dll b/jackify/engine/Wabbajack.Downloaders.Http.dll index 1f65475..59b4f0f 100644 Binary files a/jackify/engine/Wabbajack.Downloaders.Http.dll and b/jackify/engine/Wabbajack.Downloaders.Http.dll differ diff --git a/jackify/engine/Wabbajack.Downloaders.IPS4OAuth2Downloader.dll b/jackify/engine/Wabbajack.Downloaders.IPS4OAuth2Downloader.dll index 9178d1f..803c951 100644 Binary files a/jackify/engine/Wabbajack.Downloaders.IPS4OAuth2Downloader.dll and b/jackify/engine/Wabbajack.Downloaders.IPS4OAuth2Downloader.dll differ diff --git a/jackify/engine/Wabbajack.Downloaders.Interfaces.dll b/jackify/engine/Wabbajack.Downloaders.Interfaces.dll index 87f64cc..6bc6cbb 100644 Binary files a/jackify/engine/Wabbajack.Downloaders.Interfaces.dll and b/jackify/engine/Wabbajack.Downloaders.Interfaces.dll differ diff --git a/jackify/engine/Wabbajack.Downloaders.Manual.dll b/jackify/engine/Wabbajack.Downloaders.Manual.dll index 1bad653..f9e6a85 100644 Binary files a/jackify/engine/Wabbajack.Downloaders.Manual.dll and b/jackify/engine/Wabbajack.Downloaders.Manual.dll differ diff --git a/jackify/engine/Wabbajack.Downloaders.MediaFire.dll b/jackify/engine/Wabbajack.Downloaders.MediaFire.dll index 6b4cd1d..c43ff45 100644 Binary files a/jackify/engine/Wabbajack.Downloaders.MediaFire.dll and b/jackify/engine/Wabbajack.Downloaders.MediaFire.dll differ diff --git a/jackify/engine/Wabbajack.Downloaders.Mega.dll b/jackify/engine/Wabbajack.Downloaders.Mega.dll index 662df05..f627cbe 100644 Binary files a/jackify/engine/Wabbajack.Downloaders.Mega.dll and b/jackify/engine/Wabbajack.Downloaders.Mega.dll differ diff --git a/jackify/engine/Wabbajack.Downloaders.ModDB.dll b/jackify/engine/Wabbajack.Downloaders.ModDB.dll index 2e41174..979ddbe 100644 Binary files a/jackify/engine/Wabbajack.Downloaders.ModDB.dll and b/jackify/engine/Wabbajack.Downloaders.ModDB.dll differ diff --git a/jackify/engine/Wabbajack.Downloaders.Nexus.dll b/jackify/engine/Wabbajack.Downloaders.Nexus.dll index 5ce19c9..43d13be 100644 Binary files a/jackify/engine/Wabbajack.Downloaders.Nexus.dll and b/jackify/engine/Wabbajack.Downloaders.Nexus.dll differ diff --git a/jackify/engine/Wabbajack.Downloaders.VerificationCache.dll b/jackify/engine/Wabbajack.Downloaders.VerificationCache.dll index 3d50f98..0e3b06d 100644 Binary files a/jackify/engine/Wabbajack.Downloaders.VerificationCache.dll and b/jackify/engine/Wabbajack.Downloaders.VerificationCache.dll differ diff --git a/jackify/engine/Wabbajack.Downloaders.WabbajackCDN.dll b/jackify/engine/Wabbajack.Downloaders.WabbajackCDN.dll index bf963b8..8c2cfe0 100644 Binary files a/jackify/engine/Wabbajack.Downloaders.WabbajackCDN.dll and b/jackify/engine/Wabbajack.Downloaders.WabbajackCDN.dll differ diff --git a/jackify/engine/Wabbajack.FileExtractor.dll b/jackify/engine/Wabbajack.FileExtractor.dll index 3046b42..4cb7a65 100644 Binary files a/jackify/engine/Wabbajack.FileExtractor.dll and b/jackify/engine/Wabbajack.FileExtractor.dll differ diff --git a/jackify/engine/Wabbajack.Hashing.PHash.dll b/jackify/engine/Wabbajack.Hashing.PHash.dll index aa6ab99..743b659 100644 Binary files a/jackify/engine/Wabbajack.Hashing.PHash.dll and b/jackify/engine/Wabbajack.Hashing.PHash.dll differ diff --git a/jackify/engine/Wabbajack.Hashing.xxHash64.dll b/jackify/engine/Wabbajack.Hashing.xxHash64.dll index e344112..c393de3 100644 Binary files a/jackify/engine/Wabbajack.Hashing.xxHash64.dll and b/jackify/engine/Wabbajack.Hashing.xxHash64.dll differ diff --git a/jackify/engine/Wabbajack.IO.Async.dll b/jackify/engine/Wabbajack.IO.Async.dll index 25edc1a..0c9d484 100644 Binary files a/jackify/engine/Wabbajack.IO.Async.dll and b/jackify/engine/Wabbajack.IO.Async.dll differ diff --git a/jackify/engine/Wabbajack.Installer.dll b/jackify/engine/Wabbajack.Installer.dll index 2a5287b..5adc57e 100644 Binary files a/jackify/engine/Wabbajack.Installer.dll and b/jackify/engine/Wabbajack.Installer.dll differ diff --git a/jackify/engine/Wabbajack.Networking.BethesdaNet.dll b/jackify/engine/Wabbajack.Networking.BethesdaNet.dll index 04c5110..d56d852 100644 Binary files a/jackify/engine/Wabbajack.Networking.BethesdaNet.dll and b/jackify/engine/Wabbajack.Networking.BethesdaNet.dll differ diff --git a/jackify/engine/Wabbajack.Networking.Discord.dll b/jackify/engine/Wabbajack.Networking.Discord.dll index aa845e9..7fa583c 100644 Binary files a/jackify/engine/Wabbajack.Networking.Discord.dll and b/jackify/engine/Wabbajack.Networking.Discord.dll differ diff --git a/jackify/engine/Wabbajack.Networking.GitHub.dll b/jackify/engine/Wabbajack.Networking.GitHub.dll index db21880..0b9d9d1 100644 Binary files a/jackify/engine/Wabbajack.Networking.GitHub.dll and b/jackify/engine/Wabbajack.Networking.GitHub.dll differ diff --git a/jackify/engine/Wabbajack.Networking.Http.Interfaces.dll b/jackify/engine/Wabbajack.Networking.Http.Interfaces.dll index fc8283b..5ef0b2f 100644 Binary files a/jackify/engine/Wabbajack.Networking.Http.Interfaces.dll and b/jackify/engine/Wabbajack.Networking.Http.Interfaces.dll differ diff --git a/jackify/engine/Wabbajack.Networking.Http.dll b/jackify/engine/Wabbajack.Networking.Http.dll index 0d9d5f2..2e686cc 100644 Binary files a/jackify/engine/Wabbajack.Networking.Http.dll and b/jackify/engine/Wabbajack.Networking.Http.dll differ diff --git a/jackify/engine/Wabbajack.Networking.NexusApi.dll b/jackify/engine/Wabbajack.Networking.NexusApi.dll index 941c435..59c82dd 100644 Binary files a/jackify/engine/Wabbajack.Networking.NexusApi.dll and b/jackify/engine/Wabbajack.Networking.NexusApi.dll differ diff --git a/jackify/engine/Wabbajack.Networking.WabbajackClientApi.dll b/jackify/engine/Wabbajack.Networking.WabbajackClientApi.dll index ddbf1cc..3c62c9d 100644 Binary files a/jackify/engine/Wabbajack.Networking.WabbajackClientApi.dll and b/jackify/engine/Wabbajack.Networking.WabbajackClientApi.dll differ diff --git a/jackify/engine/Wabbajack.Paths.IO.dll b/jackify/engine/Wabbajack.Paths.IO.dll index 5ed41d6..78eda6d 100644 Binary files a/jackify/engine/Wabbajack.Paths.IO.dll and b/jackify/engine/Wabbajack.Paths.IO.dll differ diff --git a/jackify/engine/Wabbajack.Paths.dll b/jackify/engine/Wabbajack.Paths.dll index c38bd09..c7b08fc 100644 Binary files a/jackify/engine/Wabbajack.Paths.dll and b/jackify/engine/Wabbajack.Paths.dll differ diff --git a/jackify/engine/Wabbajack.RateLimiter.dll b/jackify/engine/Wabbajack.RateLimiter.dll index dc39d59..dc1d824 100644 Binary files a/jackify/engine/Wabbajack.RateLimiter.dll and b/jackify/engine/Wabbajack.RateLimiter.dll differ diff --git a/jackify/engine/Wabbajack.Server.Lib.dll b/jackify/engine/Wabbajack.Server.Lib.dll index 4fcff1f..ff2427d 100644 Binary files a/jackify/engine/Wabbajack.Server.Lib.dll and b/jackify/engine/Wabbajack.Server.Lib.dll differ diff --git a/jackify/engine/Wabbajack.Services.OSIntegrated.dll b/jackify/engine/Wabbajack.Services.OSIntegrated.dll index 992edd2..7714c9a 100644 Binary files a/jackify/engine/Wabbajack.Services.OSIntegrated.dll and b/jackify/engine/Wabbajack.Services.OSIntegrated.dll differ diff --git a/jackify/engine/Wabbajack.VFS.Interfaces.dll b/jackify/engine/Wabbajack.VFS.Interfaces.dll index 10b6646..2047b87 100644 Binary files a/jackify/engine/Wabbajack.VFS.Interfaces.dll and b/jackify/engine/Wabbajack.VFS.Interfaces.dll differ diff --git a/jackify/engine/Wabbajack.VFS.dll b/jackify/engine/Wabbajack.VFS.dll index 2a68781..11e1f4f 100644 Binary files a/jackify/engine/Wabbajack.VFS.dll and b/jackify/engine/Wabbajack.VFS.dll differ diff --git a/jackify/engine/jackify-engine.deps.json b/jackify/engine/jackify-engine.deps.json index d004f47..ce64396 100644 --- a/jackify/engine/jackify-engine.deps.json +++ b/jackify/engine/jackify-engine.deps.json @@ -7,7 +7,7 @@ "targets": { ".NETCoreApp,Version=v8.0": {}, ".NETCoreApp,Version=v8.0/linux-x64": { - "jackify-engine/0.3.16": { + "jackify-engine/0.3.17": { "dependencies": { "Markdig": "0.40.0", "Microsoft.Extensions.Configuration.Json": "9.0.1", @@ -22,16 +22,16 @@ "SixLabors.ImageSharp": "3.1.6", "System.CommandLine": "2.0.0-beta4.22272.1", "System.CommandLine.NamingConventionBinder": "2.0.0-beta4.22272.1", - "Wabbajack.CLI.Builder": "0.3.16", - "Wabbajack.Downloaders.Bethesda": "0.3.16", - "Wabbajack.Downloaders.Dispatcher": "0.3.16", - "Wabbajack.Hashing.xxHash64": "0.3.16", - "Wabbajack.Networking.Discord": "0.3.16", - "Wabbajack.Networking.GitHub": "0.3.16", - "Wabbajack.Paths.IO": "0.3.16", - "Wabbajack.Server.Lib": "0.3.16", - "Wabbajack.Services.OSIntegrated": "0.3.16", - "Wabbajack.VFS": "0.3.16", + "Wabbajack.CLI.Builder": "0.3.17", + "Wabbajack.Downloaders.Bethesda": "0.3.17", + "Wabbajack.Downloaders.Dispatcher": "0.3.17", + "Wabbajack.Hashing.xxHash64": "0.3.17", + "Wabbajack.Networking.Discord": "0.3.17", + "Wabbajack.Networking.GitHub": "0.3.17", + "Wabbajack.Paths.IO": "0.3.17", + "Wabbajack.Server.Lib": "0.3.17", + "Wabbajack.Services.OSIntegrated": "0.3.17", + "Wabbajack.VFS": "0.3.17", "MegaApiClient": "1.0.0.0", "runtimepack.Microsoft.NETCore.App.Runtime.linux-x64": "8.0.19" }, @@ -1781,7 +1781,7 @@ } } }, - "Wabbajack.CLI.Builder/0.3.16": { + "Wabbajack.CLI.Builder/0.3.17": { "dependencies": { "Microsoft.Extensions.Configuration.Json": "9.0.1", "Microsoft.Extensions.DependencyInjection": "9.0.1", @@ -1791,109 +1791,109 @@ "Microsoft.Extensions.Logging.Abstractions": "9.0.1", "System.CommandLine": "2.0.0-beta4.22272.1", "System.CommandLine.NamingConventionBinder": "2.0.0-beta4.22272.1", - "Wabbajack.Paths": "0.3.16" + "Wabbajack.Paths": "0.3.17" }, "runtime": { "Wabbajack.CLI.Builder.dll": {} } }, - "Wabbajack.Common/0.3.16": { + "Wabbajack.Common/0.3.17": { "dependencies": { "Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.Logging.Abstractions": "9.0.1", "System.Reactive": "6.0.1", - "Wabbajack.DTOs": "0.3.16", - "Wabbajack.Networking.Http": "0.3.16", - "Wabbajack.Paths.IO": "0.3.16" + "Wabbajack.DTOs": "0.3.17", + "Wabbajack.Networking.Http": "0.3.17", + "Wabbajack.Paths.IO": "0.3.17" }, "runtime": { "Wabbajack.Common.dll": {} } }, - "Wabbajack.Compiler/0.3.16": { + "Wabbajack.Compiler/0.3.17": { "dependencies": { "F23.StringSimilarity": "6.0.0", "Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Newtonsoft.Json": "13.0.3", "SixLabors.ImageSharp": "3.1.6", - "Wabbajack.Downloaders.Dispatcher": "0.3.16", - "Wabbajack.Installer": "0.3.16", - "Wabbajack.VFS": "0.3.16", + "Wabbajack.Downloaders.Dispatcher": "0.3.17", + "Wabbajack.Installer": "0.3.17", + "Wabbajack.VFS": "0.3.17", "ini-parser-netstandard": "2.5.2" }, "runtime": { "Wabbajack.Compiler.dll": {} } }, - "Wabbajack.Compression.BSA/0.3.16": { + "Wabbajack.Compression.BSA/0.3.17": { "dependencies": { "K4os.Compression.LZ4.Streams": "1.3.8", "Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "SharpZipLib": "1.4.2", - "Wabbajack.Common": "0.3.16", - "Wabbajack.DTOs": "0.3.16" + "Wabbajack.Common": "0.3.17", + "Wabbajack.DTOs": "0.3.17" }, "runtime": { "Wabbajack.Compression.BSA.dll": {} } }, - "Wabbajack.Compression.Zip/0.3.16": { + "Wabbajack.Compression.Zip/0.3.17": { "dependencies": { - "Wabbajack.IO.Async": "0.3.16" + "Wabbajack.IO.Async": "0.3.17" }, "runtime": { "Wabbajack.Compression.Zip.dll": {} } }, - "Wabbajack.Configuration/0.3.16": { + "Wabbajack.Configuration/0.3.17": { "runtime": { "Wabbajack.Configuration.dll": {} } }, - "Wabbajack.Downloaders.Bethesda/0.3.16": { + "Wabbajack.Downloaders.Bethesda/0.3.17": { "dependencies": { "LibAES-CTR": "1.1.0", "Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "SharpZipLib": "1.4.2", - "Wabbajack.Common": "0.3.16", - "Wabbajack.Downloaders.Interfaces": "0.3.16", - "Wabbajack.Networking.BethesdaNet": "0.3.16" + "Wabbajack.Common": "0.3.17", + "Wabbajack.Downloaders.Interfaces": "0.3.17", + "Wabbajack.Networking.BethesdaNet": "0.3.17" }, "runtime": { "Wabbajack.Downloaders.Bethesda.dll": {} } }, - "Wabbajack.Downloaders.Dispatcher/0.3.16": { + "Wabbajack.Downloaders.Dispatcher/0.3.17": { "dependencies": { "Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.Logging.Abstractions": "9.0.1", "Newtonsoft.Json": "13.0.3", "SixLabors.ImageSharp": "3.1.6", - "Wabbajack.Downloaders.Bethesda": "0.3.16", - "Wabbajack.Downloaders.GameFile": "0.3.16", - "Wabbajack.Downloaders.GoogleDrive": "0.3.16", - "Wabbajack.Downloaders.Http": "0.3.16", - "Wabbajack.Downloaders.IPS4OAuth2Downloader": "0.3.16", - "Wabbajack.Downloaders.Interfaces": "0.3.16", - "Wabbajack.Downloaders.Manual": "0.3.16", - "Wabbajack.Downloaders.MediaFire": "0.3.16", - "Wabbajack.Downloaders.Mega": "0.3.16", - "Wabbajack.Downloaders.ModDB": "0.3.16", - "Wabbajack.Downloaders.Nexus": "0.3.16", - "Wabbajack.Downloaders.VerificationCache": "0.3.16", - "Wabbajack.Downloaders.WabbajackCDN": "0.3.16", - "Wabbajack.Networking.WabbajackClientApi": "0.3.16" + "Wabbajack.Downloaders.Bethesda": "0.3.17", + "Wabbajack.Downloaders.GameFile": "0.3.17", + "Wabbajack.Downloaders.GoogleDrive": "0.3.17", + "Wabbajack.Downloaders.Http": "0.3.17", + "Wabbajack.Downloaders.IPS4OAuth2Downloader": "0.3.17", + "Wabbajack.Downloaders.Interfaces": "0.3.17", + "Wabbajack.Downloaders.Manual": "0.3.17", + "Wabbajack.Downloaders.MediaFire": "0.3.17", + "Wabbajack.Downloaders.Mega": "0.3.17", + "Wabbajack.Downloaders.ModDB": "0.3.17", + "Wabbajack.Downloaders.Nexus": "0.3.17", + "Wabbajack.Downloaders.VerificationCache": "0.3.17", + "Wabbajack.Downloaders.WabbajackCDN": "0.3.17", + "Wabbajack.Networking.WabbajackClientApi": "0.3.17" }, "runtime": { "Wabbajack.Downloaders.Dispatcher.dll": {} } }, - "Wabbajack.Downloaders.GameFile/0.3.16": { + "Wabbajack.Downloaders.GameFile/0.3.17": { "dependencies": { "GameFinder.StoreHandlers.EADesktop": "4.5.0", "GameFinder.StoreHandlers.EGS": "4.5.0", @@ -1903,360 +1903,360 @@ "Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "SixLabors.ImageSharp": "3.1.6", - "Wabbajack.Downloaders.Interfaces": "0.3.16", - "Wabbajack.VFS": "0.3.16" + "Wabbajack.Downloaders.Interfaces": "0.3.17", + "Wabbajack.VFS": "0.3.17" }, "runtime": { "Wabbajack.Downloaders.GameFile.dll": {} } }, - "Wabbajack.Downloaders.GoogleDrive/0.3.16": { + "Wabbajack.Downloaders.GoogleDrive/0.3.17": { "dependencies": { "HtmlAgilityPack": "1.11.72", "Microsoft.AspNetCore.Http.Extensions": "2.3.0", "Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.Logging.Abstractions": "9.0.1", - "Wabbajack.Common": "0.3.16", - "Wabbajack.DTOs": "0.3.16", - "Wabbajack.Downloaders.Interfaces": "0.3.16", - "Wabbajack.Networking.Http": "0.3.16", - "Wabbajack.Networking.Http.Interfaces": "0.3.16" + "Wabbajack.Common": "0.3.17", + "Wabbajack.DTOs": "0.3.17", + "Wabbajack.Downloaders.Interfaces": "0.3.17", + "Wabbajack.Networking.Http": "0.3.17", + "Wabbajack.Networking.Http.Interfaces": "0.3.17" }, "runtime": { "Wabbajack.Downloaders.GoogleDrive.dll": {} } }, - "Wabbajack.Downloaders.Http/0.3.16": { + "Wabbajack.Downloaders.Http/0.3.17": { "dependencies": { "Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.Logging.Abstractions": "9.0.1", - "Wabbajack.Common": "0.3.16", - "Wabbajack.DTOs": "0.3.16", - "Wabbajack.Downloaders.Interfaces": "0.3.16", - "Wabbajack.Networking.BethesdaNet": "0.3.16", - "Wabbajack.Networking.Http.Interfaces": "0.3.16", - "Wabbajack.Paths.IO": "0.3.16" + "Wabbajack.Common": "0.3.17", + "Wabbajack.DTOs": "0.3.17", + "Wabbajack.Downloaders.Interfaces": "0.3.17", + "Wabbajack.Networking.BethesdaNet": "0.3.17", + "Wabbajack.Networking.Http.Interfaces": "0.3.17", + "Wabbajack.Paths.IO": "0.3.17" }, "runtime": { "Wabbajack.Downloaders.Http.dll": {} } }, - "Wabbajack.Downloaders.Interfaces/0.3.16": { + "Wabbajack.Downloaders.Interfaces/0.3.17": { "dependencies": { "Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", - "Wabbajack.Compression.Zip": "0.3.16", - "Wabbajack.DTOs": "0.3.16", - "Wabbajack.Paths.IO": "0.3.16" + "Wabbajack.Compression.Zip": "0.3.17", + "Wabbajack.DTOs": "0.3.17", + "Wabbajack.Paths.IO": "0.3.17" }, "runtime": { "Wabbajack.Downloaders.Interfaces.dll": {} } }, - "Wabbajack.Downloaders.IPS4OAuth2Downloader/0.3.16": { + "Wabbajack.Downloaders.IPS4OAuth2Downloader/0.3.17": { "dependencies": { "F23.StringSimilarity": "6.0.0", "Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.Logging.Abstractions": "9.0.1", - "Wabbajack.Common": "0.3.16", - "Wabbajack.Downloaders.Interfaces": "0.3.16", - "Wabbajack.Networking.Http": "0.3.16", - "Wabbajack.Networking.Http.Interfaces": "0.3.16" + "Wabbajack.Common": "0.3.17", + "Wabbajack.Downloaders.Interfaces": "0.3.17", + "Wabbajack.Networking.Http": "0.3.17", + "Wabbajack.Networking.Http.Interfaces": "0.3.17" }, "runtime": { "Wabbajack.Downloaders.IPS4OAuth2Downloader.dll": {} } }, - "Wabbajack.Downloaders.Manual/0.3.16": { + "Wabbajack.Downloaders.Manual/0.3.17": { "dependencies": { "Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.Logging.Abstractions": "9.0.1", - "Wabbajack.Common": "0.3.16", - "Wabbajack.Downloaders.Interfaces": "0.3.16" + "Wabbajack.Common": "0.3.17", + "Wabbajack.Downloaders.Interfaces": "0.3.17" }, "runtime": { "Wabbajack.Downloaders.Manual.dll": {} } }, - "Wabbajack.Downloaders.MediaFire/0.3.16": { + "Wabbajack.Downloaders.MediaFire/0.3.17": { "dependencies": { "HtmlAgilityPack": "1.11.72", "Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.Logging.Abstractions": "9.0.1", - "Wabbajack.Common": "0.3.16", - "Wabbajack.Downloaders.Interfaces": "0.3.16", - "Wabbajack.Networking.Http.Interfaces": "0.3.16" + "Wabbajack.Common": "0.3.17", + "Wabbajack.Downloaders.Interfaces": "0.3.17", + "Wabbajack.Networking.Http.Interfaces": "0.3.17" }, "runtime": { "Wabbajack.Downloaders.MediaFire.dll": {} } }, - "Wabbajack.Downloaders.Mega/0.3.16": { + "Wabbajack.Downloaders.Mega/0.3.17": { "dependencies": { "Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.Logging.Abstractions": "9.0.1", "Newtonsoft.Json": "13.0.3", - "Wabbajack.Common": "0.3.16", - "Wabbajack.Downloaders.Interfaces": "0.3.16", - "Wabbajack.Paths.IO": "0.3.16" + "Wabbajack.Common": "0.3.17", + "Wabbajack.Downloaders.Interfaces": "0.3.17", + "Wabbajack.Paths.IO": "0.3.17" }, "runtime": { "Wabbajack.Downloaders.Mega.dll": {} } }, - "Wabbajack.Downloaders.ModDB/0.3.16": { + "Wabbajack.Downloaders.ModDB/0.3.17": { "dependencies": { "HtmlAgilityPack": "1.11.72", "Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.Logging.Abstractions": "9.0.1", "Newtonsoft.Json": "13.0.3", - "Wabbajack.Common": "0.3.16", - "Wabbajack.Downloaders.Interfaces": "0.3.16", - "Wabbajack.Networking.Http": "0.3.16", - "Wabbajack.Networking.Http.Interfaces": "0.3.16" + "Wabbajack.Common": "0.3.17", + "Wabbajack.Downloaders.Interfaces": "0.3.17", + "Wabbajack.Networking.Http": "0.3.17", + "Wabbajack.Networking.Http.Interfaces": "0.3.17" }, "runtime": { "Wabbajack.Downloaders.ModDB.dll": {} } }, - "Wabbajack.Downloaders.Nexus/0.3.16": { + "Wabbajack.Downloaders.Nexus/0.3.17": { "dependencies": { "Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", - "Wabbajack.DTOs": "0.3.16", - "Wabbajack.Downloaders.Interfaces": "0.3.16", - "Wabbajack.Hashing.xxHash64": "0.3.16", - "Wabbajack.Networking.Http": "0.3.16", - "Wabbajack.Networking.Http.Interfaces": "0.3.16", - "Wabbajack.Networking.NexusApi": "0.3.16", - "Wabbajack.Paths": "0.3.16" + "Wabbajack.DTOs": "0.3.17", + "Wabbajack.Downloaders.Interfaces": "0.3.17", + "Wabbajack.Hashing.xxHash64": "0.3.17", + "Wabbajack.Networking.Http": "0.3.17", + "Wabbajack.Networking.Http.Interfaces": "0.3.17", + "Wabbajack.Networking.NexusApi": "0.3.17", + "Wabbajack.Paths": "0.3.17" }, "runtime": { "Wabbajack.Downloaders.Nexus.dll": {} } }, - "Wabbajack.Downloaders.VerificationCache/0.3.16": { + "Wabbajack.Downloaders.VerificationCache/0.3.17": { "dependencies": { "Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.Logging.Abstractions": "9.0.1", "Stub.System.Data.SQLite.Core.NetStandard": "1.0.119", - "Wabbajack.DTOs": "0.3.16", - "Wabbajack.Paths.IO": "0.3.16" + "Wabbajack.DTOs": "0.3.17", + "Wabbajack.Paths.IO": "0.3.17" }, "runtime": { "Wabbajack.Downloaders.VerificationCache.dll": {} } }, - "Wabbajack.Downloaders.WabbajackCDN/0.3.16": { + "Wabbajack.Downloaders.WabbajackCDN/0.3.17": { "dependencies": { "Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.Logging.Abstractions": "9.0.1", "Microsoft.Toolkit.HighPerformance": "7.1.2", - "Wabbajack.Common": "0.3.16", - "Wabbajack.Downloaders.Interfaces": "0.3.16", - "Wabbajack.Networking.Http": "0.3.16", - "Wabbajack.RateLimiter": "0.3.16" + "Wabbajack.Common": "0.3.17", + "Wabbajack.Downloaders.Interfaces": "0.3.17", + "Wabbajack.Networking.Http": "0.3.17", + "Wabbajack.RateLimiter": "0.3.17" }, "runtime": { "Wabbajack.Downloaders.WabbajackCDN.dll": {} } }, - "Wabbajack.DTOs/0.3.16": { + "Wabbajack.DTOs/0.3.17": { "dependencies": { "Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", - "Wabbajack.Hashing.xxHash64": "0.3.16", - "Wabbajack.Paths": "0.3.16" + "Wabbajack.Hashing.xxHash64": "0.3.17", + "Wabbajack.Paths": "0.3.17" }, "runtime": { "Wabbajack.DTOs.dll": {} } }, - "Wabbajack.FileExtractor/0.3.16": { + "Wabbajack.FileExtractor/0.3.17": { "dependencies": { "Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.Logging.Abstractions": "9.0.1", "OMODFramework": "3.0.1", - "Wabbajack.Common": "0.3.16", - "Wabbajack.Compression.BSA": "0.3.16", - "Wabbajack.Hashing.PHash": "0.3.16", - "Wabbajack.Paths": "0.3.16" + "Wabbajack.Common": "0.3.17", + "Wabbajack.Compression.BSA": "0.3.17", + "Wabbajack.Hashing.PHash": "0.3.17", + "Wabbajack.Paths": "0.3.17" }, "runtime": { "Wabbajack.FileExtractor.dll": {} } }, - "Wabbajack.Hashing.PHash/0.3.16": { + "Wabbajack.Hashing.PHash/0.3.17": { "dependencies": { "BCnEncoder.Net.ImageSharp": "1.1.1", "Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Shipwreck.Phash": "0.5.0", "SixLabors.ImageSharp": "3.1.6", - "Wabbajack.Common": "0.3.16", - "Wabbajack.DTOs": "0.3.16", - "Wabbajack.Paths": "0.3.16", - "Wabbajack.Paths.IO": "0.3.16" + "Wabbajack.Common": "0.3.17", + "Wabbajack.DTOs": "0.3.17", + "Wabbajack.Paths": "0.3.17", + "Wabbajack.Paths.IO": "0.3.17" }, "runtime": { "Wabbajack.Hashing.PHash.dll": {} } }, - "Wabbajack.Hashing.xxHash64/0.3.16": { + "Wabbajack.Hashing.xxHash64/0.3.17": { "dependencies": { - "Wabbajack.Paths": "0.3.16", - "Wabbajack.RateLimiter": "0.3.16" + "Wabbajack.Paths": "0.3.17", + "Wabbajack.RateLimiter": "0.3.17" }, "runtime": { "Wabbajack.Hashing.xxHash64.dll": {} } }, - "Wabbajack.Installer/0.3.16": { + "Wabbajack.Installer/0.3.17": { "dependencies": { "Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Newtonsoft.Json": "13.0.3", "Octopus.Octodiff": "2.0.548", "SixLabors.ImageSharp": "3.1.6", - "Wabbajack.DTOs": "0.3.16", - "Wabbajack.Downloaders.Dispatcher": "0.3.16", - "Wabbajack.Downloaders.GameFile": "0.3.16", - "Wabbajack.FileExtractor": "0.3.16", - "Wabbajack.Networking.WabbajackClientApi": "0.3.16", - "Wabbajack.Paths": "0.3.16", - "Wabbajack.Paths.IO": "0.3.16", - "Wabbajack.VFS": "0.3.16", + "Wabbajack.DTOs": "0.3.17", + "Wabbajack.Downloaders.Dispatcher": "0.3.17", + "Wabbajack.Downloaders.GameFile": "0.3.17", + "Wabbajack.FileExtractor": "0.3.17", + "Wabbajack.Networking.WabbajackClientApi": "0.3.17", + "Wabbajack.Paths": "0.3.17", + "Wabbajack.Paths.IO": "0.3.17", + "Wabbajack.VFS": "0.3.17", "ini-parser-netstandard": "2.5.2" }, "runtime": { "Wabbajack.Installer.dll": {} } }, - "Wabbajack.IO.Async/0.3.16": { + "Wabbajack.IO.Async/0.3.17": { "runtime": { "Wabbajack.IO.Async.dll": {} } }, - "Wabbajack.Networking.BethesdaNet/0.3.16": { + "Wabbajack.Networking.BethesdaNet/0.3.17": { "dependencies": { "Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", - "Wabbajack.DTOs": "0.3.16", - "Wabbajack.Networking.Http": "0.3.16", - "Wabbajack.Networking.Http.Interfaces": "0.3.16" + "Wabbajack.DTOs": "0.3.17", + "Wabbajack.Networking.Http": "0.3.17", + "Wabbajack.Networking.Http.Interfaces": "0.3.17" }, "runtime": { "Wabbajack.Networking.BethesdaNet.dll": {} } }, - "Wabbajack.Networking.Discord/0.3.16": { + "Wabbajack.Networking.Discord/0.3.17": { "dependencies": { "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.Logging.Abstractions": "9.0.1", - "Wabbajack.Networking.Http.Interfaces": "0.3.16" + "Wabbajack.Networking.Http.Interfaces": "0.3.17" }, "runtime": { "Wabbajack.Networking.Discord.dll": {} } }, - "Wabbajack.Networking.GitHub/0.3.16": { + "Wabbajack.Networking.GitHub/0.3.17": { "dependencies": { "Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.Logging.Abstractions": "9.0.1", "Octokit": "14.0.0", - "Wabbajack.DTOs": "0.3.16", - "Wabbajack.Networking.Http.Interfaces": "0.3.16" + "Wabbajack.DTOs": "0.3.17", + "Wabbajack.Networking.Http.Interfaces": "0.3.17" }, "runtime": { "Wabbajack.Networking.GitHub.dll": {} } }, - "Wabbajack.Networking.Http/0.3.16": { + "Wabbajack.Networking.Http/0.3.17": { "dependencies": { "Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.Http": "9.0.1", "Microsoft.Extensions.Logging": "9.0.1", - "Wabbajack.Configuration": "0.3.16", - "Wabbajack.Downloaders.Interfaces": "0.3.16", - "Wabbajack.Hashing.xxHash64": "0.3.16", - "Wabbajack.Networking.Http.Interfaces": "0.3.16", - "Wabbajack.Paths": "0.3.16", - "Wabbajack.Paths.IO": "0.3.16" + "Wabbajack.Configuration": "0.3.17", + "Wabbajack.Downloaders.Interfaces": "0.3.17", + "Wabbajack.Hashing.xxHash64": "0.3.17", + "Wabbajack.Networking.Http.Interfaces": "0.3.17", + "Wabbajack.Paths": "0.3.17", + "Wabbajack.Paths.IO": "0.3.17" }, "runtime": { "Wabbajack.Networking.Http.dll": {} } }, - "Wabbajack.Networking.Http.Interfaces/0.3.16": { + "Wabbajack.Networking.Http.Interfaces/0.3.17": { "dependencies": { - "Wabbajack.Hashing.xxHash64": "0.3.16" + "Wabbajack.Hashing.xxHash64": "0.3.17" }, "runtime": { "Wabbajack.Networking.Http.Interfaces.dll": {} } }, - "Wabbajack.Networking.NexusApi/0.3.16": { + "Wabbajack.Networking.NexusApi/0.3.17": { "dependencies": { "Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.Logging.Abstractions": "9.0.1", - "Wabbajack.DTOs": "0.3.16", - "Wabbajack.Networking.Http": "0.3.16", - "Wabbajack.Networking.Http.Interfaces": "0.3.16", - "Wabbajack.Networking.WabbajackClientApi": "0.3.16" + "Wabbajack.DTOs": "0.3.17", + "Wabbajack.Networking.Http": "0.3.17", + "Wabbajack.Networking.Http.Interfaces": "0.3.17", + "Wabbajack.Networking.WabbajackClientApi": "0.3.17" }, "runtime": { "Wabbajack.Networking.NexusApi.dll": {} } }, - "Wabbajack.Networking.WabbajackClientApi/0.3.16": { + "Wabbajack.Networking.WabbajackClientApi/0.3.17": { "dependencies": { "Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.Logging.Abstractions": "9.0.1", "Octokit": "14.0.0", - "Wabbajack.Common": "0.3.16", - "Wabbajack.DTOs": "0.3.16", - "Wabbajack.Paths.IO": "0.3.16", - "Wabbajack.VFS.Interfaces": "0.3.16", + "Wabbajack.Common": "0.3.17", + "Wabbajack.DTOs": "0.3.17", + "Wabbajack.Paths.IO": "0.3.17", + "Wabbajack.VFS.Interfaces": "0.3.17", "YamlDotNet": "16.3.0" }, "runtime": { "Wabbajack.Networking.WabbajackClientApi.dll": {} } }, - "Wabbajack.Paths/0.3.16": { + "Wabbajack.Paths/0.3.17": { "runtime": { "Wabbajack.Paths.dll": {} } }, - "Wabbajack.Paths.IO/0.3.16": { + "Wabbajack.Paths.IO/0.3.17": { "dependencies": { - "Wabbajack.Paths": "0.3.16", + "Wabbajack.Paths": "0.3.17", "shortid": "4.0.0" }, "runtime": { "Wabbajack.Paths.IO.dll": {} } }, - "Wabbajack.RateLimiter/0.3.16": { + "Wabbajack.RateLimiter/0.3.17": { "runtime": { "Wabbajack.RateLimiter.dll": {} } }, - "Wabbajack.Server.Lib/0.3.16": { + "Wabbajack.Server.Lib/0.3.17": { "dependencies": { "FluentFTP": "52.0.0", "Microsoft.Extensions.DependencyInjection": "9.0.1", @@ -2264,58 +2264,58 @@ "Nettle": "3.0.0", "Newtonsoft.Json": "13.0.3", "SixLabors.ImageSharp": "3.1.6", - "Wabbajack.Common": "0.3.16", - "Wabbajack.Networking.Http.Interfaces": "0.3.16", - "Wabbajack.Services.OSIntegrated": "0.3.16" + "Wabbajack.Common": "0.3.17", + "Wabbajack.Networking.Http.Interfaces": "0.3.17", + "Wabbajack.Services.OSIntegrated": "0.3.17" }, "runtime": { "Wabbajack.Server.Lib.dll": {} } }, - "Wabbajack.Services.OSIntegrated/0.3.16": { + "Wabbajack.Services.OSIntegrated/0.3.17": { "dependencies": { "DeviceId": "6.8.0", "Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Newtonsoft.Json": "13.0.3", "SixLabors.ImageSharp": "3.1.6", - "Wabbajack.Compiler": "0.3.16", - "Wabbajack.Downloaders.Dispatcher": "0.3.16", - "Wabbajack.Installer": "0.3.16", - "Wabbajack.Networking.BethesdaNet": "0.3.16", - "Wabbajack.Networking.Discord": "0.3.16", - "Wabbajack.VFS": "0.3.16" + "Wabbajack.Compiler": "0.3.17", + "Wabbajack.Downloaders.Dispatcher": "0.3.17", + "Wabbajack.Installer": "0.3.17", + "Wabbajack.Networking.BethesdaNet": "0.3.17", + "Wabbajack.Networking.Discord": "0.3.17", + "Wabbajack.VFS": "0.3.17" }, "runtime": { "Wabbajack.Services.OSIntegrated.dll": {} } }, - "Wabbajack.VFS/0.3.16": { + "Wabbajack.VFS/0.3.17": { "dependencies": { "Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.Logging.Abstractions": "9.0.1", "SixLabors.ImageSharp": "3.1.6", "System.Data.SQLite.Core": "1.0.119", - "Wabbajack.Common": "0.3.16", - "Wabbajack.FileExtractor": "0.3.16", - "Wabbajack.Hashing.PHash": "0.3.16", - "Wabbajack.Hashing.xxHash64": "0.3.16", - "Wabbajack.Paths": "0.3.16", - "Wabbajack.Paths.IO": "0.3.16", - "Wabbajack.VFS.Interfaces": "0.3.16" + "Wabbajack.Common": "0.3.17", + "Wabbajack.FileExtractor": "0.3.17", + "Wabbajack.Hashing.PHash": "0.3.17", + "Wabbajack.Hashing.xxHash64": "0.3.17", + "Wabbajack.Paths": "0.3.17", + "Wabbajack.Paths.IO": "0.3.17", + "Wabbajack.VFS.Interfaces": "0.3.17" }, "runtime": { "Wabbajack.VFS.dll": {} } }, - "Wabbajack.VFS.Interfaces/0.3.16": { + "Wabbajack.VFS.Interfaces/0.3.17": { "dependencies": { "Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", - "Wabbajack.DTOs": "0.3.16", - "Wabbajack.Hashing.xxHash64": "0.3.16", - "Wabbajack.Paths": "0.3.16" + "Wabbajack.DTOs": "0.3.17", + "Wabbajack.Hashing.xxHash64": "0.3.17", + "Wabbajack.Paths": "0.3.17" }, "runtime": { "Wabbajack.VFS.Interfaces.dll": {} @@ -2332,7 +2332,7 @@ } }, "libraries": { - "jackify-engine/0.3.16": { + "jackify-engine/0.3.17": { "type": "project", "serviceable": false, "sha512": "" @@ -3021,202 +3021,202 @@ "path": "yamldotnet/16.3.0", "hashPath": "yamldotnet.16.3.0.nupkg.sha512" }, - "Wabbajack.CLI.Builder/0.3.16": { + "Wabbajack.CLI.Builder/0.3.17": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.Common/0.3.16": { + "Wabbajack.Common/0.3.17": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.Compiler/0.3.16": { + "Wabbajack.Compiler/0.3.17": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.Compression.BSA/0.3.16": { + "Wabbajack.Compression.BSA/0.3.17": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.Compression.Zip/0.3.16": { + "Wabbajack.Compression.Zip/0.3.17": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.Configuration/0.3.16": { + "Wabbajack.Configuration/0.3.17": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.Downloaders.Bethesda/0.3.16": { + "Wabbajack.Downloaders.Bethesda/0.3.17": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.Downloaders.Dispatcher/0.3.16": { + "Wabbajack.Downloaders.Dispatcher/0.3.17": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.Downloaders.GameFile/0.3.16": { + "Wabbajack.Downloaders.GameFile/0.3.17": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.Downloaders.GoogleDrive/0.3.16": { + "Wabbajack.Downloaders.GoogleDrive/0.3.17": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.Downloaders.Http/0.3.16": { + "Wabbajack.Downloaders.Http/0.3.17": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.Downloaders.Interfaces/0.3.16": { + "Wabbajack.Downloaders.Interfaces/0.3.17": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.Downloaders.IPS4OAuth2Downloader/0.3.16": { + "Wabbajack.Downloaders.IPS4OAuth2Downloader/0.3.17": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.Downloaders.Manual/0.3.16": { + "Wabbajack.Downloaders.Manual/0.3.17": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.Downloaders.MediaFire/0.3.16": { + "Wabbajack.Downloaders.MediaFire/0.3.17": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.Downloaders.Mega/0.3.16": { + "Wabbajack.Downloaders.Mega/0.3.17": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.Downloaders.ModDB/0.3.16": { + "Wabbajack.Downloaders.ModDB/0.3.17": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.Downloaders.Nexus/0.3.16": { + "Wabbajack.Downloaders.Nexus/0.3.17": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.Downloaders.VerificationCache/0.3.16": { + "Wabbajack.Downloaders.VerificationCache/0.3.17": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.Downloaders.WabbajackCDN/0.3.16": { + "Wabbajack.Downloaders.WabbajackCDN/0.3.17": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.DTOs/0.3.16": { + "Wabbajack.DTOs/0.3.17": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.FileExtractor/0.3.16": { + "Wabbajack.FileExtractor/0.3.17": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.Hashing.PHash/0.3.16": { + "Wabbajack.Hashing.PHash/0.3.17": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.Hashing.xxHash64/0.3.16": { + "Wabbajack.Hashing.xxHash64/0.3.17": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.Installer/0.3.16": { + "Wabbajack.Installer/0.3.17": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.IO.Async/0.3.16": { + "Wabbajack.IO.Async/0.3.17": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.Networking.BethesdaNet/0.3.16": { + "Wabbajack.Networking.BethesdaNet/0.3.17": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.Networking.Discord/0.3.16": { + "Wabbajack.Networking.Discord/0.3.17": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.Networking.GitHub/0.3.16": { + "Wabbajack.Networking.GitHub/0.3.17": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.Networking.Http/0.3.16": { + "Wabbajack.Networking.Http/0.3.17": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.Networking.Http.Interfaces/0.3.16": { + "Wabbajack.Networking.Http.Interfaces/0.3.17": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.Networking.NexusApi/0.3.16": { + "Wabbajack.Networking.NexusApi/0.3.17": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.Networking.WabbajackClientApi/0.3.16": { + "Wabbajack.Networking.WabbajackClientApi/0.3.17": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.Paths/0.3.16": { + "Wabbajack.Paths/0.3.17": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.Paths.IO/0.3.16": { + "Wabbajack.Paths.IO/0.3.17": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.RateLimiter/0.3.16": { + "Wabbajack.RateLimiter/0.3.17": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.Server.Lib/0.3.16": { + "Wabbajack.Server.Lib/0.3.17": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.Services.OSIntegrated/0.3.16": { + "Wabbajack.Services.OSIntegrated/0.3.17": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.VFS/0.3.16": { + "Wabbajack.VFS/0.3.17": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.VFS.Interfaces/0.3.16": { + "Wabbajack.VFS.Interfaces/0.3.17": { "type": "project", "serviceable": false, "sha512": "" diff --git a/jackify/engine/jackify-engine.dll b/jackify/engine/jackify-engine.dll index 8966d15..bc0e2a9 100644 Binary files a/jackify/engine/jackify-engine.dll and b/jackify/engine/jackify-engine.dll differ diff --git a/jackify/frontends/gui/dialogs/update_dialog.py b/jackify/frontends/gui/dialogs/update_dialog.py index ab38f98..3606bc1 100644 --- a/jackify/frontends/gui/dialogs/update_dialog.py +++ b/jackify/frontends/gui/dialogs/update_dialog.py @@ -256,18 +256,24 @@ class UpdateDialog(QDialog): self.downloaded_path = downloaded_path self.progress_label.setText("Download completed successfully!") self.progress_bar.setValue(100) - - # Show install button - self.download_button.setVisible(False) - self.install_button.setVisible(True) - - # Re-enable other buttons - self.later_button.setEnabled(True) - self.skip_button.setEnabled(True) - + + # Check if auto-restart is enabled + if self.auto_restart_checkbox.isChecked(): + # Auto-install immediately + self.progress_label.setText("Auto-installing update...") + self.install_update() + else: + # Show install button for manual installation + self.download_button.setVisible(False) + self.install_button.setVisible(True) + + # Re-enable other buttons + self.later_button.setEnabled(True) + self.skip_button.setEnabled(True) + else: self.show_error("Download Failed", "Failed to download the update. Please try again later.") - + # Reset UI self.progress_group.setVisible(False) self.download_button.setEnabled(True) diff --git a/jackify/frontends/gui/main.py b/jackify/frontends/gui/main.py index fd61821..4cdb6ca 100644 --- a/jackify/frontends/gui/main.py +++ b/jackify/frontends/gui/main.py @@ -303,29 +303,50 @@ class SettingsDialog(QDialog): general_layout.addWidget(api_group) general_layout.addSpacing(12) - # --- Default Proton Version Section --- - proton_group = QGroupBox("Default Proton Version") + # --- Proton Version Settings Section --- + proton_group = QGroupBox("Proton Version Settings") proton_group.setStyleSheet("QGroupBox { border: 1px solid #555; border-radius: 6px; margin-top: 8px; padding: 8px; background: #23282d; } QGroupBox:title { subcontrol-origin: margin; left: 10px; padding: 0 3px 0 3px; font-weight: bold; color: #fff; }") - proton_layout = QHBoxLayout() + proton_layout = QVBoxLayout() proton_group.setLayout(proton_layout) - self.proton_dropdown = QComboBox() - self.proton_dropdown.setToolTip("Select default Proton version for shortcut creation and texture processing") - self.proton_dropdown.setMinimumWidth(200) + # Install Proton Version (for jackify-engine texture processing) + install_proton_layout = QHBoxLayout() + self.install_proton_dropdown = QComboBox() + self.install_proton_dropdown.setToolTip("Proton version for modlist installation and texture processing (requires fast Proton)") + self.install_proton_dropdown.setMinimumWidth(200) - # Populate Proton dropdown - self._populate_proton_dropdown() + install_refresh_btn = QPushButton("↻") + install_refresh_btn.setFixedSize(30, 30) + install_refresh_btn.setToolTip("Refresh install Proton version list") + install_refresh_btn.clicked.connect(self._refresh_install_proton_dropdown) - # Refresh button for Proton detection - refresh_btn = QPushButton("↻") - refresh_btn.setFixedSize(30, 30) - refresh_btn.setToolTip("Refresh Proton version list") - refresh_btn.clicked.connect(self._refresh_proton_dropdown) + install_proton_layout.addWidget(QLabel("Install Proton:")) + install_proton_layout.addWidget(self.install_proton_dropdown) + install_proton_layout.addWidget(install_refresh_btn) + install_proton_layout.addStretch() - proton_layout.addWidget(QLabel("Proton Version:")) - proton_layout.addWidget(self.proton_dropdown) - proton_layout.addWidget(refresh_btn) - proton_layout.addStretch() + # Game Proton Version (for game shortcuts) + game_proton_layout = QHBoxLayout() + self.game_proton_dropdown = QComboBox() + self.game_proton_dropdown.setToolTip("Proton version for game shortcuts (can be any Proton 9+)") + self.game_proton_dropdown.setMinimumWidth(200) + + game_refresh_btn = QPushButton("↻") + game_refresh_btn.setFixedSize(30, 30) + game_refresh_btn.setToolTip("Refresh game Proton version list") + game_refresh_btn.clicked.connect(self._refresh_game_proton_dropdown) + + game_proton_layout.addWidget(QLabel("Game Proton:")) + game_proton_layout.addWidget(self.game_proton_dropdown) + game_proton_layout.addWidget(game_refresh_btn) + game_proton_layout.addStretch() + + proton_layout.addLayout(install_proton_layout) + proton_layout.addLayout(game_proton_layout) + + # Populate both Proton dropdowns + self._populate_install_proton_dropdown() + self._populate_game_proton_dropdown() general_layout.addWidget(proton_group) general_layout.addSpacing(12) @@ -416,6 +437,22 @@ class SettingsDialog(QDialog): self.bandwidth_spin = None # No bandwidth UI if Downloads resource doesn't exist advanced_layout.addWidget(resource_group) + + # Component Installation Method Section + component_group = QGroupBox("Component Installation") + component_group.setStyleSheet("QGroupBox { border: 1px solid #555; border-radius: 6px; margin-top: 8px; padding: 8px; background: #23282d; } QGroupBox:title { subcontrol-origin: margin; left: 10px; padding: 0 3px 0 3px; font-weight: bold; color: #fff; }") + component_layout = QVBoxLayout() + component_group.setLayout(component_layout) + + 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.setToolTip( + "When enabled: Uses winetricks for most components (faster) and protontricks only for dotnet40 (more reliable).\n" + "When disabled: Uses protontricks for all components (legacy behavior, slower but more compatible)." + ) + component_layout.addWidget(self.use_winetricks_checkbox) + + advanced_layout.addWidget(component_group) advanced_layout.addStretch() # Add stretch to push content to top self.tab_widget.addTab(advanced_tab, "Advanced") @@ -488,167 +525,250 @@ class SettingsDialog(QDialog): except: return 'auto' - def _populate_proton_dropdown(self): - """Populate Proton version dropdown with detected versions (includes GE-Proton and Valve Proton)""" + def _populate_install_proton_dropdown(self): + """Populate Install Proton dropdown (Experimental/GE-Proton 10+ only for fast texture processing)""" try: from jackify.backend.handlers.wine_utils import WineUtils - # Get all available Proton versions (GE-Proton + Valve Proton) + # Get all available Proton versions available_protons = WineUtils.scan_all_proton_versions() # Add "Auto" option first - self.proton_dropdown.addItem("Auto", "auto") + self.install_proton_dropdown.addItem("Auto (Recommended)", "auto") + + # Filter for fast Proton versions only + fast_protons = [] + slow_protons = [] - # Add detected Proton versions with type indicators for proton in available_protons: proton_name = proton.get('name', 'Unknown Proton') proton_type = proton.get('type', 'Unknown') - # Format display name to show type for clarity + is_fast_proton = False + + # Fast Protons: Experimental, GE-Proton 10+ + if proton_name == "Proton - Experimental": + is_fast_proton = True + elif proton_type == 'GE-Proton': + # For GE-Proton, check major_version field + major_version = proton.get('major_version', 0) + if major_version >= 10: + is_fast_proton = True + + if is_fast_proton: + if proton_type == 'GE-Proton': + display_name = f"{proton_name} (GE)" + else: + display_name = proton_name + fast_protons.append((display_name, str(proton['path']))) + else: + # Slow Protons: Valve 9, 10 beta, older GE-Proton, etc. + if proton_type == 'GE-Proton': + display_name = f"{proton_name} (GE) (Slow texture processing)" + else: + display_name = f"{proton_name} (Slow texture processing)" + slow_protons.append((display_name, str(proton['path']))) + + # Add fast Protons first + for display_name, path in fast_protons: + self.install_proton_dropdown.addItem(display_name, path) + + # Add separator and slow Protons with warnings + if slow_protons: + self.install_proton_dropdown.insertSeparator(self.install_proton_dropdown.count()) + for display_name, path in slow_protons: + self.install_proton_dropdown.addItem(display_name, path) + + # Load saved preference + saved_proton = self.config_handler.get('proton_path', self._get_proton_10_path()) + self._set_dropdown_selection(self.install_proton_dropdown, saved_proton) + + except Exception as e: + logger.error(f"Failed to populate install Proton dropdown: {e}") + self.install_proton_dropdown.addItem("Auto (Recommended)", "auto") + + def _populate_game_proton_dropdown(self): + """Populate Game Proton dropdown (any Proton 9+ for game compatibility)""" + try: + from jackify.backend.handlers.wine_utils import WineUtils + + # Get all available Proton versions + available_protons = WineUtils.scan_all_proton_versions() + + # Add "Same as Install" option first + self.game_proton_dropdown.addItem("Same as Install Proton", "same_as_install") + + # Add all Proton 9+ versions + for proton in available_protons: + proton_name = proton.get('name', 'Unknown Proton') + proton_type = proton.get('type', 'Unknown') + + # Add type indicator for clarity if proton_type == 'GE-Proton': display_name = f"{proton_name} (GE)" - elif proton_type == 'Valve-Proton': - display_name = f"{proton_name}" else: display_name = proton_name - self.proton_dropdown.addItem(display_name, str(proton['path'])) + self.game_proton_dropdown.addItem(display_name, str(proton['path'])) - # Load saved preference and determine UI selection - saved_proton = self.config_handler.get('proton_path', self._get_proton_10_path()) - - # Check if saved path matches any specific Proton in dropdown - found_match = False - for i in range(self.proton_dropdown.count()): - if self.proton_dropdown.itemData(i) == saved_proton: - self.proton_dropdown.setCurrentIndex(i) - found_match = True - break - - # If no exact match found, check if it's a resolved auto-selection - if not found_match and saved_proton != "auto": - # This means config has a resolved path from previous "Auto" selection - # Show "Auto" in UI since user chose auto-detection - for i in range(self.proton_dropdown.count()): - if self.proton_dropdown.itemData(i) == "auto": - self.proton_dropdown.setCurrentIndex(i) - break + # Load saved preference + saved_game_proton = self.config_handler.get('game_proton_path', 'same_as_install') + self._set_dropdown_selection(self.game_proton_dropdown, saved_game_proton) except Exception as e: - logger.error(f"Failed to populate Proton dropdown: {e}") - # Fallback: just show auto - self.proton_dropdown.addItem("Auto", "auto") + logger.error(f"Failed to populate game Proton dropdown: {e}") + self.game_proton_dropdown.addItem("Same as Install Proton", "same_as_install") - def _refresh_proton_dropdown(self): - """Refresh Proton dropdown with latest detected versions""" - current_selection = self.proton_dropdown.currentData() - self.proton_dropdown.clear() - self._populate_proton_dropdown() - - # Restore selection if still available - for i in range(self.proton_dropdown.count()): - if self.proton_dropdown.itemData(i) == current_selection: - self.proton_dropdown.setCurrentIndex(i) + def _set_dropdown_selection(self, dropdown, saved_value): + """Helper to set dropdown selection based on saved value""" + found_match = False + for i in range(dropdown.count()): + if dropdown.itemData(i) == saved_value: + dropdown.setCurrentIndex(i) + found_match = True break + # If no exact match and not auto/same_as_install, select first option + if not found_match and saved_value not in ["auto", "same_as_install"]: + dropdown.setCurrentIndex(0) + + def _refresh_install_proton_dropdown(self): + """Refresh Install Proton dropdown""" + current_selection = self.install_proton_dropdown.currentData() + self.install_proton_dropdown.clear() + self._populate_install_proton_dropdown() + self._set_dropdown_selection(self.install_proton_dropdown, current_selection) + + def _refresh_game_proton_dropdown(self): + """Refresh Game Proton dropdown""" + current_selection = self.game_proton_dropdown.currentData() + self.game_proton_dropdown.clear() + self._populate_game_proton_dropdown() + self._set_dropdown_selection(self.game_proton_dropdown, current_selection) + def _save(self): - # Validate values - for k, (multithreading_checkbox, max_tasks_spin) in self.resource_edits.items(): - if max_tasks_spin.value() > 128: - self.error_label.setText(f"Invalid value for {k}: Max Tasks must be <= 128.") + try: + # Validate values (only if resource_edits exist) + for k, (multithreading_checkbox, max_tasks_spin) in self.resource_edits.items(): + if max_tasks_spin.value() > 128: + self.error_label.setText(f"Invalid value for {k}: Max Tasks must be <= 128.") + return + if self.bandwidth_spin and self.bandwidth_spin.value() > 1000000: + self.error_label.setText("Bandwidth limit must be <= 1,000,000 KB/s.") return - if self.bandwidth_spin and self.bandwidth_spin.value() > 1000000: - self.error_label.setText("Bandwidth limit must be <= 1,000,000 KB/s.") - return - self.error_label.setText("") - # Save resource settings - for k, (multithreading_checkbox, max_tasks_spin) in self.resource_edits.items(): - resource_data = self.resource_settings.get(k, {}) - resource_data['MaxTasks'] = max_tasks_spin.value() - self.resource_settings[k] = resource_data - - # Save bandwidth limit to Downloads resource MaxThroughput (only if bandwidth UI exists) - if self.bandwidth_spin: - if "Downloads" not in self.resource_settings: - self.resource_settings["Downloads"] = {"MaxTasks": 16} # Provide default MaxTasks - self.resource_settings["Downloads"]["MaxThroughput"] = self.bandwidth_spin.value() - - # Save all resource settings (including bandwidth) in one operation - self._save_json(self.resource_settings_path, self.resource_settings) - - # Save debug mode to config - self.config_handler.set('debug_mode', self.debug_checkbox.isChecked()) - # Save API key - api_key = self.api_key_edit.text().strip() - self.config_handler.save_api_key(api_key) - # Save modlist base dirs - self.config_handler.set("modlist_install_base_dir", self.install_dir_edit.text().strip()) - self.config_handler.set("modlist_downloads_base_dir", self.download_dir_edit.text().strip()) - # Save jackify data directory (always store actual path, never None) - jackify_data_dir = self.jackify_data_dir_edit.text().strip() - self.config_handler.set("jackify_data_dir", jackify_data_dir) + self.error_label.setText("") - # Save Proton selection - resolve "auto" to actual path - selected_proton_path = self.proton_dropdown.currentData() - if selected_proton_path == "auto": - # Resolve "auto" to actual best Proton path using unified detection - try: - from jackify.backend.handlers.wine_utils import WineUtils - best_proton = WineUtils.select_best_proton() + # Save resource settings + for k, (multithreading_checkbox, max_tasks_spin) in self.resource_edits.items(): + resource_data = self.resource_settings.get(k, {}) + resource_data['MaxTasks'] = max_tasks_spin.value() + self.resource_settings[k] = resource_data - if best_proton: - resolved_path = str(best_proton['path']) - resolved_version = best_proton['name'] - else: - resolved_path = "auto" - resolved_version = "auto" - except: - resolved_path = "auto" - resolved_version = "auto" - else: - # User selected specific Proton version - resolved_path = selected_proton_path - # Extract version from dropdown text - resolved_version = self.proton_dropdown.currentText() + # Save bandwidth limit to Downloads resource MaxThroughput (only if bandwidth UI exists) + if self.bandwidth_spin: + if "Downloads" not in self.resource_settings: + self.resource_settings["Downloads"] = {"MaxTasks": 16} # Provide default MaxTasks + self.resource_settings["Downloads"]["MaxThroughput"] = self.bandwidth_spin.value() - self.config_handler.set("proton_path", resolved_path) - self.config_handler.set("proton_version", resolved_version) + # Save all resource settings (including bandwidth) in one operation + self._save_json(self.resource_settings_path, self.resource_settings) - # Force immediate save and verify - save_result = self.config_handler.save_config() - if not save_result: - self.logger.error("Failed to save Proton configuration") - else: - self.logger.info(f"Saved Proton config: path={resolved_path}, version={resolved_version}") - # Verify the save worked by reading it back - saved_path = self.config_handler.get("proton_path") - if saved_path != resolved_path: - self.logger.error(f"Config save verification failed: expected {resolved_path}, got {saved_path}") + # Save debug mode to config + self.config_handler.set('debug_mode', self.debug_checkbox.isChecked()) + # Save API key + api_key = self.api_key_edit.text().strip() + self.config_handler.save_api_key(api_key) + # Save modlist base dirs + self.config_handler.set("modlist_install_base_dir", self.install_dir_edit.text().strip()) + self.config_handler.set("modlist_downloads_base_dir", self.download_dir_edit.text().strip()) + # Save jackify data directory (always store actual path, never None) + jackify_data_dir = self.jackify_data_dir_edit.text().strip() + self.config_handler.set("jackify_data_dir", jackify_data_dir) + + # Save Install Proton selection - resolve "auto" to actual path + selected_install_proton_path = self.install_proton_dropdown.currentData() + if selected_install_proton_path == "auto": + # Resolve "auto" to actual best Proton path using unified detection + try: + from jackify.backend.handlers.wine_utils import WineUtils + best_proton = WineUtils.select_best_proton() + + if best_proton: + resolved_install_path = str(best_proton['path']) + resolved_install_version = best_proton['name'] + else: + resolved_install_path = "auto" + resolved_install_version = "auto" + except: + resolved_install_path = "auto" + resolved_install_version = "auto" else: - self.logger.debug("Config save verified successfully") - - # Refresh cached paths in GUI screens if Jackify directory changed - self._refresh_gui_paths() - - # Check if debug mode changed and prompt for restart - new_debug_mode = self.debug_checkbox.isChecked() - if new_debug_mode != self._original_debug_mode: - reply = MessageService.question(self, "Restart Required", "Debug mode change requires a restart. Restart Jackify now?", safety_level="low") - if reply == QMessageBox.Yes: - import os, sys - # User requested restart - do it regardless of execution environment - self.accept() + # User selected specific Proton version + resolved_install_path = selected_install_proton_path + # Extract version from dropdown text + resolved_install_version = self.install_proton_dropdown.currentText() - # Check if running from AppImage - if os.environ.get('APPIMAGE'): - # AppImage: restart the AppImage - os.execv(os.environ['APPIMAGE'], [os.environ['APPIMAGE']] + sys.argv[1:]) + self.config_handler.set("proton_path", resolved_install_path) + self.config_handler.set("proton_version", resolved_install_version) + + # Save Game Proton selection + selected_game_proton_path = self.game_proton_dropdown.currentData() + if selected_game_proton_path == "same_as_install": + # Use same as install proton + resolved_game_path = resolved_install_path + resolved_game_version = resolved_install_version + else: + # User selected specific game Proton version + resolved_game_path = selected_game_proton_path + resolved_game_version = self.game_proton_dropdown.currentText() + + self.config_handler.set("game_proton_path", resolved_game_path) + self.config_handler.set("game_proton_version", resolved_game_version) + + # Save component installation method preference + self.config_handler.set("use_winetricks_for_components", self.use_winetricks_checkbox.isChecked()) + + # Force immediate save and verify + save_result = self.config_handler.save_config() + if not save_result: + self.logger.error("Failed to save Proton configuration") + else: + self.logger.info(f"Saved Proton config: install_path={resolved_install_path}, game_path={resolved_game_path}") + # Verify the save worked by reading it back + saved_path = self.config_handler.get("proton_path") + if saved_path != resolved_install_path: + self.logger.error(f"Config save verification failed: expected {resolved_install_path}, got {saved_path}") else: - # Dev mode: restart the Python module - os.execv(sys.executable, [sys.executable, '-m', 'jackify.frontends.gui'] + sys.argv[1:]) - return - MessageService.information(self, "Settings Saved", "Settings have been saved successfully.", safety_level="low") - self.accept() + self.logger.debug("Config save verified successfully") + + # Refresh cached paths in GUI screens if Jackify directory changed + self._refresh_gui_paths() + + # Check if debug mode changed and prompt for restart + new_debug_mode = self.debug_checkbox.isChecked() + if new_debug_mode != self._original_debug_mode: + reply = MessageService.question(self, "Restart Required", "Debug mode change requires a restart. Restart Jackify now?", safety_level="low") + if reply == QMessageBox.Yes: + import os, sys + # User requested restart - do it regardless of execution environment + self.accept() + + # Check if running from AppImage + if os.environ.get('APPIMAGE'): + # AppImage: restart the AppImage + os.execv(os.environ['APPIMAGE'], [os.environ['APPIMAGE']] + sys.argv[1:]) + else: + # Dev mode: restart the Python module + os.execv(sys.executable, [sys.executable, '-m', 'jackify.frontends.gui'] + sys.argv[1:]) + return + + # If we get here, no restart was needed + MessageService.information(self, "Settings Saved", "Settings have been saved successfully.", safety_level="low") + self.accept() + + except Exception as e: + self.logger.error(f"Error saving settings: {e}") + MessageService.warning(self, "Save Error", f"Failed to save settings: {e}", safety_level="medium") def _refresh_gui_paths(self): """Refresh cached paths in all GUI screens.""" diff --git a/jackify/frontends/gui/screens/configure_existing_modlist.py b/jackify/frontends/gui/screens/configure_existing_modlist.py index 6a1132b..6d65659 100644 --- a/jackify/frontends/gui/screens/configure_existing_modlist.py +++ b/jackify/frontends/gui/screens/configure_existing_modlist.py @@ -499,6 +499,7 @@ class ConfigureExistingModlistScreen(QWidget): # For existing modlists, add resolution if specified if self.resolution != "Leave unchanged": modlist_context.resolution = self.resolution.split()[0] + # Note: If "Leave unchanged" is selected, resolution stays None (no fallback needed) # Define callbacks def progress_callback(message): diff --git a/jackify/frontends/gui/screens/configure_new_modlist.py b/jackify/frontends/gui/screens/configure_new_modlist.py index b5e521b..b58a44d 100644 --- a/jackify/frontends/gui/screens/configure_new_modlist.py +++ b/jackify/frontends/gui/screens/configure_new_modlist.py @@ -1184,7 +1184,7 @@ class ConfigureNewModlistScreen(QWidget): nexus_api_key='', # Not needed for configuration modlist_value='', # Not needed for existing modlist modlist_source='existing', - resolution=self.context.get('resolution'), + resolution=self.context.get('resolution') or get_resolution_fallback(None), skip_confirmation=True ) diff --git a/jackify/frontends/gui/screens/install_modlist.py b/jackify/frontends/gui/screens/install_modlist.py index e6e674e..19fd5ae 100644 --- a/jackify/frontends/gui/screens/install_modlist.py +++ b/jackify/frontends/gui/screens/install_modlist.py @@ -1746,7 +1746,14 @@ class InstallModlistScreen(QWidget): # Save resolution for later use in configuration resolution = self.resolution_combo.currentText() - self._current_resolution = resolution.split()[0] if resolution != "Leave unchanged" else None + # Extract resolution properly (e.g., "1280x800" from "1280x800 (Steam Deck)") + if resolution != "Leave unchanged": + if " (" in resolution: + self._current_resolution = resolution.split(" (")[0] + else: + self._current_resolution = resolution + else: + self._current_resolution = None # Use automated prefix creation instead of manual steps debug_print("DEBUG: Starting automated prefix creation workflow") @@ -1760,7 +1767,14 @@ class InstallModlistScreen(QWidget): # Ensure _current_resolution is always set before starting workflow if not hasattr(self, '_current_resolution') or self._current_resolution is None: resolution = self.resolution_combo.currentText() if hasattr(self, 'resolution_combo') else None - self._current_resolution = resolution.split()[0] if resolution and resolution != "Leave unchanged" else None + # Extract resolution properly (e.g., "1280x800" from "1280x800 (Steam Deck)") + if resolution and resolution != "Leave unchanged": + if " (" in resolution: + self._current_resolution = resolution.split(" (")[0] + else: + self._current_resolution = resolution + else: + self._current_resolution = None """Start the automated prefix creation workflow""" try: # Disable controls during installation