From a7ed4b2a1e4c8eb379c12c8d5fa58cb5ba3b332b Mon Sep 17 00:00:00 2001 From: Omni Date: Tue, 23 Dec 2025 21:49:18 +0000 Subject: [PATCH] Sync from development - prepare for v0.2.0.4 --- CHANGELOG.md | 22 + jackify/__init__.py | 2 +- jackify/backend/core/modlist_operations.py | 9 +- jackify/backend/handlers/config_handler.py | 14 + .../handlers/install_wabbajack_handler.py | 3 +- jackify/backend/handlers/logging_handler.py | 3 +- .../backend/handlers/modlist_install_cli.py | 9 +- .../backend/handlers/oauth_token_handler.py | 8 + .../backend/handlers/ttw_installer_handler.py | 3 +- jackify/backend/handlers/wabbajack_handler.py | 3 +- .../services/automated_prefix_service.py | 6 +- .../services/modlist_gallery_service.py | 41 +- jackify/backend/services/modlist_service.py | 9 - .../native_steam_operations_service.py | 2 +- .../protontricks_detection_service.py | 59 ++- .../backend/services/steam_restart_service.py | 52 +- jackify/backend/services/update_service.py | 12 +- jackify/engine/Wabbajack.CLI.Builder.dll | Bin 13824 -> 19456 bytes jackify/engine/Wabbajack.Common.dll | Bin 207872 -> 207872 bytes jackify/engine/Wabbajack.Compiler.dll | Bin 160256 -> 160256 bytes jackify/engine/Wabbajack.Compression.BSA.dll | Bin 94720 -> 94720 bytes jackify/engine/Wabbajack.Compression.Zip.dll | Bin 18944 -> 18944 bytes jackify/engine/Wabbajack.Configuration.dll | Bin 4096 -> 4096 bytes jackify/engine/Wabbajack.DTOs.dll | Bin 142336 -> 142336 bytes .../engine/Wabbajack.Downloaders.Bethesda.dll | Bin 18432 -> 18432 bytes .../Wabbajack.Downloaders.Dispatcher.dll | Bin 29184 -> 29184 bytes .../engine/Wabbajack.Downloaders.GameFile.dll | Bin 16384 -> 16384 bytes .../Wabbajack.Downloaders.GoogleDrive.dll | Bin 17920 -> 17920 bytes jackify/engine/Wabbajack.Downloaders.Http.dll | Bin 15872 -> 15872 bytes ...ajack.Downloaders.IPS4OAuth2Downloader.dll | Bin 35328 -> 35328 bytes .../Wabbajack.Downloaders.Interfaces.dll | Bin 7168 -> 7168 bytes .../engine/Wabbajack.Downloaders.Manual.dll | Bin 9216 -> 9216 bytes .../Wabbajack.Downloaders.MediaFire.dll | Bin 15872 -> 15872 bytes jackify/engine/Wabbajack.Downloaders.Mega.dll | Bin 16384 -> 16384 bytes .../engine/Wabbajack.Downloaders.ModDB.dll | Bin 19456 -> 19456 bytes .../engine/Wabbajack.Downloaders.Nexus.dll | Bin 23552 -> 23552 bytes ...abbajack.Downloaders.VerificationCache.dll | Bin 13824 -> 13824 bytes .../Wabbajack.Downloaders.WabbajackCDN.dll | Bin 24576 -> 24576 bytes jackify/engine/Wabbajack.FileExtractor.dll | Bin 77312 -> 77312 bytes jackify/engine/Wabbajack.Hashing.PHash.dll | Bin 49664 -> 49664 bytes jackify/engine/Wabbajack.Hashing.xxHash64.dll | Bin 21504 -> 21504 bytes jackify/engine/Wabbajack.IO.Async.dll | Bin 15360 -> 15360 bytes jackify/engine/Wabbajack.Installer.dll | Bin 136704 -> 136704 bytes .../Wabbajack.Networking.BethesdaNet.dll | Bin 39936 -> 39936 bytes .../engine/Wabbajack.Networking.Discord.dll | Bin 14336 -> 14336 bytes .../engine/Wabbajack.Networking.GitHub.dll | Bin 21504 -> 21504 bytes .../Wabbajack.Networking.Http.Interfaces.dll | Bin 5632 -> 5632 bytes jackify/engine/Wabbajack.Networking.Http.dll | Bin 29696 -> 29696 bytes .../engine/Wabbajack.Networking.NexusApi.dll | Bin 62976 -> 62976 bytes ...abbajack.Networking.WabbajackClientApi.dll | Bin 77824 -> 77824 bytes jackify/engine/Wabbajack.Paths.IO.dll | Bin 34304 -> 34304 bytes jackify/engine/Wabbajack.Paths.dll | Bin 17408 -> 17408 bytes jackify/engine/Wabbajack.RateLimiter.dll | Bin 24064 -> 24064 bytes jackify/engine/Wabbajack.Server.Lib.dll | Bin 6656 -> 6656 bytes .../Wabbajack.Services.OSIntegrated.dll | Bin 54784 -> 54784 bytes jackify/engine/Wabbajack.VFS.Interfaces.dll | Bin 5120 -> 5120 bytes jackify/engine/Wabbajack.VFS.dll | Bin 64512 -> 64512 bytes jackify/engine/jackify-engine.deps.json | 450 +++++++++--------- jackify/engine/jackify-engine.dll | Bin 220160 -> 220160 bytes jackify/frontends/gui/main.py | 72 ++- .../frontends/gui/screens/install_modlist.py | 6 - .../frontends/gui/screens/modlist_gallery.py | 219 ++++++--- 62 files changed, 575 insertions(+), 429 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f970c4..52e9ab9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,27 @@ # Jackify Changelog +## v0.2.0.4 - Bugfixes & Improvements +**Release Date:** 2025-12-23 + +### Engine Updates +- **jackify-engine 0.4.3**: Fixed case sensitivity issues, archive extraction crashes, and improved error messages + +### Bug Fixes +- Fixed modlist gallery metadata showing outdated versions (now always fetches fresh data) +- Fixed hardcoded ~/Jackify paths preventing custom data directory settings +- Fixed update check blocking GUI startup +- Improved Steam restart reliability (3-minute timeout, better error handling) +- Fixed Protontricks Flatpak installation on Steam Deck + +### Backend Changes +- GPU texture conversion now always enabled (config setting deprecated) + +### UI Improvements +- Redesigned modlist detail view to show more of hero image +- Improved gallery loading with animated feedback and faster initial load + +--- + ## v0.2.0.3 - Engine Bugfix & Settings Cleanup **Release Date:** 2025-12-21 diff --git a/jackify/__init__.py b/jackify/__init__.py index 73b8b82..03efe2a 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.2.0.3" +__version__ = "0.2.0.4" diff --git a/jackify/backend/core/modlist_operations.py b/jackify/backend/core/modlist_operations.py index 4537407..e5aac72 100644 --- a/jackify/backend/core/modlist_operations.py +++ b/jackify/backend/core/modlist_operations.py @@ -680,7 +680,8 @@ class ModlistInstallCLI: start_time = time.time() # --- BEGIN: TEE LOGGING SETUP & LOG ROTATION --- - log_dir = Path.home() / "Jackify" / "logs" + from jackify.shared.paths import get_jackify_logs_dir + log_dir = get_jackify_logs_dir() log_dir.mkdir(parents=True, exist_ok=True) workflow_log_path = log_dir / "Modlist_Install_workflow.log" # Log rotation: keep last 3 logs, 1MB each (adjust as needed) @@ -775,12 +776,6 @@ class ModlistInstallCLI: cmd.append('--debug') self.logger.info("Adding --debug flag to jackify-engine") - # Check GPU setting and add --no-gpu flag if disabled - gpu_enabled = config_handler.get('enable_gpu_texture_conversion', True) - if not gpu_enabled: - cmd.append('--no-gpu') - self.logger.info("GPU texture conversion disabled - adding --no-gpu flag to jackify-engine") - # Store original environment values to restore later original_env_values = { 'NEXUS_API_KEY': os.environ.get('NEXUS_API_KEY'), diff --git a/jackify/backend/handlers/config_handler.py b/jackify/backend/handlers/config_handler.py index 88909bd..376642a 100644 --- a/jackify/backend/handlers/config_handler.py +++ b/jackify/backend/handlers/config_handler.py @@ -389,6 +389,14 @@ class ConfigHandler: """ try: from Crypto.Cipher import AES + + # Check if MODE_GCM is available (pycryptodome has it, old pycrypto doesn't) + if not hasattr(AES, 'MODE_GCM'): + # Fallback to base64 decode if old pycrypto is installed + try: + return base64.b64decode(encrypted_key.encode('utf-8')).decode('utf-8') + except: + return None # Derive 32-byte AES key key = base64.urlsafe_b64decode(self._get_encryption_key()) @@ -411,6 +419,12 @@ class ConfigHandler: return base64.b64decode(encrypted_key.encode('utf-8')).decode('utf-8') except: return None + except AttributeError: + # Old pycrypto doesn't have MODE_GCM, fallback to base64 + try: + return base64.b64decode(encrypted_key.encode('utf-8')).decode('utf-8') + except: + return None except Exception as e: # Might be old base64-only format, try decoding try: diff --git a/jackify/backend/handlers/install_wabbajack_handler.py b/jackify/backend/handlers/install_wabbajack_handler.py index 3de05fb..721b592 100644 --- a/jackify/backend/handlers/install_wabbajack_handler.py +++ b/jackify/backend/handlers/install_wabbajack_handler.py @@ -1196,7 +1196,8 @@ class InstallWabbajackHandler: """Displays the final success message and next steps.""" # Basic log file path (assuming standard location) # TODO: Get log file path more reliably if needed - log_path = Path.home() / "Jackify" / "logs" / "jackify-cli.log" + from jackify.shared.paths import get_jackify_logs_dir + log_path = get_jackify_logs_dir() / "jackify-cli.log" print("\n───────────────────────────────────────────────────────────────────") print(f"{COLOR_INFO}Wabbajack Installation Completed Successfully!{COLOR_RESET}") diff --git a/jackify/backend/handlers/logging_handler.py b/jackify/backend/handlers/logging_handler.py index 5210e9e..8a26a86 100644 --- a/jackify/backend/handlers/logging_handler.py +++ b/jackify/backend/handlers/logging_handler.py @@ -21,7 +21,8 @@ class LoggingHandler: logger = LoggingHandler().setup_logger('install_wabbajack', 'jackify-install-wabbajack.log') """ def __init__(self): - self.log_dir = Path.home() / "Jackify" / "logs" + from jackify.shared.paths import get_jackify_logs_dir + self.log_dir = get_jackify_logs_dir() self.ensure_log_directory() def ensure_log_directory(self) -> None: diff --git a/jackify/backend/handlers/modlist_install_cli.py b/jackify/backend/handlers/modlist_install_cli.py index 753273f..eff1ecb 100644 --- a/jackify/backend/handlers/modlist_install_cli.py +++ b/jackify/backend/handlers/modlist_install_cli.py @@ -558,7 +558,8 @@ class ModlistInstallCLI: start_time = time.time() # --- BEGIN: TEE LOGGING SETUP & LOG ROTATION --- - log_dir = Path.home() / "Jackify" / "logs" + from jackify.shared.paths import get_jackify_logs_dir + log_dir = get_jackify_logs_dir() log_dir.mkdir(parents=True, exist_ok=True) workflow_log_path = log_dir / "Modlist_Install_workflow.log" # Log rotation: keep last 3 logs, 1MB each (adjust as needed) @@ -644,12 +645,6 @@ class ModlistInstallCLI: cmd.append('--debug') self.logger.info("Debug mode enabled in config - passing --debug flag to jackify-engine") - # Check GPU setting and add --no-gpu flag if disabled - gpu_enabled = config_handler.get('enable_gpu_texture_conversion', True) - if not gpu_enabled: - cmd.append('--no-gpu') - self.logger.info("GPU texture conversion disabled - passing --no-gpu flag to jackify-engine") - # Determine if this is a local .wabbajack file or an online modlist modlist_value = self.context.get('modlist_value') machineid = self.context.get('machineid') diff --git a/jackify/backend/handlers/oauth_token_handler.py b/jackify/backend/handlers/oauth_token_handler.py index 9d27d31..3408170 100644 --- a/jackify/backend/handlers/oauth_token_handler.py +++ b/jackify/backend/handlers/oauth_token_handler.py @@ -142,6 +142,11 @@ class OAuthTokenHandler: """ try: from Crypto.Cipher import AES + + # Check if MODE_GCM is available (pycryptodome has it, old pycrypto doesn't) + if not hasattr(AES, 'MODE_GCM'): + logger.error("pycryptodome required for token decryption (pycrypto doesn't support MODE_GCM)") + return None # Derive 32-byte AES key from encryption_key key = base64.urlsafe_b64decode(self._encryption_key) @@ -163,6 +168,9 @@ class OAuthTokenHandler: except ImportError: logger.error("pycryptodome package not available for token decryption") return None + except AttributeError: + logger.error("pycryptodome required for token decryption (pycrypto doesn't support MODE_GCM)") + return None except Exception as e: logger.error(f"Failed to decrypt data: {e}") return None diff --git a/jackify/backend/handlers/ttw_installer_handler.py b/jackify/backend/handlers/ttw_installer_handler.py index fe2fefe..ea0eef9 100644 --- a/jackify/backend/handlers/ttw_installer_handler.py +++ b/jackify/backend/handlers/ttw_installer_handler.py @@ -23,7 +23,8 @@ from .subprocess_utils import get_clean_subprocess_env logger = logging.getLogger(__name__) # Define default TTW_Linux_Installer paths -JACKIFY_BASE_DIR = Path.home() / "Jackify" +from jackify.shared.paths import get_jackify_data_dir +JACKIFY_BASE_DIR = get_jackify_data_dir() DEFAULT_TTW_INSTALLER_DIR = JACKIFY_BASE_DIR / "TTW_Linux_Installer" TTW_INSTALLER_EXECUTABLE_NAME = "ttw_linux_gui" # Same executable, runs in CLI mode with args diff --git a/jackify/backend/handlers/wabbajack_handler.py b/jackify/backend/handlers/wabbajack_handler.py index 69a180a..a7ac8db 100644 --- a/jackify/backend/handlers/wabbajack_handler.py +++ b/jackify/backend/handlers/wabbajack_handler.py @@ -1196,7 +1196,8 @@ class InstallWabbajackHandler: """Displays the final success message and next steps.""" # Basic log file path (assuming standard location) # TODO: Get log file path more reliably if needed - log_path = Path.home() / "Jackify" / "logs" / "jackify-cli.log" + from jackify.shared.paths import get_jackify_logs_dir + log_path = get_jackify_logs_dir() / "jackify-cli.log" print("\n───────────────────────────────────────────────────────────────────") print(f"{COLOR_INFO}Wabbajack Installation Completed Successfully!{COLOR_RESET}") diff --git a/jackify/backend/services/automated_prefix_service.py b/jackify/backend/services/automated_prefix_service.py index d2b0071..3ce5708 100644 --- a/jackify/backend/services/automated_prefix_service.py +++ b/jackify/backend/services/automated_prefix_service.py @@ -30,7 +30,8 @@ class AutomatedPrefixService: """ def __init__(self, system_info=None): - self.scripts_dir = Path.home() / "Jackify/scripts" + from jackify.shared.paths import get_jackify_data_dir + self.scripts_dir = get_jackify_data_dir() / "scripts" self.scripts_dir.mkdir(parents=True, exist_ok=True) self.system_info = system_info # Use shared timing for consistency across services @@ -749,7 +750,8 @@ echo Creating Proton prefix... timeout /t 3 /nobreak >nul echo Prefix creation complete. """ - batch_path = Path.home() / "Jackify/temp_prefix_creation.bat" + from jackify.shared.paths import get_jackify_data_dir + batch_path = get_jackify_data_dir() / "temp_prefix_creation.bat" batch_path.parent.mkdir(parents=True, exist_ok=True) with open(batch_path, 'w') as f: diff --git a/jackify/backend/services/modlist_gallery_service.py b/jackify/backend/services/modlist_gallery_service.py index 8e5336f..9106000 100644 --- a/jackify/backend/services/modlist_gallery_service.py +++ b/jackify/backend/services/modlist_gallery_service.py @@ -25,7 +25,8 @@ from jackify.shared.paths import get_jackify_data_dir class ModlistGalleryService: """Service for fetching and caching modlist metadata from jackify-engine""" - CACHE_VALIDITY_DAYS = 7 # Refresh cache after 7 days + # REMOVED: CACHE_VALIDITY_DAYS - metadata is now always fetched fresh from engine + # Images are still cached indefinitely (managed separately) # CRITICAL: Thread lock to prevent concurrent engine calls that could cause recursive spawning _engine_call_lock = threading.Lock() @@ -59,31 +60,20 @@ class ModlistGalleryService: """ Fetch modlist metadata from jackify-engine. + NOTE: Metadata is ALWAYS fetched fresh from the engine to ensure up-to-date + version numbers and sizes for frequently-updated modlists. Only images are cached. + Args: include_validation: Include validation status (slower) include_search_index: Include mod search index (slower) sort_by: Sort order (title, size, date) - force_refresh: Force refresh even if cache is valid + force_refresh: Deprecated parameter (kept for API compatibility) Returns: ModlistMetadataResponse or None if fetch fails """ - # Check cache first unless force refresh - # If include_search_index is True, check if cache has mods before using it - if not force_refresh: - cached = self._load_from_cache() - if cached and self._is_cache_valid(): - # If we need search index, check if cached data has mods - if include_search_index: - # Check if at least one modlist has mods (indicates cache was built with search index) - has_mods = any(hasattr(m, 'mods') and m.mods for m in cached.modlists) - if has_mods: - return cached # Cache has mods, use it - # Cache doesn't have mods, need to fetch fresh - else: - return cached # Don't need search index, use cache - - # Fetch fresh data from jackify-engine + # Always fetch fresh data from jackify-engine + # The engine itself is fast (~1-2 seconds) and always gets latest metadata try: metadata = self._fetch_from_engine( include_validation=include_validation, @@ -91,6 +81,7 @@ class ModlistGalleryService: sort_by=sort_by ) + # Still save to cache as a fallback for offline scenarios if metadata: self._save_to_cache(metadata) @@ -98,7 +89,8 @@ class ModlistGalleryService: except Exception as e: print(f"Error fetching modlist metadata: {e}") - # Fall back to cache if available + print("Falling back to cached metadata (may be outdated)") + # Fall back to cache if network/engine fails return self._load_from_cache() def _fetch_from_engine( @@ -253,17 +245,6 @@ class ModlistGalleryService: return result - def _is_cache_valid(self) -> bool: - """Check if cache is still valid based on age""" - if not self.METADATA_CACHE_FILE.exists(): - return False - - # Check file modification time - mtime = datetime.fromtimestamp(self.METADATA_CACHE_FILE.stat().st_mtime) - age = datetime.now() - mtime - - return age < timedelta(days=self.CACHE_VALIDITY_DAYS) - def download_images( self, game_filter: Optional[str] = None, diff --git a/jackify/backend/services/modlist_service.py b/jackify/backend/services/modlist_service.py index 7c2035c..d03c7cc 100644 --- a/jackify/backend/services/modlist_service.py +++ b/jackify/backend/services/modlist_service.py @@ -288,15 +288,6 @@ class ModlistService: # Build command (copied from working code) cmd = [engine_path, 'install', '--show-file-progress'] - # Check GPU setting - from jackify.backend.handlers.config_handler import ConfigHandler - config_handler = ConfigHandler() - gpu_enabled = config_handler.get('enable_gpu_texture_conversion', True) - logger.info(f"GPU texture conversion setting: {gpu_enabled}") - if not gpu_enabled: - cmd.append('--no-gpu') - logger.info("Added --no-gpu flag to jackify-engine command") - modlist_value = context.get('modlist_value') if modlist_value and modlist_value.endswith('.wabbajack') and os.path.isfile(modlist_value): cmd += ['-w', modlist_value] diff --git a/jackify/backend/services/native_steam_operations_service.py b/jackify/backend/services/native_steam_operations_service.py index a14b6b9..78af8bc 100644 --- a/jackify/backend/services/native_steam_operations_service.py +++ b/jackify/backend/services/native_steam_operations_service.py @@ -177,7 +177,7 @@ class NativeSteamOperationsService: # Also check additional Steam libraries via libraryfolders.vdf try: - from jackify.shared.paths import PathHandler + from jackify.backend.handlers.path_handler import PathHandler all_steam_libs = PathHandler.get_all_steam_library_paths() for lib_path in all_steam_libs: diff --git a/jackify/backend/services/protontricks_detection_service.py b/jackify/backend/services/protontricks_detection_service.py index 95e6471..8acbb2c 100644 --- a/jackify/backend/services/protontricks_detection_service.py +++ b/jackify/backend/services/protontricks_detection_service.py @@ -132,14 +132,31 @@ class ProtontricksDetectionService: logger.error(error_msg) return False, error_msg - # Install command - install_cmd = ["flatpak", "install", "-u", "-y", "--noninteractive", "flathub", "com.github.Matoking.protontricks"] + # Install command - use --user flag for user-level installation (works on Steam Deck) + # This avoids requiring system-wide installation permissions + install_cmd = ["flatpak", "install", "--user", "-y", "--noninteractive", "flathub", "com.github.Matoking.protontricks"] # Use clean environment env = handler._get_clean_subprocess_env() - # Run installation - process = subprocess.run(install_cmd, check=True, text=True, env=env, capture_output=True) + # Log the command for debugging + logger.debug(f"Running flatpak install command: {' '.join(install_cmd)}") + + # Run installation with timeout (5 minutes should be plenty) + process = subprocess.run( + install_cmd, + check=True, + text=True, + env=env, + capture_output=True, + timeout=300 # 5 minute timeout + ) + + # Log stdout/stderr for debugging (even on success, might contain useful info) + if process.stdout: + logger.debug(f"Flatpak install stdout: {process.stdout}") + if process.stderr: + logger.debug(f"Flatpak install stderr: {process.stderr}") # Clear cache to force re-detection self._cached_detection_valid = False @@ -152,13 +169,41 @@ class ProtontricksDetectionService: error_msg = "Flatpak command not found. Please install Flatpak first." logger.error(error_msg) return False, error_msg - except subprocess.CalledProcessError as e: - error_msg = f"Flatpak installation failed: {e}" + except subprocess.TimeoutExpired: + error_msg = "Flatpak installation timed out after 5 minutes. Please check your network connection and try again." logger.error(error_msg) return False, error_msg + except subprocess.CalledProcessError as e: + # Include stderr in error message for better debugging + stderr_msg = e.stderr.strip() if e.stderr else "No error details available" + stdout_msg = e.stdout.strip() if e.stdout else "" + + # Try to extract meaningful error from stderr + if stderr_msg: + # Common errors: permission denied, network issues, etc. + if "permission" in stderr_msg.lower() or "denied" in stderr_msg.lower(): + error_msg = f"Permission denied. Try running: flatpak install --user flathub com.github.Matoking.protontricks\n\nDetails: {stderr_msg}" + elif "network" in stderr_msg.lower() or "connection" in stderr_msg.lower(): + error_msg = f"Network error during installation. Check your internet connection.\n\nDetails: {stderr_msg}" + elif "already installed" in stderr_msg.lower(): + # This might actually be success - clear cache and re-detect + logger.info("Protontricks appears to already be installed (according to flatpak output)") + self._cached_detection_valid = False + return True, "Protontricks is already installed." + else: + error_msg = f"Flatpak installation failed:\n\n{stderr_msg}" + if stdout_msg: + error_msg += f"\n\nOutput: {stdout_msg}" + else: + error_msg = f"Flatpak installation failed with return code {e.returncode}." + if stdout_msg: + error_msg += f"\n\nOutput: {stdout_msg}" + + logger.error(f"Flatpak installation error: {error_msg}") + return False, error_msg except Exception as e: error_msg = f"Unexpected error during Flatpak installation: {e}" - logger.error(error_msg) + logger.error(error_msg, exc_info=True) return False, error_msg def get_installation_guidance(self) -> str: diff --git a/jackify/backend/services/steam_restart_service.py b/jackify/backend/services/steam_restart_service.py index 00b7ea7..129417b 100644 --- a/jackify/backend/services/steam_restart_service.py +++ b/jackify/backend/services/steam_restart_service.py @@ -402,14 +402,15 @@ def robust_steam_restart(progress_callback: Optional[Callable[[str], None]] = No if final_check.returncode != 0: logger.info("Steam processes successfully force terminated.") else: - report("Failed to terminate Steam processes.") - return False + # Steam might still be running, but proceed anyway - wait phase will verify + logger.warning("Steam processes may still be running after termination attempts. Proceeding to start phase...") + report("Steam shutdown incomplete, but proceeding...") else: logger.info("Steam processes successfully terminated.") except Exception as e: - logger.error(f"Error during Steam shutdown: {e}") - report("Failed to shut down Steam.") - return False + # Don't fail completely on shutdown errors - proceed to start phase + logger.warning(f"Error during Steam shutdown: {e}. Proceeding to start phase anyway...") + report("Steam shutdown had issues, but proceeding...") report("Steam closed successfully.") @@ -427,42 +428,56 @@ def robust_steam_restart(progress_callback: Optional[Callable[[str], None]] = No return False else: # All other distros: Use start_steam() which now uses -foreground to ensure GUI opens - if not start_steam( + steam_started = start_steam( is_steamdeck_flag=_is_steam_deck, is_flatpak_flag=_is_flatpak, env_override=start_env, strategy=strategy, - ): - report("Failed to start Steam.") - return False + ) + # Even if start_steam() returns False, Steam might still be starting + # Give it a chance by proceeding to wait phase + if not steam_started: + logger.warning("start_steam() returned False, but proceeding to wait phase in case Steam is starting anyway") + report("Steam start command issued, waiting for process...") # Wait for Steam to fully initialize # CRITICAL: Use steamwebhelper (actual Steam process), not "steam" (matches steam-powerbuttond, etc.) report("Waiting for Steam to fully start") - logger.info("Waiting up to 2 minutes for Steam to fully initialize...") - max_startup_wait = 120 + logger.info("Waiting up to 3 minutes (180 seconds) for Steam to fully initialize...") + max_startup_wait = 180 # Increased from 120 to 180 seconds (3 minutes) for slower systems elapsed_wait = 0 initial_wait_done = False + last_status_log = 0 # Track when we last logged status while elapsed_wait < max_startup_wait: try: + # Log status every 30 seconds so user knows we're still waiting + if elapsed_wait - last_status_log >= 30: + remaining = max_startup_wait - elapsed_wait + logger.info(f"Still waiting for Steam... ({elapsed_wait}s elapsed, {remaining}s remaining)") + if progress_callback: + progress_callback(f"Waiting for Steam... ({elapsed_wait}s / {max_startup_wait}s)") + last_status_log = elapsed_wait + # Use steamwebhelper for detection (matches shutdown logic) result = subprocess.run(['pgrep', '-f', 'steamwebhelper'], capture_output=True, timeout=10, env=start_env) if result.returncode == 0: if not initial_wait_done: - logger.info("Steam process detected. Waiting additional time for full initialization...") + logger.info(f"Steam process detected at {elapsed_wait}s. Waiting additional time for full initialization...") initial_wait_done = True time.sleep(5) elapsed_wait += 5 - if initial_wait_done and elapsed_wait >= 15: + # Require at least 20 seconds of stable detection (increased from 15) + if initial_wait_done and elapsed_wait >= 20: final_check = subprocess.run(['pgrep', '-f', 'steamwebhelper'], capture_output=True, timeout=10, env=start_env) if final_check.returncode == 0: report("Steam started successfully.") - logger.info("Steam confirmed running after wait.") + logger.info(f"Steam confirmed running after {elapsed_wait}s wait.") return True else: - logger.warning("Steam process disappeared during final initialization wait.") - break + logger.warning("Steam process disappeared during final initialization wait, continuing to wait...") + # Don't break - continue waiting in case Steam is still starting + initial_wait_done = False # Reset to allow re-detection else: logger.debug(f"Steam process not yet detected. Waiting... ({elapsed_wait + 5}s)") time.sleep(5) @@ -472,6 +487,7 @@ def robust_steam_restart(progress_callback: Optional[Callable[[str], None]] = No time.sleep(5) elapsed_wait += 5 - report("Steam did not start within timeout.") - logger.error("Steam failed to start/initialize within the allowed time.") + # Only reach here if we've waited the full duration + report(f"Steam did not start within {max_startup_wait}s timeout.") + logger.error(f"Steam failed to start/initialize within the allowed time ({elapsed_wait}s elapsed).") return False \ No newline at end of file diff --git a/jackify/backend/services/update_service.py b/jackify/backend/services/update_service.py index 4ac86c4..255ce9c 100644 --- a/jackify/backend/services/update_service.py +++ b/jackify/backend/services/update_service.py @@ -271,9 +271,9 @@ class UpdateService: total_size = int(response.headers.get('content-length', 0)) downloaded_size = 0 - # Create update directory in user's home directory - home_dir = Path.home() - update_dir = home_dir / "Jackify" / "updates" + # Create update directory in user's data directory + from jackify.shared.paths import get_jackify_data_dir + update_dir = get_jackify_data_dir() / "updates" update_dir.mkdir(parents=True, exist_ok=True) temp_file = update_dir / f"Jackify-{update_info.version}.AppImage" @@ -345,9 +345,9 @@ class UpdateService: Path to helper script, or None if creation failed """ try: - # Create update directory in user's home directory - home_dir = Path.home() - update_dir = home_dir / "Jackify" / "updates" + # Create update directory in user's data directory + from jackify.shared.paths import get_jackify_data_dir + update_dir = get_jackify_data_dir() / "updates" update_dir.mkdir(parents=True, exist_ok=True) helper_script = update_dir / "update_helper.sh" diff --git a/jackify/engine/Wabbajack.CLI.Builder.dll b/jackify/engine/Wabbajack.CLI.Builder.dll index dd0bdaded19a70fd11fe4bb2af0ab03695c09b50..1f69963af4d62d483be690276e570b43203c0b8b 100644 GIT binary patch literal 19456 zcmeHv3vgWJmFBtkcHh3;lG^H)CBN+!e$bZWmTdWfi7}QX84LWDZQjVO)|K42)mOgV zGFFU9D?AD^kcq>DWPqud5GGG3*pNv`paL1fgqctiY9Y#b*oA)(TS4 zEvy_LwoRuXwY3txzJ^E>?gt0!^<+I!#3(^QpoyriO{a^Y24pNw$p*+y)vUSdfSgEcp~()5QRq6NtWWV%sk#1ESa z^I^Y1pTu}j8`GVoU`BLyAmS_sSYf)5jj$JymTuiSAHU02nJe6#;8ulPWT_IastLI# zeWIn_bdlsXDYlis+d8wrT~SNKsL)R}HD=o6;f2ps+In_z7> z-G7`QK4iMT1|jy;gq1yw?jn;$!c*;WZE%w*G3pFZD7`&YVbtIIM2qSE6gcFcw#s%q z1RSiUaG7-h@Fr8{CrGn7A22@!tOK5&NwZKf2lZ+K%dy{hCugS2s(U{)PKs^8tO>^h zi|2%ssA`dT!2Kn+SS|SUh$9!##%agT3hB5cR_=LrYR|b9b5+?>A4TqIRiTzmA!jXA zCD#G0YE=qr$_memOqk*|tkhyU>(R|Yx<{Jo+Jf|Lc*&pHvcL-$fWQ2Q7h)hucyDol zYZf+gKS&@)0hlC*Xp$T$KS_>WljK{b$Tv-qcTACAG)Z3KLQwb+EKv*kle%#dQykC( zCxiSGJZbVzG;k6NH=ycQQ|gCUrMftUN^pYPH=XBoWCr&Xh9F?D4q*ra1}hVWAb&IW zE{@Xj26r>!ttU}5Oj%l=TRz9#Dy-N9%d4Ak8x`5lMbq7mW)6JkQgQtRuA9yr^lS;6 zOA-z|hjhtF50S~kyyXz8o4#ZVS?Gyjrg*%(8aw2k?#9-pl-Bx@Yu&++(djv>h-d6 zowpjZ=@pfnx*A6%vC^$FS?N>htaM09H+{^TxsCG* zFN!e#1W!izM>caT2IPNvF>pJ{Y(bFf+(Q_GfWdu*AqeLmrg2g(Q`I?gc_qwwpIqLj zro_+X#S^OF=6UEoV`eU^5Tq)5te6m}%*4(&H8Zi>sLWJ*_(6YsIECCqnVpoIYTh|E zGh`b_XcGGp)*%6NwgbIFl90pg$f&#)Y;Qc|V4u-)l^W5Iaa9ibxyEA}CuHPFxyFMU zGE080l}9zy*gY8A&o%5(X44}RxyJfbZYUipK~z7Ly%HR2~JfvzP_sB#weCY4Ei4 za+j+N2UiV|B&tfYgS?3(FK71T@vfN8x{_JGl?#vqPiRCa4KX8$=`bV7tI)vRjyzVo zh;ub)BZ-MNt3qn0uaY()C2_8Syt<*L`2tH6)CFPzOAaf^YawZAPztYr!dO7|CFy6W ze&IP+)L1Qn@Z2@e7|wNQ@ALsQY`zzhr#g=CiV_=?{1D`I!RBjlPEP}U2-Bt2nbLOt z+(JJ~L-CONIdn^AK*d9GGdTc+PteVfd~uRwuq;Uq0hghm`!MiT_q|dd3y!w%2v*E$ z3B@cY3;xdn&h_}wPoUBTl1G5H3@BqC@r_mFk3kycb9mW%CH{kmm!khv$%<<3c>W|w z^DNb?3o@}XFvrQE1?Q`V^w8;H$~I`oHe4j&Jk^hR#f&2V<1iD}@NT%gvCe4VM`D{(;4B`!RF?8FnN?wGZqNNOv!cp zxM1O@z|E1Erc(rlrla^-^@CU9W$g)^hySbv%x!bSJ)u`ossNfmUEX*5Zrv31QFG)+^hLvxb#3T9dB0mB9>lq zpr;G6+d*H0MZR=v&cPCq5A4-0pYyq@FdO%Kt(_1Nm`4ZjMcyz;9r$8ODL&sk3NI2nK+lS^dE^^J}9s^_$}bK3I7+>Z$%bU!T7yp(#J&4TNcOa>n7Xz zxtf=&8tAtc!#6_=Zws*HhIsl*v@xj=V(3Col%9l-=h0}@XG1IKpX~gYO@#WzJJ_;3UKE88I5KHUcsFE4?nzNUQZEU`B5NyhLD0;DQSL zn|f70~IcLXFeA`h5X|>KeJ_DeHyESD<0ONBv9WnLvon z6Y3u8(a28&7VT6Ny{ex9l~XP0fbr`TyrO4x2HT^~?w&mXg z)iifG%bpECT)hP`Tqe|QR&Ri*7LU3na5hj&>paS~Hfb@s$fHKBP%uW@JgO10IPLPN zHpt?1xkr5uvO4PXs2@RAM~6LXZq=;dZ2F8)&(YV+P1QU_NGV1lHt<}4O%js&5+Esl3)PPXR_Z9T0P|Eie6kW--uOxQgZp#lE|sx`krjj|WrKrzps739hCag?dxV z8Fl(vdfcP_S(~q~qyHwC$xba!w)nb>uRZl+E2h2RF7=TQei zT|g}!71q8G+(>;Mb+h(J@IpG~QQIK<5Z&QXSx}qkexZ1dOsxFRd$Jw$jo?Le<0>BS zjCLKW^~IEERn+=Gi+7)RWtexSWmODqR27|G0LP#Pi(B$Xqr0(DX~NM7_fvp>8&V(g zRk%)L;25+PFhDPfRP~w;?~oCHhFw{wGk_XZ;{QX&`nI%L7}6mG*O zs4im}v`_T>?~nBhgU5PF#!@;tK01xVM}xMB@Au-2p^<{GXpC3GlJ|ogG2QF64fV~0 z9}%2j?m|s}g2MD*L_>w<8Jq_gp_PEOv|8XsfgJ*S1nw1hrNA_xMp?jmIx6r>fb#`U z&||<&dJ%919hEFNs`2bUpfP_;qzmc(z;0SdSJYeySQi?kGvI%imQg13=hQ+u+o;uI?9jSkuJ97+^_@Y0E>k z6w|8oTGF&=;0`UUy(*gDthrkolS=ub_7k0J4g1w17A}K5pMh1((X^29*U%X<9@b*| zZ_URA|E9K|-l_gOz&7JW?Ha+a5&RVWpyn0uuc~?ze1-!e9T4e&a2oYR#=BZh;<;uXpI|dyoEe^G5wI zq~-kr_v+cm7xgRkdDwN{ru(c%^cS_C)_hHWo4%?&p=b47HQ&+G`bQ#vs~^?3);zCY ziCDb=$g$e1eNi}7`akJ=5nGm;;Z536{kodHT3Y)!cGX5ruX#lu*RQr-*BiA>hK7Bd zvyMmqFy{I)>$9|jJ`!m%K2KX~dWT+bAB$TBXn!vd^~~L6Iw|HiUOXX)=&zkp5LH0*U=vU7tjJTMN4TJU^Ddtw$n+# z57EZ}JL%JamkGZYupK(B&=&eL`UkU=g3ENyNU>8%1!3;zLu4+`fQ!T(m^vogxFGRhg@{8xdm3;%WDYdBLIAwyuJz?A|! z1eOHyx!VXmE$|tQWA&QgXEcu08R3x5Z8V*w3Bel$-y--Sfob8C1Rodt0sRDB9C}(f z&j{x=!O6%VdStN9gy0Fmw+K8WoI`?_1TP7GTHwo-V`KI#PL(_S! z*q?d-D}R3FT>(wj_cG2Q{NZT%pOwO!-0t^6^8EOpcZ}ajyUAmmk|vY;eqB`a44C-x z99W)Zv*^oOE2VKVdX2t9$Mk=om-YAPpSb;|&YY9CH!q`2n>Y6LtzO;NMw^E0V&6gA z?JsQZ_gR}WeSKZo!bmPX)|pEe3TwDsDw8QXt~6T5^uEzN6H_Itp?egHXxC^im+sHm zhgMThcYbu(b|`xbtpvCTyV%Et6=_rilGtAs+7Y{oFTSv3G0o#S38=Z35 z;#j9MJd$>W#mzcyNN0;e?Mb@@yTcvANckcUy3bAz?9S)Lc4P}e7wuti^TqZxQ=9?K z&J6VRt)XqB`OKj;)R7T;)=*bgu(Ue{ZVyyU?c7cW)45T5|L6!@ZI>qPQ}nO(!o99< z-4qGv%^PXcPG?{=XK$w7u>wX&_3Xx5BRnl#2oqXld_kv^%h_U2A$5tJx7}<84jprj z*b+AC7#JW4$bozzJ!tRFbFgxD(H;q*kuocYwl zOUfy5;uG?%m>sq`yxUEsIz$Q~bI8+g*hGC`QLEt|82 z@Va>em9{B>yb=CSc>+}bmlO(>$QuM=t8BC$7H+33PFycmI$KO z1e^UZ91fdg;Zmn-V@y)MyIpq99%8z0z|IM>Y4hs7J{g)Yk0Wr? zl_h{i106-&EA1ae^Deu8bZCe}T;}Y%Axz;{(8>b8 z2Ww8}$I1dP?}T5>_Gh_tm!+IFk{)!7i>IHRUZ<273gkcSchr4wzX=rb+U8tMapG{B zo#|rcFj{u>V}&3K_N0r4>42Lhr~i5^zygx3cXV(to52b#b|1GhqZ~=(bmoYxWZp-} zakE2NOvA*oKp_D&vHFHbuyf{%DAjh3E>HLOr>{r4tmt`ixV2-=y=xK6hKvf$rlOTXq8#w>-AAG7EL5OxTlJR_r69zmi50(M~LH z+vVk@qTxpjX>)WUr8@EhotPV!dc1m%v_F=`X@mOkHmbsAoM>NLWiEO-z_rPrTA!s7 z*rzsaF81~Ht7FYfim+sn(^zQ7kZqW+ty#>zOHT1HAa6omg`{JcRv*Xo%dkGJb~v1M zYcJByA`%xKWBXMOmyIt2o zY`qN-M@Md3L18=ek5(1j6dy84oi#-bXK%p|R97}Vly?fnY^ET>i6etVrc6!V{8EUH zY#81=7rbo(apvW$RCVOC=>l*R`a;F}=2}v~8EkD|UlH#q`-bd4jO}Uw3Q}A=3hH#K z6v;|2AQAG#bT*G3>M-Famq(UYIH{?rU`)@U3Zd+l`|JXyuY&72%u|rUF=#ZG#zGo# zaYW%F@+Vi86iR3jry)+f317>lL^2}XunAJ1 zBdfg=bF6grj%G42l8|p+PD3ut=!k zhl|ux;K;Zr<~^u+qR3M&ctr#;ox}Z3PKA^6)5B1DJj}nK@JRNvbJTUQHbqw_su;{E zrrjd#bdK4(FaZ-e!Z>uIR7&Md=e+Gx&g`n@`yBWo?{;JE?Bk;OGJA|qjQ$d-xDe?^ zEXK$g>GLm%81KpB>?#*XaG$s9-EQ~rND&X%FYCp-Ku2j5Fps-DHYETZ;6+>`X7O79 zCl5{n&neU3X6Uf+hrqG%Y}y8&`{IGWMys|##=LFl;o>Q>>$N`y-gfjap!Xipa~Ln5 z97H?3(}G?+7WW%~)}oA-19Wg_DFNF?00+GmRC>1qq>ILEc2+R%<~K;t4Kk22yC@O| zE%TzBUE(1QqkjUu*+F&{H#o&C91FlpLAGBUW%t?fF|YLi`edXf_fLy73mpZ~#X%W> zh8v)R9Y}*}0gprK;vc(G^xX9AWjRX|#6dz8hbFdx$7Rjsu&0Ph-hY7|LTIPb!!rlecl!MTx<{Pi z#8yj(^NMe#svP45!&$=FH9a#ppd7|wIM1Q+Got|K`L--BoA7T?LXkrTV*WQAKoHX4 za0c^aWa-0bhpX0#9y5fru~N=?&tnguXC65Br_z}{KwK~B%1Kz+h%@Ni4wpkVGJ_@_ zFzwAC56e#SB2K_Do{bM+w&i$WYmx$! z#(OrZ>SC(fdE{fDa~N{~maC|8N~v`+gC|RNLRMLf($K6bHWwFK#WCg@oRBrsL96I0 za8@CS+5k6FGqvLB>b3Y?N!lg*&}VXyovhDKq^T7nDRar=$QnDbQVQzi%$b}6{u1$X zh_j`f5B;!%+fQFYYRC8^3#RnY%^C`!IgP z3<;|?*B(f_(cVQ*yrx&vjnc~Ly$gM*^i;X>shT}~Ma)zar@PGAbMBIM?roTZV57Ek zKB^+9syDAOUcagys0!fkB|J`%%be#-FRD!^oVoej3zWAcl^8Y_3-8VL49PZi5ppo5 zF5UN8jxebM#&jaWII?8^PZvuBVYj~+)MCAku7S;JX(p&&3dRRo&anih#7_%Zm`j<% zWrRx$hh8lwU#qlPP}wUXC6cF4WX3Ae@Inb-BbO(?Fm*u>+s=E7&!bQRkNvRW&e{*l zdAtM)4d_Nb|MWs5r)g2joZrW5v4mUfka-@ONiG_A%mRGijiYRdr(qsz=#WCHc4|&O z8GX?~^m7r-8Eu}m3(3mI+e$r^o=)hQrB)*QAd#8FQDdFz7@dP{!e5P7$xzHW|9mgG z^r@5Rpu|9(_f(0$5buAoCc?`xfagcBwt1!wOKkZZ%6$i>j6=))d7XT);_Xw#UbL;` zR=Xu)Lo!=R8~e*!2yaHVvU*VjGsK?wCmUCAmf~#@yzRdan;Wmf8^kZ(UN|e*u6{`P zR6GbvStDbrAaS<+aZk!CQk~JKj-DcI*5q-tmEEu-jZt|6SCR28;Pdzp{&8Lo5k41h z_*uu2yWZ$}@GF;}UT|&A$0?A|G|NaJr-8&`Ohy?5nO+nXD0sZb1Sej4B2*tQ{YZ;f zd%v}TkQPgr^|8_mnuVuwg49u{4ew#pF2=jqkXi|^#d3fPs;JfOXTOUgs@t$}s;|t? z+G0&0hTEwxro~!aWO@$>LZroeVx=>)rl<=i3=9tw^t$@^wb~RZTLa@lfoL>-GJX1_+oBjG0AP6dqC;(fu=c)WBg`j^IIr8_JP zXj#C++7Zd;u$D{ z8}ETO9ojgqXoUFV<$;9|E{u0T=!G(bn3V2{+fWUIPDK(3<*=ZJDH=-;XfQ!XRz#s6 zlm&gIPie8zUo!FlBhf?va$(~^b~M5hKfXwdk2hA;YT9B@m?H{Cn1gqBxQFT+AJ>#p zeRe2}_>Ni8cRKLl(cr-EgfoOz4M zh-hG-(M5O)tm7xXcT*^Wx96?+!g%Q^r0FwL=Xc6UN2jCagJ+$-1MY|ztE9}RnSGa4{?@uGn&J}~Dt2r(scNJy|%YJ?bjK+|>8 z0xDGk{Vc;=6crlA2Q@BDVr&Q2fq1wmIxAtskE+tZ3%wS94l#pa4WyyNGJc^Z3 zQ>*FPM7ao(#;JuQjrU+VS_y4Q=_TMHh46A-#LG;_qLal_9k1rqjZ6X$C~@IJO{%Vj zj@o!nZIu7QV}h3G}K4way1N|zp{|&l27&4OhK`j%iaf)C{IQbbt(zRf&fsWdGC5G=9km=>kXH?>5*Md ze&X&n?&LZ*6g0Hcd0yxujpn9yb?@(VTpPF1TfApc7ahAG)dmI8*%J=|y*DKI8JopD z5@<1BqxzYj8Bn!GAv|@nahDl?VF98owKmnBYQuFP-d`4?xvg#Wy4CG%>(=zQr`NZy z->{+0UYFk3mRX+n4v-!~w8qe8|l@TV`6KKpvRdcPfdqV-dkf41u@OFr?;*^Q_6 zu#VQl&amA&U}ug19BbtVaoNGKRdyav@a$Inzb{&63{5MZ2ex`s(3-c4n6H@|rbY(( zsdszFnsw{R8^7p}4 zo)FM*A%k4A8tD`SVoZ*Jn8|148jTZu~LiU2w*j<{Yg2bb-*I zm2V69mq>g`&WBb0ER$aQA?15m1v$+62Y6{fj|P4TrJ5X+`67f5nbS3WU)r?6Z}o32 zj;e?OE^ko0adD|`2J#qV@JL;Ju>Q;CP}>i9J-&Pp<~Kig!0#0P2P}LWobQ(Mr3~Q` zfpz&~^F_$?3!@LJvki5!tW#Yy`S%D}R~zhDi*Gx=?9YChkM>TZ=p358oxrNwh`~Cyw?IE{SlrPL|g-GWOI98$TXw318xTkeHTjQ1i40 zV>+;lZ_^ely!s$I3GXYm!k@{yrbf0L*JjC>)wGUQBg$>~U4z_dhd1j1*V6|4YlC0w zz}W~a10G{(NHh4|0NHx{uETE|`eK*B=K`764!PjtCpmoK#E&}8ZHJm)>3lA5Nd1lA Qq4sk%`2YO>mm~1M03#ta1^@s6 literal 13824 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 diff --git a/jackify/engine/Wabbajack.Compiler.dll b/jackify/engine/Wabbajack.Compiler.dll index 9c8057441fa8bc40ba66b900e84b7eaf1aa66dfa..d59b807dffcd30ea5b7b8c5a98eda14394069e31 100644 GIT binary patch delta 500 zcmZWkO(;ZB6h7y^r%W_WVWBKoOcqnGJ9EAFdLfb$HcDn87SHs;L?}v$#!lub%;F{s zJ5jdNOEI#u$HKE;g z#9)O2`Lo{m%~ViiO%lr*3syz*ua!^3e$#*LzVcU-6G^P4C2QYsNCW@unvE2Kxg^&A zv7Q12%W`ppW}La0M8$KKaBMD+J8rJJex7REIa-;_ZMK|v*%J2D-XJs%AP_XJ)oCKc zc~}n%u15xhZ3x3Oxue?_j~TXQiDGg{o}u8y<&7+kvv~E7s^(S7&-m|N)2Z`Qx3BbJ*ISLVSM({Metkpn%?vWA>dSD(CG?dIz(WAm<n#A2jTOw*rtF{+9(F~GoVTL_DVfoZy8H>0pELQ2#D zE(KG~u-6__?6V|XYakGc9 zn~`aIYA<7coxpM%@7D`-Z9O*@+N+1KZ#ugD2{+SsPa7jW0|o|TMg~0tJrg}6ZA+un zG?SzhGt<<>G!tVB3$rv+%VZ0~WQ&yKM04Y0OS44F)Z~;@GX@40MuzE)VNAx`y~3EH zgjsy{tF4^AB9kdVKuI|M#j|C{+;^rZsO2%G?w`()#gwH06;-!{ih|TL`q~Ou-%;85 zXu}+q?US;Y{xEVfF|dHW!L)sP4pS^6qtW)SxlG=S!A1<)43-Q=45Xo3@Hp| z45mO{BA9Q?U;%_?K(;ARRWgum2qsg2WFmt(gE5e{1j;7@$y6Xa1xT7rpPbKR%4)=* S$6zr1V-}OicE$px|BL_x({MZh diff --git a/jackify/engine/Wabbajack.Compression.BSA.dll b/jackify/engine/Wabbajack.Compression.BSA.dll index d9e243792f5271ae05af3ee106b78d0f69bd1c44..b0059cde3147e99cdd56a9ebc49285904c2e7fe3 100644 GIT binary patch delta 366 zcmZp8!rJhJbwUS=TgK7;jXg&H7?~&U|7WSm%m4#RwIM7P287sT6}Z@B#s9*L%$rUB zE87ZKr`>Wr`^n$6yHRt+tOvU}wrAKd>MEKW>lrXG7&9{H8R(hl8EYFD7@8Uy8<-j; z87G<IA7@2QB6U+FAgT>>+$?4P2q%j5vxa6)n z_pd45xkonA(u=X4Yr0N4V}$}#wAlwL3REw?knLif#hdq973{}6w;xGoJjKMx%)kP6 zGxPT8S&XHOjKK>=t=Cq9IhpdY=w`bTe>MEKW=@~FE7&9{H8R(hl8EIP@rKXuArI?wf zCZ?GfTUeN-nOY`W7$#e!Bqy31CtI2&TBataq?&C%6U+FAgQez=)Zyu8(ij5-=AGHi z;ufdu)qUyVak*OuOQ!3jGgc@-MXS7_q9FBl>^D_bFJt1Gxbcen_9N+xr90g`6Z&*w6lvKleyF&JzY%wzn(IMIM(J7WRk Ge?|b@TVwtJ diff --git a/jackify/engine/Wabbajack.Compression.Zip.dll b/jackify/engine/Wabbajack.Compression.Zip.dll index 32a86e4c2d3e4aa76c8ed8cd4c4622c1919f6b4f..280bbd8b5fc198cff3ae968b2ce2a44a5d67b544 100644 GIT binary patch delta 309 zcmZpe!q_l{aY6^nzqu!_Z|pJBVq~7YUrUvddGk}P_q+l(|LvG{M)99h-`R+mqRhp- zo1a*$XEit0GhkpaW@OMa&@<69);2IOG&M9fFf~dtPBb$%GdDL#HBGcMNH$BfOf*im zG*30NG)*-yGT(g4?i3@7Y1{n0lV>;t2%Mf$(QBb_%xUo|t)F%`XZ)S~!=XX}D)>nm zDhN`oC$i<<*4Tp2YSrC2lP5U-<78%FVE_T<&C{Jq85xZ?^Sb0S1{*VIGZ+A&A%iIp z8v|LU3`Pt|Kvp7y8IUw%Fb6^dhE$+#teE42Ac)lJ}^!+;MmOQ@t+X@*>Ya@ delta 309 zcmZpe!q_l{aY6^nlQQnN8+(kj7?~#T*HUF<+Wb`OJ+FYY;BOmw;UbSs3!EqV8mpYy z{KR5CtGSV$0Rw|EBZHoSo{653wxv;Onn_ZMnQ3Zbnu)Q6g;|=ZWwM1~vPDXAqPcOh zrCFk7YH~`d+2&Jrrx;l*J*Ia|p5YK6us0+j8!D90g`5u b&%2tk8ZqcG7;F}F`@lHSfMYYG$A3luA1__J diff --git a/jackify/engine/Wabbajack.Configuration.dll b/jackify/engine/Wabbajack.Configuration.dll index f4107287a8dbf7666fe78e377a2833158a0f49a9..1b033800dbf17478c290b2b58e0787a826892eb8 100644 GIT binary patch delta 289 zcmZorXi%8Y!Qy=E|H_R$cFc^-n@=+{G6`@BJ^0Hk?&`RXC+zQa!>MtT@38K7G}bd< zU@&H6&@<38(KFUIFfcSVG&V3bN-|C~Gd43fH%K*2v@}RIOSDWhPPQ~pHM2BLH83(~ z00XuB$RCsMumuQwR?cBoa+&3^NHcQIv5A)iC)==>C_n`}9zg{`s-N)%XN!ln`TcHC zTCn*7`#vU4W(F3pSC_7?OajL`=@~FE z7&9{H8R(hl8EIP@rKXuArI?wfCZ?GfTUeN-nOY`W7$#e!Bqy31CtI2&TBataq?$2+ zL0{bQrIYWl1qe)i?RjCmCMLzw3(GBnK9UiL7Tyn!H6N1A&tR=A&DV{!HmHa$V&wC zjTtO}&FlR6Z(w0E^L?D?8WTya0v&p-8O@YRLCj{7W1Rw<=6Uf<5~mGPsDxv`!B1A{RmgPwt&iJq~xfq|i^p|OFfQIc_@nX#F< zxk0LFqNPEyS)yg4ak8a(s+px}s)3RDb^{@%2?8uX$_}c9Pt}=>8H0@(v>6P5(2&6th>d|PQwAf3Bp@r1!3;>6F_;6P0YfTKJdwc?$Vvu^ kfmn$^*<>JX4wN$k(xyP#0I1h|dbTE$DbRw2noRLb0I-=?XaE2J delta 332 zcmZp;!O?JoV?qbZ7XH6i8hf_(Fs|`pWSVa2&FCx2!~g^4!4MV;1Jm}|-i)y;1vE~q zZjlS~bnf)ncCTsk)C=1=zA}E4F*njPU|=w2WY9CvGto2BwlqpjGf7G@GfhoQGcmTX zFiSJFOtvsgwn#}%G&fGRG)uHhO-@NQ+ioDlG(mu6PodGW=|_~90tA%4?moSBE3f+` z8>v+WGfzyIuA&s-|yWrp)w$iIa(e1?*g=?bFqm zd>I*ywm(&8GG+`mV$f!=WH4e#Wk_Q%VMt;~VK8Gb1@aQXd}9U+AT$HAO@XSCfowxC mnF1se8O#}sfwU!1J`qT!0@*1*(rkLRCX*@9f`yt)@k{{!eO@*I diff --git a/jackify/engine/Wabbajack.Downloaders.Bethesda.dll b/jackify/engine/Wabbajack.Downloaders.Bethesda.dll index 3a7339d7a9ef021a912606e52e74f83eab5ddb26..278ec4f89fe869ec37e0f9f2df252cd4ca1fac20 100644 GIT binary patch delta 420 zcmZpez}PT>aY6@6v3KhEjXhfw7?~$KDyrHtGr)i@H-yE)fDn7ig(=p{3m2QbSkYP( zp*oQfA@f<$8fb&Ql0TCCdxZSv$x4j^0>96kl5wJ$v6;EKL8@t@r9rY;qGh6SvZZ;dnWbr}fsy%S zM{CQ?Ce}|FS@?K5pH7ys4G@^4IH9h~Sk!&KrAp@6L#)MaY6@+PjKJSjXhfw7?~zJDyrHtF~EQ>H-yE)fDn7ig(=p{3m2QbSkYP( zp*oQfA@f<$8fb&Ql0TCCdxZSv$x4j^0-VPC+b&O;=)CiZhM0Tc>+;Pi#tYcZjr0r{ z7>pSi^bGV&^o+DEjZ)K0l2Xh}Qxnrnj4dq8(o8LrEew+_Qj!zRjgu|S5-n4cQ&P<) zJ6ckQWSat2B?H-pU@`?rCNh{a7z1fbpnM{bOa-!2fTY=EZWmLa J@s2M0nE@%YUx@$! diff --git a/jackify/engine/Wabbajack.Downloaders.Dispatcher.dll b/jackify/engine/Wabbajack.Downloaders.Dispatcher.dll index 6a62f0212d6561574afa4f90a456c3c65c022fee..22a29d23c8975d2f7c76d0726fa75685b7102ca0 100644 GIT binary patch delta 623 zcmZp8!r1VHaY6^nTA_lE8+*1{F)~kfwpJBoW`Kc9{16rk1M}osYjs8#uw z6E4AHZmeg(z+lYCpl6_GqGzmaU|?u!Xl!6=lw_P}W^86|Zjfr4XlamamS~x1oNQ^H zYG!GgYG7o(d3CZOGs~pQpL!=hNDC0)J$~Lo>%k%a=BBO`U2|r|$qwml3NS%ts31sn zV!=w|fd7u}3knx3-25zk1rsMT1H@a*o2O?@Wn?toteZ2RG1!b%7 delta 623 zcmZp8!r1VHaY6^nukC+kZtU4+#mF?-*;-YUi2(*K@k3ZF3`~=2t<`N2GI3mR8JJ>* zt*n@0a|GaGlOI|OYa&e8fG`_wLK_c4wXBUll1qX(5i*T7!bmQe$^w^xn{$&5Q%ne< zZt_JG6BKwcRqG*C!%ek8m^xX}Rv5{3`w*eA+0&L;N`PNu{~gA?-yD`MPv!q1bER){ zOt=J(xsjd$1A{RmgPwt&iJpS3vV~!?MM`p_xpA_k zS)yfXa!RV%=GDoD%q*v;GC6L&k}e9y?d`MuOeApx#ft1g?%Z5|U7KI~gnQ!KZ6gYH!}b7MUN1_onB20a5k6Fp;X z0|P@-Lt_I|qa@=*Gh;JzbAwdVL`#EYvqZ~8<77+oR5MG{R0AXP$$L!puxQ?TGG}s) zX@J1?@6W#nuPF1~>SX+BS3vV~!?MM`p_xpA_kS)yfXa!RV%msi(B$mcM^ zkl5wJ$v6;EKL8@t@r9rY;qGh6SvZZ;dnWbr}fsy%U zb*q_-EERw3A51=C9UyR1_7jWjJ7bsC)^^UJYi@W=R=ieku{p%Z$;`k4b|~}a>2^~Y8I3pVI?QJbHfGRfFaSbB22&t52C_^Uj2M!D ztV9MgAZf;64ul2_sX*~W21_6-87Kx~B?4uWfwVbL&J0MK0%-%FUh~QFPNuBJ40;R( Pn+07~Fm7ga`OgRdoY`Cb delta 405 zcmZqZVQlDOoY29-7h`aLV-JTMBh%!ma+ab@3@}j31!1u;Fin0eXAP8>msi(B$mcM^ z#tJnSED^Idk(4BMWwOBRvBK z24hABJp(-xJtJ*PqtrB$q!csL)WkFsV+#wjG*ioD3&UiKl;lKn<77*-M9b9VlvK0L z>Q*xuS)wia?oK{p9UyS%+o3;_6L)*-+st79 zb^dC9S1;Op#^w+sCldn;*r80Dr`t_sWHj2W>oA`&*oZ-!!IHs0)x-4-FA#R!*& zX<=yR!W8R5GACaKXwK&8GLHNL(@%@Fbg9|dh~-Pq5h<>lrXG7&9{H z8R(hl8EYFD7@8Uy8<-j;87G<IA7@0FL*fBC# zPTpv&wOQP_k&(s0UViQ5IVJ%DE4KQr+Q^jPGx3RVwD+q7xygS_suZAtcP>H&L8@~j zAHLh+UVHiAw=EYYPc!|&$;`k4_6yLtX0?or#+yYgsu_cg8MGM;fY6Y^6o`$1EK>#} zh9n>>k--c|nlYFIp#eiGP&|>r639vhih)>(K-pv7ZJj2KcG(ilt_k{D7L z%ot38yhJeHn85-F&46rEpsHjb+Yn5q0LerKa|UA|Z3&c51d^#hb_$R*oBY_)l+}nq RkHKKGpv@D;&5Snx83DrKU^V~% diff --git a/jackify/engine/Wabbajack.Downloaders.IPS4OAuth2Downloader.dll b/jackify/engine/Wabbajack.Downloaders.IPS4OAuth2Downloader.dll index c8cd94bc419afdecc146d606c4f805d7aa527ded..f262753d716bf3491907babf04d777c68086a6ab 100644 GIT binary patch delta 405 zcmZpe!qhN@X+j4}_!sL%8+$Z-8JQ<<^;H#RW`F@FVF-(bfqC+KUu&Sewx7BtLjEoz zTpp%{K~V%QHo4lb8_6^dLAcCjX@64%0q*~iZ*!JAx~+-S|I%=;y>D|u{$y@*V?6@~ z24hABJp(-xJ!5SH14C0oV*^v8B;!OgV>5GegH+Q*OM_&yM9W0uWJ~i@GfUG{10(az zTGe$dEc2|6&YZlaK0x5{yWMGTa#=h(t}=d@`}w^3WS)ju3Q$4L6Hq~r>Q##mdAOvw zJ^sOO?6CP*!wE)CW(F3pLzy>EZ`#YqXuLVJAT(e|1&Sv!SOQteKrs+25h$Asq|JeHWpSi^bGV&^o+DEjZ)K0l2Xh}Qxnrnj4dq8(o8LrEew+_Qj!zRjgu|S5-n4cQ&P<~ zYgN~=u*iNCUNd=3eSiSNd=vMRH{JXfxNX1n%xKT9$vh3S6rh6f2~a_h>Wj!SbWpNVqrjtr9dT-#hxJ4ZJx;4%_^`d zlkvi@fEK@IjSF{o{7CWMti%6_G00fYfPul7kwMQu&qU8y+rYrk)X><#)F{a~(ahM) z+}t45G||!^*(}jA(Ky-CJk`w7G}XY!oPmLrk%5VUfrF8O8|E?~y;;ZZ_cxabNicCTGeBI< zym`9FR7OVQ&AMXq8H0@(v>6P5(2&6th>d|PQwAf3Bp@r1!3;>6F_;6P0YfTKJdwc? x$Vvu^fmn$^*<>JX4wN$k(xyP#0I1h|vb=;Tt1*KfgTZD&sTGWy8KwR+0stTkSP%dJ delta 382 zcmZp$Xt0>j!SXW9df&#LMI4Mwll3`OZJ8Kgz>WpNVqrjtr9dT-#hxJ4ZJx;4%_=Zo zy)Yz?JH}`3pH1TT&$V3Jti%6_F~~^IfPul7kwMQu&qU8i+tMgC%_J$s%rrGI&BWNk z!Ys|yGTFi~*&-!5(cC!M(k#(3H8~~KjDdlb5##_4Mh0$}%Yd|eQPkAQ1%d$rnsy1j z!K!^;4Kp-uWc^lOI{AWNn*vmj3nmCuE&5FOqSNLX6&I%;Iia<=Oh|%>lZgT1a;DAG zMW!+`8g14Uo6i_*#GuV!$za5g%8!XuqRb31;KdGMu`n=CF636%M98QjWMGOJrbAgE z)d#tSfu>#Mwq|7BEXX6xC9war#-|IiV$KW98^cd)2S3?tBld>L+*r?mfx(!OLC-+X zM9)~;z`)Sd(AdD#D9Jd{%-GD_+#uC7(b6EL+(^%Wfx(!OLC-+X zM9)au(kL~}Bq_zrG&M2J#Mr{ZEX~w1*}^c{A|*M|+&J0NEYUJGIVII>a=c_Fi&KL5 z(U@)E&(V+IQ# vGy}3tfvS>$Y(p@a0wfa|%o&V3YVDy=v-GH diff --git a/jackify/engine/Wabbajack.Downloaders.MediaFire.dll b/jackify/engine/Wabbajack.Downloaders.MediaFire.dll index 15df2744955300314bd1d165a107ae6210b952dc..f023068e0cba23f594284dbf00f76b2704268877 100644 GIT binary patch delta 388 zcmZpuX{edd!ID}e`+H;0J!wYf$=NchKysptuqZPF46Npauvi$FC-0Q8)|?jXl|TrX_jc2nw*kq#=sE3$lyKs zpSsxO0%P^fZpKE8EK&OcH%_iG2@r^nedJ(U@z{5Ij8DV5_@gnC@0heGKm~2Xp@Klw zhDPcK&oqn1tNu`)GjVf+DIX&z69Ws_@l2bioAoj>8f{j!n9LY##GuV!$za5g%88$TpWPHT>(!slB6=6t%#HO77#NHh z8T1VFO!SPk4GauT4UG*lJ^6=Ol>$_d9VQ4=?Q5idJniV)mU$OW zh0dHj$@~u|GXo3Q&CHvpThuZ#8gCZ0s%8u}X3%CZ0763sQy?}5vP>C_7?OajL+6E2(NSF~Gn|9tewt0U?%z5SzSK$r?$uHB=F@ zYDZqU36lkt{edzUT(XwkWOk69k4xsjd$1A{Rm zgPwt&iJpS3vV~!?MM`p_xpA_kS)yfXa!RV%=0t~L zMwaYLYYtAn;TRwwJJ-bA^IN{(u4?WIMrZa1lP#QT6rh4}RZu~Y>aZv7{;zgd>g2zT z;Mjb{X%{0W69Ws_(M+4CyVNo=8f_MJt7Z%~V$f!=WH4e#Wk_Q%VMt;~VK8Gb1@aQX zd}9U+AT$HAO@XSCfowxCnF1se8O#}sfwU!1J`qT!0@*1*(rof$cT-j)20aFY&4OM} L7&kL|{bvLKoYY%P diff --git a/jackify/engine/Wabbajack.Downloaders.Nexus.dll b/jackify/engine/Wabbajack.Downloaders.Nexus.dll index 9b08620feac8c89075bf419d977c575be6e51f3f..eef2cd0a6d0f34cc2d9229964a93507bd99b1fb2 100644 GIT binary patch delta 431 zcmZqJ!Pu~aaY6?RyOQwHjXe!&jLegH)m1f_8DPMJ7s6s;K!`CS#3o0ps{{2ktE(gF z>EprF^9@PQWw4&#>gA#cGx@pUdN#Lf{FE0EUYT4xCH$uUBvH|YjAg$$H&5`G#$j%( zXTZQ<%*dc;pl70ItZiUmXliI|U}}_PoM>ijW^QhfYMN+ikZhJ{nP{ABX`X6kX_{(a zWWG5zP@R#*U$b%XKfwR~4m9nl4a@Zy3rd-N0;ltz?K{X0c!3)2jfEii)R> znnWz?n(}#-+UAyEE=Epf1{SbGnKw@lsbyp|-Ygnc%@}OVpv_$lOKnhvKlk! RF&JzXjC#VjnK9}=BLFg=T*UwY delta 431 zcmZqJ!Pu~aaY6@6NY0+M8+#hm7?~#Xs;g=;F~EQaFNDRyfDmIuh)s@GR|o28R#!*T z)5n9U=Npoq%V0gf)yqW@X7Y2x^=xj}_$e>&qe(iIMU~Ze|6SWxbwMc`Hc#-F#$j%x zXTZQ<%*dc;pl70Iq-|-Gnr4!eVrH6}m}X*ZVPTeLYME?dm~4@foM>*GY-yHgnVOuE zYPLBxP@R$Ghg_dVje` z!^ePy#m-`LOE4EBCldn;*r80Dr-#%sG8%0b4Xb7hHe%3buw*b|NM%T4FkwhyNMSHz zFa`1w!F*!|3m`NDvQ2@il7VbPFqr}*6B*1IjDfTzP(BezrUKb1K+z-!Ll#6an;72Ffm5v$&bZUMVT33Ae9}$VqsvOEGzEM$hXTZQ<%*dc;pl70ItZiUmXliI|U}}_PoM>ijW^Qhf zYMN+ikZhJ{nP{ABX`X6kX_{(aWWJe8D~ge&=H0y8lQXmf1UCEcDeGVS+hy$(DV19G zM>i&)(4L|I6=czc3IbI}KisbTVz1}|^+%1yk2lxo2r+RoGq8Z2%e;BI-daXR#}h9n>>k--c|nlYFIp#eiGP&|>r639vhih)>(K-pv< bZ4Q((1Jb5I+5o86d~&prDbVz-!4h42s&!*em>46|?V@3u&13eQxBW+8g)HIW%6f@J*#55CQ3k$O} zQ_Exv!(@w;uRU^)S2^rzo_()O zxF&w`3GFEgP{9Sipn^cv330;DG&h!(8*5$+xW2hYM~I1&iGc;|T&B&__0}>n8f|tp zILsJq#GuV!$za5g%8HhH#@HIiwH2-7ydHc}K7SheZN_7*iguO8XL%%l1O$2ZG(OyIIL)-zyW zFlJ=XGte{9GuAdRFf=tZHZV0xGEOuzHZwOjNHtBgG)Oi}v`jQkwlq&QvouXLFfwOg zaA9PypFAN%Z1evhL1q@FgPqSNi-ZLTFt`TPe9C>~)TFh3=~X2P~GH2|pczNdfoBYjk;RTGG%nU4G&oFPE9x<1Z(Rj0I)N00HV+L&o10XbHFa=^` zAj_1&h#?8cN@Oqtl4cC%Kxn{_3KUOdumrM_fnp$5B2YFNNSg!Y%z(5hkTwA7HJ@x8 WW6EmGpvPdaSuk!2<7URV|BL{@QD92| delta 415 zcmZoTz}RqraY6@+*`4NX8+%L)8JQ*@G*lI3Vt|1RZU~Eofobw@Lv^6MvXQ?gLjD{N zTpp%{Ass3W(lgu08p$+8glU^!8!3tk$ZXC3b-6moZMRX_jC)NkC7WeDCUDsr=@~FE z7&9{H8R(hl8EIP@rKXuArI?wfCZ?GfTUeN-nOY`W7$#e!Bqy31CtI2&TBataq?$1> zxG*x58-mFcAeqQu&R`6rErIfhKr$7`P63i;lZ|6c VS&bO<7z{QG#!X?|%oz8d5dernV~hX* diff --git a/jackify/engine/Wabbajack.FileExtractor.dll b/jackify/engine/Wabbajack.FileExtractor.dll index 70ba2997c4417d797a3656a74c27b900dcd2f816..65021c3410321a32ed5f9fe014fe3cb59bdb43fd 100644 GIT binary patch delta 388 zcmZp;!_shvWkLsw5KnpQ#vZ0kjLeh!H>rv;Gr+(R2?&dYfqC-DP3l1Tk6==AGl=cD zSr|!e5f4Ib=4NX~=FLktYX=By+ZejZ=VX%ey8FH7-c7xJX!`?Z#%?KFV?6@~24hAB zJp(-xJ!5SH14C0oV*^v8B;!OgV>5GegH+Q*OM_&yM9W0uWJ~i@GfUG{10!<=1{X#K z`{@gm7$vqJkYmhXV`*t<-7{TBl`%lzR#%4Cf1$_joBqz(Ftwc7W_ph*V~GM(bbAq0 z6r}$7i+@umY|nno^G#vjb_F%YJSI+N1{SdMnYT~ZV9aG?G~Ujt#hA<(Y|Nm|U;u=M z45mPA3}l%y7%?OPS&0m0K+=rC90&~O-$j<}-4hmxG*x< zPhX(KD6##39AgF>%kz@=zozS`G6o2&Ff6M7SG3$~f8mk|IZF(br}wBbmMB0)tNuep zLF$u?-8b#EXR^~h$$NRbf*NBU6DJb`3)uNg+ox+V<}xxGZD-YDOlAx=V$f!=WH4e# zWk_Q%VMt;~VK8Gb1@aQXd}9U+AT$HAO@XSCfowxCnF1se8O#}sfwU!1J`qT!0@*1* S(ro%}ZAMd|1s}B;`I!I`nrAWq diff --git a/jackify/engine/Wabbajack.Hashing.PHash.dll b/jackify/engine/Wabbajack.Hashing.PHash.dll index cc29618ca48c70db6e3bc5ef9973cffbd47f6856..5e1a50b06cfed78a7de1b354f981d8d72d5eca51 100644 GIT binary patch delta 330 zcmZo@VQy$)p3uSKF=_sbjXi03jLefi=c$S^Gr$0&2!zGLz&u$uKN%=LG2fbzdGp!) zY-52#E;AM0TsZ5}`8a{qb8?&iW{<_|1k8=~3>X-U85#5p^i1@OwG9jmO%06=OpTI^ z6U~gx%*_o_O%p8*lFbq=6OEHC%~Q=RO;Zhw%s1<9F=l01vbz5JHqgVb< zc9>ZCc?~1$3N{Er1wpD4T9)?d8Lv8XUikma%@MoIm^hgkSip{E-aLIz zE+eDyX4ZYljKRhX+6)FjXvkm+#Ku6DDT5J15|EY1Uu9I1~*< zG!tVB3$rv+%VZ0~WQ&yKM04Y0OS44F)Z~;@v(36&j9FQV>LnIVPT3hCaOKLndAW;{ zy!#d}vb%g>wdUj_J4+Oxf~U_x1%av+mmLq=SvS=>;l-1)vo=TUGGpRoVqgI~nrZX& zJ-LjGMw?mpB{K#aF=#VbG8i$WGNdt>FeEXgFqkoz0(psGzA=LZ5Sjtmra)E6K(-;6 lOaYRK4CV~RK-v;0p9myVf$S6@X*PNHep8_FANTV!0RYy&V+#NP diff --git a/jackify/engine/Wabbajack.Hashing.xxHash64.dll b/jackify/engine/Wabbajack.Hashing.xxHash64.dll index 7fd9465b7a434aa482cc9035182498ddbb993549..3459fdacc98c6b23e8b09b4ad3fb89b2a9dbb352 100644 GIT binary patch delta 337 zcmZoz!Pu~ZaY6@6@-eQijXg*77?~#r>Z^(}Gr+(eE(nW-fq8SczN~;i#ZR`*dY$S1 zeaDyP{cpbcaPtoPmmIdndIk&(#*7Sl26`rX#@Yr3hNgzb2BtupXs+Rhy>9$e-#MgA=;QyC4XL+kIaxycpfSt^|dAd(6 zBct(VQNL=&U}FYt1_K~8WH1F{V<5|v!H6LV$Vy}|1CnM8=0Iq`kO~w}WUvIXl7V6% rRw7U~8AzK0<;;MzDUdb*>NTJI*x!`Zm_d)hV6$M*6UNPqLH`*6um@WZ delta 337 zcmZoz!Pu~ZaY6^n_HMiL8+(rEF)~dK)K?W{Vt|1?To4uu1JmYieOUp4d5Md1<4RvU zY_#{hondNfxp{~EOAcEjJp%>?V@3u&13eQxBW+8g)HIW%6f@J*#55CQ3k$O}Q_Exv z!(@w;?2q+_Fh@-3wsMV6bhyj2)EnHX5WPG;IX-KUn3 z(P*=%Uo~T}5rZ~^C4&(|DnlBB2}2S?3WFJgDUg>4<{L9u0HGO>Z3FG3YTEY!(c9!nm0+=szO>3tL`X diff --git a/jackify/engine/Wabbajack.IO.Async.dll b/jackify/engine/Wabbajack.IO.Async.dll index 62c8a034eb6ee7061522472f865e3f40b7af44df..a11ff86044b8584b4641633fd609e9f98460b06d 100644 GIT binary patch delta 298 zcmZpuXsDRb!Sesiy~i7SrYJBnZ&p-v<`j6XTkTVOd7bOD0Dc?8_noUYmuN+^nj7mG zFfbT1GUyrTndlj78yFax8X6mz8YLMgni-p!n;WE>CR!RKnCQa&Efv!4@Ln3oG!}C*K&Eg^<-D*Ou8>1JvqQQO93i)4JHUw9ltTjed5_q zvQ}q<_O0K1%XkYDCo=;J0|+p0o^BS)$Y{LztGPF0urY%+g8>j4GMECfF_2}-V8oCF zWF<0~0ZB6kb09QeNCk=~GFSpx$v`m>D-kH045ZD0a%Mo<6i6EY^_owfY-!4B%%I0$ OFxk<>WHY1He?|a8@KSgH delta 298 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 PitTnm5vKQy6Ajn_0LlZr delta 34130 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 A4*&oF diff --git a/jackify/engine/Wabbajack.Networking.Discord.dll b/jackify/engine/Wabbajack.Networking.Discord.dll index f6618838caada99882dab79dda8538710443ddbc..94b72ae440ee5b7996cbf1b34766cec29d16f098 100644 GIT binary patch delta 316 zcmZoDXegM_!E#-#@A<}_2nj~!$xkJGfuymdIwSMuXh}<6f!|LurZNdCyKXpSi^bGV&^o+F)3=B;TjSWnVl8h70jLpo=4N^@LEe(>*5-k&r zlP%3t%`8n*4UEhg7#tZHY$hwJh)$lMt+u&Dn}vyG{~!BTlWTMX1Qb_Kc(warlUK_e zy&b!Eoi3exN4HJ^D){RNR1l~-Ym!quPxqTWP3=2`r8ZaTi7;_8Gq5m#0Q2VQ2Ca;Y z#+zl0+8Kk58MGM;fY6Y^6o`$1EK>#}h9n>>k--c|nlYFIp#eiGP&|>r639vhih)>( fK-pvCYPB3+>WoaAqa`hQ1-7|toceTMiEDS&)7N}I z7T?;uL@k2F)=1BQfx(!OLC-+XM9)au(kL~}Bq_zrG&M2J#Mr{ZEX~w1*}^c{A|*M| z+&J0NEYUJGIVIJMfx(fH!Dh0eis#pWtK5hhM11{MYoVA?$0pp}u) zXtS(QJ7cgBgEoUDgAqe2LmGn#LlQ#@gBgP!z delta 317 zcmZoz!Pu~ZaY6^nx~uFDH})*jU}T!CuW1P+Gc~OlnKm!g{464HQ>r2U)*GY-yHgnVOuEYBu?`o9X6Cx7W-p7rnDKPR{TO5D5N$=8vM3n%}nUq{jN)o);#c z@TyUO3cmXW6$Gj_{8CpV+dudB@`#kU;>{`ET8x}b3@i*Fz_fX~Pc0*((PmMp3=o#pl=oxDp7#Nxw8XK4zB^f808Jn4#8>E^h zS{fvqC0ZsLCtI4Qnpv8r8W@>R{=uceBG2``d$J05fI!INm(h88c z;qFs_3L2e<3W8K?$V6RRWPWm`QbtDO&9(yD8H0@( zv>6P5(2&6th>d|PQwAf3Bp@r1!3;>6F_;6P0YfTKJdwc?$Vvu^fmn$^*<>JX4wN$k W(xyP#0I1h|vb&He(D+)RW@Z33Wl!q> delta 321 zcmZqBY0#O_!J=B~*0!sAX&|-F3Q9J0~47cEEWc)&D&WYG7AWVi&wK+ zZ*Xk6)XrADc=z(nD>x+?&5iU77#NHh8T1VFO!SPjEsavsOp;Q}Oj8rnOpGlo%+gFP zlPwIBEmD#b&5e^S%@QqBlT%X7Cja2lU=ck2`q5++?f`+C(It%T&$7LCRxf#|HMKHo zatn8#0#q>SCsYum`mVfUiTSOsQBn0v?rt{XiDBYoVqgKglWFsGzNL(eMw@K~wlfAB zF=#VbG8i$WGNdt>FeEXgFqkoz0(psGzA=LZ5Sjtmra)E6K(-;6OaYRK4CV~RK-v;0 Yp9myVf$S6@X*Sti$P{RNtxz*F08T1aoB#j- diff --git a/jackify/engine/Wabbajack.Networking.Http.dll b/jackify/engine/Wabbajack.Networking.Http.dll index ad61afb872433b8f3bcd26876d73443f5ec7aca5..67aab0844b7c60a5c6cc16ddbf877d4a50965bb0 100644 GIT binary patch delta 366 zcmZp8!PxMEaY6@+!6wG98+#_$Ffvb;wJjB8W`F@k0SJqQfqC+BTWchlRH!15%y(OV zM&`}#cCIo4#?`W^(sTLU7Eiuh<;mvsbMu_2U>?V@3u&13eQxV{HQiLsLUz z15=|U<3uxKGjnr;RMSLDgJiQr%S7X3OY>ASOVd;XBlF35>F%3z)0>%C9w{eZn*1d* zK;VPUG0EAS0*({PZfpD9>$@>IB&$LJD%c1U1gZ9uTl+L3?nV&jjZ>30zsXv}$jQvW z0`?5^=IJ@5jEu&cdGqoagN+%q84Q5Xkiis)je#sv1|xM@0&&j)w@**qsIn8(~m&wzo!n2|xxK+ih7^bs$rz`}NyXrMw-R-()RfXo3@Hp|45mO{BA9Q?U;%_?K(;AR zRWgum2qsg2WFmt(gE5e{1j;7@$y6Xa1xT7rKA&&OYQ&(&V6a)R-~;1C1CGs%MgJKA DGS+HT diff --git a/jackify/engine/Wabbajack.Networking.NexusApi.dll b/jackify/engine/Wabbajack.Networking.NexusApi.dll index 9ff0b1bec599cd7b1a5117389c2aa6d28454d6dc..e32e2a5c8bb20397ae1e25e4a596abc17753413e 100644 GIT binary patch delta 372 zcmZp8!`$$Oc|r$EzQ^mnjXg#^jLehw_gIQDGr)j|5`@LVz&x3^R~;yC+N+Kvzf=|> zU)U=QlyB|z2a;DoYB&GwJ!~T|Lv8_+)xM8D8+LaIy*u=uVe^Gk8^p|w^$Zvoj2Rj9 z4D?L&jI|963{4G<4NQ%aj1$d_&CJaWQcV*r4U)|gEfbBCEzMKSEKO4ljLat&Jks0z z^r0{pOL;*3lF5Hw2MFBVee2V#dD>o!H?Q>kwdnAz$uVyl6rh4!FhQVd72$i2%U19D zdw<&2gO4}=c{7QTlbL}9?0n|U)8Dl+G8%7|{m{-BY|Nm|U;u=M45mPA3}l%y7%?OP zS&0m0K+=rC90&~4ReulwmT FGXSk`a#H{R delta 372 zcmZp8!`$$Oc|r%vO(*-VjXg#^j7*dF_gIQDF~ER`5`@LVz%-e+R~;yC+N+Kvzf=|> zU)U=QlyB|z2a;DoYB&GwJ!~T&%=<0G^6^2R`HwEI`o1CK#O4d9Hi(%U=@~FE7&9{H z8R(hl8EIP@rKXuArI?wfCZ?GfTUeN-nOY`W7$#e!Bqy31CtI2&TBataq?%1Gc%--a z=|f>ImcJp{zb5~A9U#y%x3MqSd$Hr{+_}cPET^VSj(O9d02SP*4HX2ct|~ApF-(@8I3l}erRV5He%3buw*b|NM%T4FkwhyNMSHz zFa`1w!F*!|3m`NDvQ2@il7VbPFqr}*6B*1IjDfTzP(BezrUKb1K+ GW(ELQrEkUn diff --git a/jackify/engine/Wabbajack.Networking.WabbajackClientApi.dll b/jackify/engine/Wabbajack.Networking.WabbajackClientApi.dll index 5e264c813276d57bfc2a31e3c8f0541b245c6639..9792a3eeed9479869d6e98c4266c516d80c1ef3b 100644 GIT binary patch delta 407 zcmZp8z|!!5WkLtb3M=s$8+$}HGBQtIxKR~I9^a_0$;a9d-(df)BmV41_->ne%r#b^Na7~-G)j3Z$?>6k5OZs zq5u_rdl@PUR4=O+$C@3%5F0A==7r#P0d>YYMowl17O+p4w@=q#T+7I4yxmoc@i1es zF@rXP0T3E8m;$jekY&nX#E=AJB{G-+Nizm>AT(e|1&Sv!SOQteKrs+25h$Asq|JeH aW3ZF+kuu+t0v%jZ$8%t!g^F6YOVCk5OZs zq5u`W#R?S#s<-SH*wN8FQ~&n47YDny3#c>JF>*37uz-ESw0*h;<61^WqwTI*jE5P6 zjTp2UEE$X#QW?@1Oc;_FQW(q_Oo6;aFyENL0tn53Y*V1BWFXrROr`+ILw{PD(;jXfGcjLeg_2B|VKZ@wM$Qc6Ji&XhMxlRrD}Xnx#hopjM} z^Of99T;|4l1`G_wj0}1PdM0|t+6D%OriR7_rbbD|iDt%T=H>>eriqpY$!3X`iN?v6 z=BZ|url|%-=9Bv?EjE9xxXsLR+xgqI$yaIu1p0%n{?9Tx>9ISvrn#}=(7MScwOI;K zK@DrDAW(H!dgpw}j0k4o1^NO9Ht(ywz{JVSz`_6m%$uh-#4<7(Z~ofo%@}OVpv_$lP5QuvKlk!F&Io%tTWln*z%td04)b#TL1t6 delta 310 zcmZqZVQT1Mn$W>=rRvZ8jXfGcj7*ca2B|VKZN44!QcA%0S+A~)^$WlL7jkW94p}B{ zzLL9%%iKuMfPul7kwMQu&qU8i+tMgC%_J$s%rrGI&BWNk!Ys|yGTFi~*&-!5(cC!M z(k#(3H8~~KY;u34#pbUSx0zX(K7VbRe5EEp;O7LkkI911oqAaMMf>G0oSke^o2391 zOyYqGf>hsmR>v*ADKa8?rB~tReYF>uIGGq&7(jq&^Yn&TMnj7bI diff --git a/jackify/engine/Wabbajack.Paths.dll b/jackify/engine/Wabbajack.Paths.dll index b655e969ca4b247664aeebef33ab6b572b436499..d2ce72a696acc93d124eb2206a3a0b9efcc9bcc7 100644 GIT binary patch delta 291 zcmZqZU~K4MoY2AYU%=w@#-1r^jLe%A)!F$3K4!gN8@wvgZ^4YM58WkZF>kgoEo3z} z)-zyWFlJ=XGte{9GuAdRFf=tZHZV0xGEOuzHZwOjNHtBgG)Oi}v`jQkwlq&QvouXL zFfyO~z$${}2v_In$u`yj0_wACSNQTPd(A2_TfO`Ll1-E6Sf?mJ1-qi4fYt52xz z+;s6*5mU#RE1MN;@|ZZ88CV!VfO+$DJ6}dd#} zh9n>>k--c|nlYFIp#eiGP&|>r639vhih)>(K-pv?D<^AikmG=3t7#L z^b8mnj2Rj94D?L&jI=F{QqxS5Qp`+K6VptLEiBB^Of8cw43jNVk`v91lP%2>EmM+|C|EXsvW<0sz>;!l{sxnFuVwLK-UoNzel&TGb&3L1@KqXA5U5(dULf;P z_4`oWbvL-~ZC0?!W8!3DU||3Orp?psd>I*yHb1pDW(+oB&}Oh?Fk(n$NMkTzNMcA~ zFk>(U@)E&(V+IQ#Gy}3tfvS>$Y(p@a0wfa|%o&V4$ND#;7l5iNTIdJlYg!{M~3}CIIrdR@?vp delta 302 zcmZqJ!`QHgaY6@6+!ceD8+(Gx8JRZUH#Zd)cy(JqaK`@C-Yd-er(I6X>D#R0oy%!! zq-VguV9dy%XP{@IXQXXul$vIelwxL@nwVx{Y++%RW@?#iVVG=@lALI6oNQ^9XqlRv zl4{1lpvTCdIeB5I*ybl8a~N5A#rCb6+!7WbAS#%!uZ&I6ak*kbmn_eLjLA>J3KXD% zf$2~|km{O!Un9>=b7&J(m;bxDBwUJ-lZk0)CQ$u3|Q==r~L^ESE zb8~}K(?m;yWV1xeMB`*j^HeiS(^LZ^^T|5}wOO`I%s4jrgo&r=*872r+?e?J7cJ8iTE=SQEshrK%gmy4;GBZHj%)EKJNGct__7T#F8 delta 327 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 m6Z(-ir>q{UPU55Fnkl5wJ$v6;EKL8@t@r9rY; zqGh6SvZZ;dnWbr}fsy%SeojS}d)GIAnry-uAn6P5 z(2&6th>d|PQwAf3Bp@r1!3;>6F_;6P0YfTKJdwc?$Vvu^fmn$^*<>JX4wN$k(xyP# g0I1h|@_Bw!R$~S|27}Fl0v{MB8gOi86#CBy0N#dI0ssI2 delta 326 zcmZqBXwaC@!7^)8)Q*ikf0!AWCU>%^iZU_4fE$#_!oaloFpDg+z?Z8F&c@En_L#6| zd3p5_iQAJuu)i=j(lcOSFlJ=XGte{9Gt#y+N=-9KN-;A{O-wT}wy-cuGqp^%Fif^c zNlr93PPQ~lv`kG-Nj00y&#B0wU$|kyWE0K+0S>Rl-+Rkr9o8J?^8GxG`^MxcoMj47 zL4!9?L7-{@)y_?u@3+`LpUrnxXtNeq3KJ(20}I%}Oq-|klrl0JZRX|6XACxC&}Oh? zFk(n$NMkTzNMcA~Fk>(U@)E&(V+IQ#Gy}3tfvS>$Y(p@a0wfa|%o&VS-hB7FI%BXggEoT!5E?R=0(V8oEhkj7xbki?L} zV8&ny;1xO|`m@^myX-lAdB9Kf4vQvPh+2rt_ra;alfNxOg`Evt1xypeM=wmV23fe+^p8N3G!aG|@rFymjVSPei%r+%Wl~3S z(|1>dR3%HoxJhua^eDL4bVEKSf1r;W`B0pD z9N`nVE^!@%uDyIH&JA)wNc{v#0S#2-XG&&d-rma3B!5+)d?jBF+d&?Or4>9DS6x>& zZQt^Q>5QMbv7P|~gE1q6o`If;p0T!pfuX6Pv4N>kl5wJ$v6;EKL8@t@r9rY;qGh6S zvZZ;dnWbr}fsy(4?Z(WR@+_~O%YU7&63iSRu#7u&?%g?x{!6p{IXr!y-6P5(2&6th>d|PQwAf3Bp@r1!3;>6F_;6P0YfTKJdwc?$Vvu^fmn$^*<>JX c4wN$k(xyP#0I1h|`r=4tQ=kPmBbn8i0Q;zSA^-pY delta 684 zcmZqJ!P~HdcR~k?RLWC^5>ax;~SGBLnFnC!p;V+0;Y-KqZg)FgDhNZ`bVHing}C~c*CXOMiltK#ir}>GN~iE z>ANdJDwCHf8A(b3;h5*Iqsp=LWeTq<#XWfCeh^GbJ-JZExjglD{f2b)AIjl2=?#tyP;grBBMr z+rH%q(-}W=BR!xyjTssA4D?L&jI=F{QqxS5Qp`+K6VptLEiBB^Of8cw43jNVk`v91 zlP%2>EmM7ZJj2KcG(ilt_k{D7L%ot38yhJeHn85-F&46rEpsHjb+Yn5q0LerKa|UA|Z3&c5 Z1d^#hb_$R*o4z=b*%WBO%}8c-CIAS)c@+Qv diff --git a/jackify/frontends/gui/main.py b/jackify/frontends/gui/main.py index dfa3a14..fa0d350 100644 --- a/jackify/frontends/gui/main.py +++ b/jackify/frontends/gui/main.py @@ -105,7 +105,7 @@ from PySide6.QtWidgets import ( QApplication, QMainWindow, QWidget, QLabel, QVBoxLayout, QPushButton, QStackedWidget, QHBoxLayout, QDialog, QFormLayout, QLineEdit, QCheckBox, QSpinBox, QMessageBox, QGroupBox, QGridLayout, QFileDialog, QToolButton, QStyle, QComboBox, QTabWidget, QRadioButton, QButtonGroup ) -from PySide6.QtCore import Qt, QEvent, QTimer +from PySide6.QtCore import Qt, QEvent, QTimer, QThread, Signal from PySide6.QtGui import QIcon import json @@ -1533,58 +1533,42 @@ class JackifyMainWindow(QMainWindow): # Continue anyway - don't block startup on detection errors def _check_for_updates_on_startup(self): - """Check for updates on startup - SIMPLE VERSION""" + """Check for updates on startup - non-blocking background check""" try: debug_print("Checking for updates on startup...") - # Do it synchronously and simply - update_info = self.update_service.check_for_updates() - if update_info: + # Run update check in background thread to avoid blocking GUI startup + class UpdateCheckThread(QThread): + update_available = Signal(object) # Signal to pass update_info to main thread + + def __init__(self, update_service): + super().__init__() + self.update_service = update_service + + def run(self): + update_info = self.update_service.check_for_updates() + if update_info: + self.update_available.emit(update_info) + + def on_update_available(update_info): + """Handle update check result in main thread""" debug_print(f"Update available: v{update_info.version}") - # Simple QMessageBox - no complex dialogs - from PySide6.QtWidgets import QMessageBox - from PySide6.QtCore import QTimer - + # Show update dialog after a short delay to ensure GUI is fully loaded def show_update_dialog(): - try: - debug_print("Creating UpdateDialog...") - from jackify.frontends.gui.dialogs.update_dialog import UpdateDialog - dialog = UpdateDialog(update_info, self.update_service, self) - debug_print("UpdateDialog created, showing...") - dialog.show() # Non-blocking - debug_print("UpdateDialog shown successfully") - except Exception as e: - debug_print(f"UpdateDialog failed: {e}, falling back to simple dialog") - # Fallback to simple dialog - reply = QMessageBox.question( - self, - "Update Available", - f"Jackify v{update_info.version} is available.\n\nDownload and install now?", - QMessageBox.Yes | QMessageBox.No, - QMessageBox.Yes - ) - if reply == QMessageBox.Yes: - # Simple download and replace - try: - new_appimage = self.update_service.download_update(update_info) - if new_appimage: - if self.update_service.apply_update(new_appimage): - debug_print("Update applied successfully") - else: - QMessageBox.warning(self, "Update Failed", "Failed to apply update.") - else: - QMessageBox.warning(self, "Update Failed", "Failed to download update.") - except Exception as e: - QMessageBox.warning(self, "Update Failed", f"Update failed: {e}") + from ..dialogs.update_dialog import UpdateDialog + dialog = UpdateDialog(update_info, self.update_service, self) + dialog.exec() - # Use QTimer to show dialog after GUI is fully loaded QTimer.singleShot(1000, show_update_dialog) - else: - debug_print("No updates available") - + + # Start background thread + self._update_thread = UpdateCheckThread(self.update_service) + self._update_thread.update_available.connect(on_update_available) + self._update_thread.start() + except Exception as e: - debug_print(f"Error checking for updates on startup: {e}") + debug_print(f"Error setting up update check: {e}") # Continue anyway - don't block startup on update check errors def cleanup_processes(self): diff --git a/jackify/frontends/gui/screens/install_modlist.py b/jackify/frontends/gui/screens/install_modlist.py index 12c76e1..46eb6a9 100644 --- a/jackify/frontends/gui/screens/install_modlist.py +++ b/jackify/frontends/gui/screens/install_modlist.py @@ -2157,12 +2157,6 @@ class InstallModlistScreen(QWidget): cmd.append('--debug') debug_print("DEBUG: Added --debug flag to jackify-engine command") - # Check GPU setting and add --no-gpu flag if disabled - gpu_enabled = config_handler.get('enable_gpu_texture_conversion', True) - if not gpu_enabled: - cmd.append('--no-gpu') - debug_print("DEBUG: Added --no-gpu flag (GPU texture conversion disabled)") - # CRITICAL: Print the FULL command so we can see exactly what's being passed debug_print(f"DEBUG: FULL Engine command: {' '.join(cmd)}") debug_print(f"DEBUG: modlist value being passed: '{self.modlist}'") diff --git a/jackify/frontends/gui/screens/modlist_gallery.py b/jackify/frontends/gui/screens/modlist_gallery.py index e1e7b9a..6c7ebdf 100644 --- a/jackify/frontends/gui/screens/modlist_gallery.py +++ b/jackify/frontends/gui/screens/modlist_gallery.py @@ -388,6 +388,15 @@ class ModlistDetailDialog(QDialog): main_layout = QVBoxLayout() main_layout.setContentsMargins(0, 0, 0, 0) main_layout.setSpacing(0) + # --- Banner area with full-width text overlay --- + # Container so we can place a semi-opaque text panel over the banner image + banner_container = QFrame() + banner_container.setFrameShape(QFrame.NoFrame) + banner_container.setStyleSheet("background: #000; border: none;") + banner_layout = QVBoxLayout() + banner_layout.setContentsMargins(0, 0, 0, 0) + banner_layout.setSpacing(0) + banner_container.setLayout(banner_layout) # Banner image at top with 16:9 aspect ratio (like Wabbajack) self.banner_label = QLabel() @@ -396,40 +405,67 @@ class ModlistDetailDialog(QDialog): self.banner_label.setStyleSheet("background: #1a1a1a; border: none;") self.banner_label.setAlignment(Qt.AlignCenter) self.banner_label.setText("Loading image...") - main_layout.addWidget(self.banner_label) + banner_layout.addWidget(self.banner_label) - # Content area with padding + # Full-width transparent container with opaque card inside (only as wide as text) + overlay_container = QWidget() + overlay_container.setStyleSheet("background: transparent;") + overlay_layout = QHBoxLayout() + overlay_layout.setContentsMargins(24, 0, 24, 24) + overlay_layout.setSpacing(0) + overlay_container.setLayout(overlay_layout) + + # Opaque text card - only as wide as content needs (where red lines are) + self.banner_text_panel = QFrame() + self.banner_text_panel.setFrameShape(QFrame.StyledPanel) + # Opaque background, rounded corners, sized to content only + self.banner_text_panel.setStyleSheet(""" + QFrame { + background-color: rgba(0, 0, 0, 180); + border: 1px solid rgba(255, 255, 255, 30); + border-radius: 8px; + } + """) + self.banner_text_panel.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) + banner_text_layout = QVBoxLayout() + banner_text_layout.setContentsMargins(20, 12, 20, 14) + banner_text_layout.setSpacing(6) + self.banner_text_panel.setLayout(banner_text_layout) + + # Add card to container (left-aligned, rest stays transparent) + overlay_layout.addWidget(self.banner_text_panel, alignment=Qt.AlignBottom | Qt.AlignLeft) + overlay_layout.addStretch() # Push card left, rest transparent + + # Title only (badges moved to tags section below) + title = QLabel(self.metadata.title) + title.setFont(QFont("Sans", 24, QFont.Bold)) + title.setStyleSheet(f"color: {JACKIFY_COLOR_BLUE};") + title.setWordWrap(True) + banner_text_layout.addWidget(title) + + # Only sizes in overlay (minimal info on image) + if self.metadata.sizes: + sizes_text = ( + f"Download: {self.metadata.sizes.downloadSizeFormatted} • " + f"Install: {self.metadata.sizes.installSizeFormatted} • " + f"Total: {self.metadata.sizes.totalSizeFormatted}" + ) + sizes_label = QLabel(sizes_text) + sizes_label.setStyleSheet("color: #fff; font-size: 13px;") + banner_text_layout.addWidget(sizes_label) + + # Add full-width transparent container at bottom of banner + banner_layout.addWidget(overlay_container, alignment=Qt.AlignBottom) + main_layout.addWidget(banner_container) + + # Content area with padding (tags + description + bottom bar) content_widget = QWidget() content_layout = QVBoxLayout() content_layout.setContentsMargins(24, 20, 24, 20) content_layout.setSpacing(16) content_widget.setLayout(content_layout) - # Title row with status badges (UNAVAILABLE, Unofficial - Official and NSFW shown in tags) - title_row = QHBoxLayout() - title_row.setSpacing(12) - - title = QLabel(self.metadata.title) - title.setFont(QFont("Sans", 24, QFont.Bold)) - title.setStyleSheet(f"color: {JACKIFY_COLOR_BLUE};") - title.setWordWrap(True) - title_row.addWidget(title, stretch=1) - - # Status badges in title row - if not self.metadata.is_available(): - unavailable_badge = QLabel("UNAVAILABLE") - unavailable_badge.setStyleSheet("background: #666; color: white; padding: 4px 10px; font-size: 10px; font-weight: bold; border-radius: 4px;") - title_row.addWidget(unavailable_badge) - - # Show "Unofficial" badge if not official (Official is shown in tags) - if not self.metadata.official: - unofficial_badge = QLabel("Unofficial") - unofficial_badge.setStyleSheet("background: #666; color: white; padding: 4px 10px; font-size: 10px; font-weight: bold; border-radius: 4px;") - title_row.addWidget(unofficial_badge) - - content_layout.addLayout(title_row) - - # Metadata line (version, author, game) - inline like Wabbajack + # Metadata line (version, author, game) - moved below image metadata_line_parts = [] if self.metadata.version: metadata_line_parts.append(f"version {self.metadata.version}") @@ -446,13 +482,25 @@ class ModlistDetailDialog(QDialog): metadata_line.setWordWrap(True) content_layout.addWidget(metadata_line) - # Tags row (like Wabbajack) + # Tags row (includes status badges moved from overlay) + tags_layout = QHBoxLayout() + tags_layout.setSpacing(6) + tags_layout.setContentsMargins(0, 0, 0, 0) + + # Add status badges first (UNAVAILABLE, Unofficial) + if not self.metadata.is_available(): + unavailable_badge = QLabel("UNAVAILABLE") + unavailable_badge.setStyleSheet("background: #666; color: white; padding: 6px 12px; font-size: 11px; border-radius: 4px;") + tags_layout.addWidget(unavailable_badge) + + if not self.metadata.official: + unofficial_badge = QLabel("Unofficial") + unofficial_badge.setStyleSheet("background: #666; color: white; padding: 6px 12px; font-size: 11px; border-radius: 4px;") + tags_layout.addWidget(unofficial_badge) + + # Add regular tags tags_to_render = getattr(self.metadata, 'normalized_tags_display', self.metadata.tags or []) if tags_to_render: - tags_layout = QHBoxLayout() - tags_layout.setSpacing(6) - tags_layout.setContentsMargins(0, 0, 0, 0) - for tag in tags_to_render: tag_badge = QLabel(tag) # Match Wabbajack tag styling @@ -463,20 +511,9 @@ class ModlistDetailDialog(QDialog): else: tag_badge.setStyleSheet("background: #3a3a3a; color: #ccc; padding: 6px 12px; font-size: 11px; border-radius: 4px;") tags_layout.addWidget(tag_badge) - - tags_layout.addStretch() - content_layout.addLayout(tags_layout) - - # Sizes (if available) - if self.metadata.sizes: - sizes_text = ( - f"Download: {self.metadata.sizes.downloadSizeFormatted} • " - f"Install: {self.metadata.sizes.installSizeFormatted} • " - f"Total: {self.metadata.sizes.totalSizeFormatted}" - ) - sizes_label = QLabel(sizes_text) - sizes_label.setStyleSheet("color: #fff; font-size: 13px;") - content_layout.addWidget(sizes_label) + + tags_layout.addStretch() + content_layout.addLayout(tags_layout) # Description section desc_label = QLabel("Description:") @@ -486,7 +523,8 @@ class ModlistDetailDialog(QDialog): self.desc_text = QTextEdit() self.desc_text.setReadOnly(True) self.desc_text.setPlainText(self.metadata.description or "No description provided.") - self.desc_text.setFixedHeight(300) + # Compact description area; scroll when content is long + self.desc_text.setFixedHeight(120) self.desc_text.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.desc_text.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.desc_text.setLineWrapMode(QTextEdit.WidgetWidth) @@ -832,8 +870,8 @@ class ModlistGalleryDialog(QDialog): main_layout.addWidget(filter_panel) # Right content area (modlist grid) - content_area = self._create_content_area() - main_layout.addWidget(content_area, stretch=1) + self.content_area = self._create_content_area() + main_layout.addWidget(self.content_area, stretch=1) self.setLayout(main_layout) @@ -934,8 +972,8 @@ class ModlistGalleryDialog(QDialog): layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(12) - # Status label (subtle, top-right) - self.status_label = QLabel("Loading modlists...") + # Status label (subtle, top-right) - hidden during initial loading (popup shows instead) + self.status_label = QLabel("") self.status_label.setStyleSheet("color: #888; font-size: 10px;") self.status_label.setAlignment(Qt.AlignRight | Qt.AlignTop) layout.addWidget(self.status_label) @@ -965,13 +1003,55 @@ class ModlistGalleryDialog(QDialog): from PySide6.QtCore import QThread, Signal from PySide6.QtGui import QFont - # Make status label more prominent during loading - self.status_label.setText("Loading modlists...") + # Hide status label during loading (popup dialog will show instead) + self.status_label.setVisible(False) + + # Show loading overlay directly in content area (simpler than separate dialog) + self._loading_overlay = QWidget(self.content_area) + self._loading_overlay.setStyleSheet(""" + QWidget { + background-color: rgba(35, 35, 35, 240); + border-radius: 8px; + } + """) + overlay_layout = QVBoxLayout() + overlay_layout.setContentsMargins(30, 20, 30, 20) + overlay_layout.setSpacing(12) + + self._loading_label = QLabel("Loading modlists") + self._loading_label.setAlignment(Qt.AlignCenter) + # Set fixed width to prevent text shifting when dots animate + # Width accommodates "Loading modlists..." (longest version) + self._loading_label.setFixedWidth(220) font = QFont() font.setPointSize(14) font.setBold(True) - self.status_label.setFont(font) - self.status_label.setStyleSheet(f"color: {JACKIFY_COLOR_BLUE}; font-size: 14px; font-weight: bold;") + self._loading_label.setFont(font) + self._loading_label.setStyleSheet(f"color: {JACKIFY_COLOR_BLUE}; font-size: 14px; font-weight: bold;") + overlay_layout.addWidget(self._loading_label) + + self._loading_overlay.setLayout(overlay_layout) + self._loading_overlay.setFixedSize(300, 120) + + # Animate dots in loading message + self._loading_dot_count = 0 + self._loading_dot_timer = QTimer() + self._loading_dot_timer.timeout.connect(self._animate_loading_dots) + self._loading_dot_timer.start(500) # Update every 500ms + + # Position overlay in center of content area + def position_overlay(): + if hasattr(self, 'content_area') and self.content_area.isVisible(): + content_width = self.content_area.width() + content_height = self.content_area.height() + x = (content_width - 300) // 2 + y = (content_height - 120) // 2 + self._loading_overlay.move(x, y) + self._loading_overlay.show() + self._loading_overlay.raise_() + + # Delay slightly to ensure content_area is laid out + QTimer.singleShot(50, position_overlay) class ModlistLoaderThread(QThread): """Background thread to load modlist metadata""" @@ -987,9 +1067,10 @@ class ModlistGalleryDialog(QDialog): start_time = time.time() # Fetch metadata (CPU-intensive work happens here in background) + # Skip search index initially for faster loading - can be loaded later if user searches metadata_response = self.gallery_service.fetch_modlist_metadata( include_validation=False, - include_search_index=True, + include_search_index=False, # Skip for faster initial load sort_by="title" ) @@ -1010,17 +1091,31 @@ class ModlistGalleryDialog(QDialog): self._loader_thread.finished.connect(self._on_modlists_loaded) self._loader_thread.start() + def _animate_loading_dots(self): + """Animate dots in loading message""" + if hasattr(self, '_loading_label') and self._loading_label: + self._loading_dot_count = (self._loading_dot_count + 1) % 4 + dots = "." * self._loading_dot_count + # Pad with spaces to keep text width constant (prevents shifting) + padding = " " * (3 - self._loading_dot_count) + self._loading_label.setText(f"Loading modlists{dots}{padding}") + def _on_modlists_loaded(self, metadata_response, error_message): """Handle modlist metadata loaded in background thread (runs in GUI thread)""" import random - from PySide6.QtCore import QTimer from PySide6.QtGui import QFont - # Restore normal status label styling - font = QFont() - font.setPointSize(10) - self.status_label.setFont(font) - self.status_label.setStyleSheet("color: #888; font-size: 10px;") + # Stop animation timer and close loading overlay + if hasattr(self, '_loading_dot_timer') and self._loading_dot_timer: + self._loading_dot_timer.stop() + self._loading_dot_timer = None + + if hasattr(self, '_loading_overlay') and self._loading_overlay: + self._loading_overlay.hide() + self._loading_overlay.deleteLater() + self._loading_overlay = None + + self.status_label.setVisible(True) if error_message: self.status_label.setText(f"Error loading modlists: {error_message}")