diff --git a/CHANGELOG.md b/CHANGELOG.md index f18f63a..5a1efc0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Jackify Changelog +## v0.2.0.6 - Premium Detection and Engine Update +**Release Date:** 2025-12-28 + +### Engine Updates +- **jackify-engine 0.4.4**: Latest engine version with improvements + +### Critical Bug Fixes +- **Premium Detection**: Fixed false Premium errors caused by overly-broad detection pattern triggering on jackify-engine 0.4.3's userinfo JSON output +- **Custom Data Directory**: Fixed AppImage always creating ~/Jackify on startup, even when user configured a custom jackify_data_dir +- **Proton Auto-Selection**: Fixed auto-selection writing invalid "auto" string to config on detection failure + +--- + ## v0.2.0.5 - Emergency OAuth Fix **Release Date:** 2025-12-24 diff --git a/jackify/__init__.py b/jackify/__init__.py index 6174054..20fe8c0 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.5" +__version__ = "0.2.0.6" 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/subprocess_utils.py b/jackify/backend/handlers/subprocess_utils.py index 99ea78a..a3fec27 100644 --- a/jackify/backend/handlers/subprocess_utils.py +++ b/jackify/backend/handlers/subprocess_utils.py @@ -48,6 +48,9 @@ def get_clean_subprocess_env(extra_env=None): env = os.environ.copy() + # Save APPDIR before removing it (we need it to find bundled tools) + appdir = env.get('APPDIR') + # Remove AppImage-specific variables that can confuse subprocess calls # These variables cause subprocesses to be interpreted as new AppImage launches for key in ['APPIMAGE', 'APPDIR', 'ARGV0', 'OWD']: @@ -57,10 +60,10 @@ def get_clean_subprocess_env(extra_env=None): for k in list(env): if k.startswith('_MEIPASS'): del env[k] - + # Get current PATH - ensure we preserve system paths current_path = env.get('PATH', '') - + # Ensure common system directories are in PATH if not already present # This is critical for tools like lz4 that might be in /usr/bin, /usr/local/bin, etc. system_paths = ['/usr/bin', '/usr/local/bin', '/bin', '/sbin', '/usr/sbin'] @@ -68,10 +71,10 @@ def get_clean_subprocess_env(extra_env=None): for sys_path in system_paths: if sys_path not in path_parts and os.path.isdir(sys_path): path_parts.append(sys_path) - + # Add bundled tools directory to PATH if running as AppImage # This ensures lz4, unzip, xz, etc. are available to subprocesses - appdir = env.get('APPDIR') + # Note: appdir was saved before env cleanup above tools_dir = None if appdir: diff --git a/jackify/backend/utils/nexus_premium_detector.py b/jackify/backend/utils/nexus_premium_detector.py index f9501c9..cd219db 100644 --- a/jackify/backend/utils/nexus_premium_detector.py +++ b/jackify/backend/utils/nexus_premium_detector.py @@ -34,9 +34,6 @@ def is_non_premium_indicator(line: str) -> bool: if phrase in normalized: return True - if "nexus" in normalized and "premium" in normalized: - return True - # Manual download + Nexus URL implies premium requirement in current workflows. if "manual download" in normalized and ("nexusmods.com" in normalized or "nexus mods" in normalized): return True diff --git a/jackify/engine/Wabbajack.CLI.Builder.dll b/jackify/engine/Wabbajack.CLI.Builder.dll index 1f69963..8416535 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 41974b6..6fdbe66 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 d59b807..59ed9cc 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 b0059cd..6bf85a3 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 280bbd8..21caded 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 1b03380..5548374 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 fa318da..50e12ac 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 278ec4f..dc1af8f 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 22a29d2..f136897 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 0b0d73c..43ece97 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 01c72de..7a65cdb 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 d0b58f1..5b0bd38 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 f262753..faea021 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 e3bbf28..312ff41 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 4930662..0b127a1 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 f023068..d754560 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 c0b0e2e..9e9ac97 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 3b3bd5b..abfa3e1 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 eef2cd0..a1759f5 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 3342732..f31aab6 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 f43f0fc..6073a3d 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 65021c3..4e424fc 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 5e1a50b..20c6459 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 3459fda..8a0b49a 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 a11ff86..2d43bfe 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 178d79b..be2b32a 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 98567a2..f9f6a63 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 94b72ae..04724dc 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 58c3f34..b29dd77 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 210ee91..b7d0e6b 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 67aab08..ebb3c11 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 e32e2a5..9b1925d 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 9792a3e..6361ed6 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 ae3b1ea..957c752 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 d2ce72a..b70e2ca 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 e3c21c9..c0703f1 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 75a6971..be1d901 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 0eef03b..897541a 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 2b157bd..e52ec45 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 9a30b91..7f65394 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 1828e00..7699157 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.4.3": { + "jackify-engine/0.4.4": { "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.4.3", - "Wabbajack.Downloaders.Bethesda": "0.4.3", - "Wabbajack.Downloaders.Dispatcher": "0.4.3", - "Wabbajack.Hashing.xxHash64": "0.4.3", - "Wabbajack.Networking.Discord": "0.4.3", - "Wabbajack.Networking.GitHub": "0.4.3", - "Wabbajack.Paths.IO": "0.4.3", - "Wabbajack.Server.Lib": "0.4.3", - "Wabbajack.Services.OSIntegrated": "0.4.3", - "Wabbajack.VFS": "0.4.3", + "Wabbajack.CLI.Builder": "0.4.4", + "Wabbajack.Downloaders.Bethesda": "0.4.4", + "Wabbajack.Downloaders.Dispatcher": "0.4.4", + "Wabbajack.Hashing.xxHash64": "0.4.4", + "Wabbajack.Networking.Discord": "0.4.4", + "Wabbajack.Networking.GitHub": "0.4.4", + "Wabbajack.Paths.IO": "0.4.4", + "Wabbajack.Server.Lib": "0.4.4", + "Wabbajack.Services.OSIntegrated": "0.4.4", + "Wabbajack.VFS": "0.4.4", "MegaApiClient": "1.0.0.0", "runtimepack.Microsoft.NETCore.App.Runtime.linux-x64": "8.0.22" }, @@ -1781,7 +1781,7 @@ } } }, - "Wabbajack.CLI.Builder/0.4.3": { + "Wabbajack.CLI.Builder/0.4.4": { "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.4.3" + "Wabbajack.Paths": "0.4.4" }, "runtime": { "Wabbajack.CLI.Builder.dll": {} } }, - "Wabbajack.Common/0.4.3": { + "Wabbajack.Common/0.4.4": { "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.4.3", - "Wabbajack.Networking.Http": "0.4.3", - "Wabbajack.Paths.IO": "0.4.3" + "Wabbajack.DTOs": "0.4.4", + "Wabbajack.Networking.Http": "0.4.4", + "Wabbajack.Paths.IO": "0.4.4" }, "runtime": { "Wabbajack.Common.dll": {} } }, - "Wabbajack.Compiler/0.4.3": { + "Wabbajack.Compiler/0.4.4": { "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.4.3", - "Wabbajack.Installer": "0.4.3", - "Wabbajack.VFS": "0.4.3", + "Wabbajack.Downloaders.Dispatcher": "0.4.4", + "Wabbajack.Installer": "0.4.4", + "Wabbajack.VFS": "0.4.4", "ini-parser-netstandard": "2.5.2" }, "runtime": { "Wabbajack.Compiler.dll": {} } }, - "Wabbajack.Compression.BSA/0.4.3": { + "Wabbajack.Compression.BSA/0.4.4": { "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.4.3", - "Wabbajack.DTOs": "0.4.3" + "Wabbajack.Common": "0.4.4", + "Wabbajack.DTOs": "0.4.4" }, "runtime": { "Wabbajack.Compression.BSA.dll": {} } }, - "Wabbajack.Compression.Zip/0.4.3": { + "Wabbajack.Compression.Zip/0.4.4": { "dependencies": { - "Wabbajack.IO.Async": "0.4.3" + "Wabbajack.IO.Async": "0.4.4" }, "runtime": { "Wabbajack.Compression.Zip.dll": {} } }, - "Wabbajack.Configuration/0.4.3": { + "Wabbajack.Configuration/0.4.4": { "runtime": { "Wabbajack.Configuration.dll": {} } }, - "Wabbajack.Downloaders.Bethesda/0.4.3": { + "Wabbajack.Downloaders.Bethesda/0.4.4": { "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.4.3", - "Wabbajack.Downloaders.Interfaces": "0.4.3", - "Wabbajack.Networking.BethesdaNet": "0.4.3" + "Wabbajack.Common": "0.4.4", + "Wabbajack.Downloaders.Interfaces": "0.4.4", + "Wabbajack.Networking.BethesdaNet": "0.4.4" }, "runtime": { "Wabbajack.Downloaders.Bethesda.dll": {} } }, - "Wabbajack.Downloaders.Dispatcher/0.4.3": { + "Wabbajack.Downloaders.Dispatcher/0.4.4": { "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.4.3", - "Wabbajack.Downloaders.GameFile": "0.4.3", - "Wabbajack.Downloaders.GoogleDrive": "0.4.3", - "Wabbajack.Downloaders.Http": "0.4.3", - "Wabbajack.Downloaders.IPS4OAuth2Downloader": "0.4.3", - "Wabbajack.Downloaders.Interfaces": "0.4.3", - "Wabbajack.Downloaders.Manual": "0.4.3", - "Wabbajack.Downloaders.MediaFire": "0.4.3", - "Wabbajack.Downloaders.Mega": "0.4.3", - "Wabbajack.Downloaders.ModDB": "0.4.3", - "Wabbajack.Downloaders.Nexus": "0.4.3", - "Wabbajack.Downloaders.VerificationCache": "0.4.3", - "Wabbajack.Downloaders.WabbajackCDN": "0.4.3", - "Wabbajack.Networking.WabbajackClientApi": "0.4.3" + "Wabbajack.Downloaders.Bethesda": "0.4.4", + "Wabbajack.Downloaders.GameFile": "0.4.4", + "Wabbajack.Downloaders.GoogleDrive": "0.4.4", + "Wabbajack.Downloaders.Http": "0.4.4", + "Wabbajack.Downloaders.IPS4OAuth2Downloader": "0.4.4", + "Wabbajack.Downloaders.Interfaces": "0.4.4", + "Wabbajack.Downloaders.Manual": "0.4.4", + "Wabbajack.Downloaders.MediaFire": "0.4.4", + "Wabbajack.Downloaders.Mega": "0.4.4", + "Wabbajack.Downloaders.ModDB": "0.4.4", + "Wabbajack.Downloaders.Nexus": "0.4.4", + "Wabbajack.Downloaders.VerificationCache": "0.4.4", + "Wabbajack.Downloaders.WabbajackCDN": "0.4.4", + "Wabbajack.Networking.WabbajackClientApi": "0.4.4" }, "runtime": { "Wabbajack.Downloaders.Dispatcher.dll": {} } }, - "Wabbajack.Downloaders.GameFile/0.4.3": { + "Wabbajack.Downloaders.GameFile/0.4.4": { "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.4.3", - "Wabbajack.VFS": "0.4.3" + "Wabbajack.Downloaders.Interfaces": "0.4.4", + "Wabbajack.VFS": "0.4.4" }, "runtime": { "Wabbajack.Downloaders.GameFile.dll": {} } }, - "Wabbajack.Downloaders.GoogleDrive/0.4.3": { + "Wabbajack.Downloaders.GoogleDrive/0.4.4": { "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.4.3", - "Wabbajack.DTOs": "0.4.3", - "Wabbajack.Downloaders.Interfaces": "0.4.3", - "Wabbajack.Networking.Http": "0.4.3", - "Wabbajack.Networking.Http.Interfaces": "0.4.3" + "Wabbajack.Common": "0.4.4", + "Wabbajack.DTOs": "0.4.4", + "Wabbajack.Downloaders.Interfaces": "0.4.4", + "Wabbajack.Networking.Http": "0.4.4", + "Wabbajack.Networking.Http.Interfaces": "0.4.4" }, "runtime": { "Wabbajack.Downloaders.GoogleDrive.dll": {} } }, - "Wabbajack.Downloaders.Http/0.4.3": { + "Wabbajack.Downloaders.Http/0.4.4": { "dependencies": { "Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.Logging.Abstractions": "9.0.1", - "Wabbajack.Common": "0.4.3", - "Wabbajack.DTOs": "0.4.3", - "Wabbajack.Downloaders.Interfaces": "0.4.3", - "Wabbajack.Networking.BethesdaNet": "0.4.3", - "Wabbajack.Networking.Http.Interfaces": "0.4.3", - "Wabbajack.Paths.IO": "0.4.3" + "Wabbajack.Common": "0.4.4", + "Wabbajack.DTOs": "0.4.4", + "Wabbajack.Downloaders.Interfaces": "0.4.4", + "Wabbajack.Networking.BethesdaNet": "0.4.4", + "Wabbajack.Networking.Http.Interfaces": "0.4.4", + "Wabbajack.Paths.IO": "0.4.4" }, "runtime": { "Wabbajack.Downloaders.Http.dll": {} } }, - "Wabbajack.Downloaders.Interfaces/0.4.3": { + "Wabbajack.Downloaders.Interfaces/0.4.4": { "dependencies": { "Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", - "Wabbajack.Compression.Zip": "0.4.3", - "Wabbajack.DTOs": "0.4.3", - "Wabbajack.Paths.IO": "0.4.3" + "Wabbajack.Compression.Zip": "0.4.4", + "Wabbajack.DTOs": "0.4.4", + "Wabbajack.Paths.IO": "0.4.4" }, "runtime": { "Wabbajack.Downloaders.Interfaces.dll": {} } }, - "Wabbajack.Downloaders.IPS4OAuth2Downloader/0.4.3": { + "Wabbajack.Downloaders.IPS4OAuth2Downloader/0.4.4": { "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.4.3", - "Wabbajack.Downloaders.Interfaces": "0.4.3", - "Wabbajack.Networking.Http": "0.4.3", - "Wabbajack.Networking.Http.Interfaces": "0.4.3" + "Wabbajack.Common": "0.4.4", + "Wabbajack.Downloaders.Interfaces": "0.4.4", + "Wabbajack.Networking.Http": "0.4.4", + "Wabbajack.Networking.Http.Interfaces": "0.4.4" }, "runtime": { "Wabbajack.Downloaders.IPS4OAuth2Downloader.dll": {} } }, - "Wabbajack.Downloaders.Manual/0.4.3": { + "Wabbajack.Downloaders.Manual/0.4.4": { "dependencies": { "Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.Logging.Abstractions": "9.0.1", - "Wabbajack.Common": "0.4.3", - "Wabbajack.Downloaders.Interfaces": "0.4.3" + "Wabbajack.Common": "0.4.4", + "Wabbajack.Downloaders.Interfaces": "0.4.4" }, "runtime": { "Wabbajack.Downloaders.Manual.dll": {} } }, - "Wabbajack.Downloaders.MediaFire/0.4.3": { + "Wabbajack.Downloaders.MediaFire/0.4.4": { "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.4.3", - "Wabbajack.Downloaders.Interfaces": "0.4.3", - "Wabbajack.Networking.Http.Interfaces": "0.4.3" + "Wabbajack.Common": "0.4.4", + "Wabbajack.Downloaders.Interfaces": "0.4.4", + "Wabbajack.Networking.Http.Interfaces": "0.4.4" }, "runtime": { "Wabbajack.Downloaders.MediaFire.dll": {} } }, - "Wabbajack.Downloaders.Mega/0.4.3": { + "Wabbajack.Downloaders.Mega/0.4.4": { "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.4.3", - "Wabbajack.Downloaders.Interfaces": "0.4.3", - "Wabbajack.Paths.IO": "0.4.3" + "Wabbajack.Common": "0.4.4", + "Wabbajack.Downloaders.Interfaces": "0.4.4", + "Wabbajack.Paths.IO": "0.4.4" }, "runtime": { "Wabbajack.Downloaders.Mega.dll": {} } }, - "Wabbajack.Downloaders.ModDB/0.4.3": { + "Wabbajack.Downloaders.ModDB/0.4.4": { "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.4.3", - "Wabbajack.Downloaders.Interfaces": "0.4.3", - "Wabbajack.Networking.Http": "0.4.3", - "Wabbajack.Networking.Http.Interfaces": "0.4.3" + "Wabbajack.Common": "0.4.4", + "Wabbajack.Downloaders.Interfaces": "0.4.4", + "Wabbajack.Networking.Http": "0.4.4", + "Wabbajack.Networking.Http.Interfaces": "0.4.4" }, "runtime": { "Wabbajack.Downloaders.ModDB.dll": {} } }, - "Wabbajack.Downloaders.Nexus/0.4.3": { + "Wabbajack.Downloaders.Nexus/0.4.4": { "dependencies": { "Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", - "Wabbajack.DTOs": "0.4.3", - "Wabbajack.Downloaders.Interfaces": "0.4.3", - "Wabbajack.Hashing.xxHash64": "0.4.3", - "Wabbajack.Networking.Http": "0.4.3", - "Wabbajack.Networking.Http.Interfaces": "0.4.3", - "Wabbajack.Networking.NexusApi": "0.4.3", - "Wabbajack.Paths": "0.4.3" + "Wabbajack.DTOs": "0.4.4", + "Wabbajack.Downloaders.Interfaces": "0.4.4", + "Wabbajack.Hashing.xxHash64": "0.4.4", + "Wabbajack.Networking.Http": "0.4.4", + "Wabbajack.Networking.Http.Interfaces": "0.4.4", + "Wabbajack.Networking.NexusApi": "0.4.4", + "Wabbajack.Paths": "0.4.4" }, "runtime": { "Wabbajack.Downloaders.Nexus.dll": {} } }, - "Wabbajack.Downloaders.VerificationCache/0.4.3": { + "Wabbajack.Downloaders.VerificationCache/0.4.4": { "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.4.3", - "Wabbajack.Paths.IO": "0.4.3" + "Wabbajack.DTOs": "0.4.4", + "Wabbajack.Paths.IO": "0.4.4" }, "runtime": { "Wabbajack.Downloaders.VerificationCache.dll": {} } }, - "Wabbajack.Downloaders.WabbajackCDN/0.4.3": { + "Wabbajack.Downloaders.WabbajackCDN/0.4.4": { "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.4.3", - "Wabbajack.Downloaders.Interfaces": "0.4.3", - "Wabbajack.Networking.Http": "0.4.3", - "Wabbajack.RateLimiter": "0.4.3" + "Wabbajack.Common": "0.4.4", + "Wabbajack.Downloaders.Interfaces": "0.4.4", + "Wabbajack.Networking.Http": "0.4.4", + "Wabbajack.RateLimiter": "0.4.4" }, "runtime": { "Wabbajack.Downloaders.WabbajackCDN.dll": {} } }, - "Wabbajack.DTOs/0.4.3": { + "Wabbajack.DTOs/0.4.4": { "dependencies": { "Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", - "Wabbajack.Hashing.xxHash64": "0.4.3", - "Wabbajack.Paths": "0.4.3" + "Wabbajack.Hashing.xxHash64": "0.4.4", + "Wabbajack.Paths": "0.4.4" }, "runtime": { "Wabbajack.DTOs.dll": {} } }, - "Wabbajack.FileExtractor/0.4.3": { + "Wabbajack.FileExtractor/0.4.4": { "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.4.3", - "Wabbajack.Compression.BSA": "0.4.3", - "Wabbajack.Hashing.PHash": "0.4.3", - "Wabbajack.Paths": "0.4.3" + "Wabbajack.Common": "0.4.4", + "Wabbajack.Compression.BSA": "0.4.4", + "Wabbajack.Hashing.PHash": "0.4.4", + "Wabbajack.Paths": "0.4.4" }, "runtime": { "Wabbajack.FileExtractor.dll": {} } }, - "Wabbajack.Hashing.PHash/0.4.3": { + "Wabbajack.Hashing.PHash/0.4.4": { "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.4.3", - "Wabbajack.DTOs": "0.4.3", - "Wabbajack.Paths": "0.4.3", - "Wabbajack.Paths.IO": "0.4.3" + "Wabbajack.Common": "0.4.4", + "Wabbajack.DTOs": "0.4.4", + "Wabbajack.Paths": "0.4.4", + "Wabbajack.Paths.IO": "0.4.4" }, "runtime": { "Wabbajack.Hashing.PHash.dll": {} } }, - "Wabbajack.Hashing.xxHash64/0.4.3": { + "Wabbajack.Hashing.xxHash64/0.4.4": { "dependencies": { - "Wabbajack.Paths": "0.4.3", - "Wabbajack.RateLimiter": "0.4.3" + "Wabbajack.Paths": "0.4.4", + "Wabbajack.RateLimiter": "0.4.4" }, "runtime": { "Wabbajack.Hashing.xxHash64.dll": {} } }, - "Wabbajack.Installer/0.4.3": { + "Wabbajack.Installer/0.4.4": { "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.4.3", - "Wabbajack.Downloaders.Dispatcher": "0.4.3", - "Wabbajack.Downloaders.GameFile": "0.4.3", - "Wabbajack.FileExtractor": "0.4.3", - "Wabbajack.Networking.WabbajackClientApi": "0.4.3", - "Wabbajack.Paths": "0.4.3", - "Wabbajack.Paths.IO": "0.4.3", - "Wabbajack.VFS": "0.4.3", + "Wabbajack.DTOs": "0.4.4", + "Wabbajack.Downloaders.Dispatcher": "0.4.4", + "Wabbajack.Downloaders.GameFile": "0.4.4", + "Wabbajack.FileExtractor": "0.4.4", + "Wabbajack.Networking.WabbajackClientApi": "0.4.4", + "Wabbajack.Paths": "0.4.4", + "Wabbajack.Paths.IO": "0.4.4", + "Wabbajack.VFS": "0.4.4", "ini-parser-netstandard": "2.5.2" }, "runtime": { "Wabbajack.Installer.dll": {} } }, - "Wabbajack.IO.Async/0.4.3": { + "Wabbajack.IO.Async/0.4.4": { "runtime": { "Wabbajack.IO.Async.dll": {} } }, - "Wabbajack.Networking.BethesdaNet/0.4.3": { + "Wabbajack.Networking.BethesdaNet/0.4.4": { "dependencies": { "Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", - "Wabbajack.DTOs": "0.4.3", - "Wabbajack.Networking.Http": "0.4.3", - "Wabbajack.Networking.Http.Interfaces": "0.4.3" + "Wabbajack.DTOs": "0.4.4", + "Wabbajack.Networking.Http": "0.4.4", + "Wabbajack.Networking.Http.Interfaces": "0.4.4" }, "runtime": { "Wabbajack.Networking.BethesdaNet.dll": {} } }, - "Wabbajack.Networking.Discord/0.4.3": { + "Wabbajack.Networking.Discord/0.4.4": { "dependencies": { "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.Logging.Abstractions": "9.0.1", - "Wabbajack.Networking.Http.Interfaces": "0.4.3" + "Wabbajack.Networking.Http.Interfaces": "0.4.4" }, "runtime": { "Wabbajack.Networking.Discord.dll": {} } }, - "Wabbajack.Networking.GitHub/0.4.3": { + "Wabbajack.Networking.GitHub/0.4.4": { "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.4.3", - "Wabbajack.Networking.Http.Interfaces": "0.4.3" + "Wabbajack.DTOs": "0.4.4", + "Wabbajack.Networking.Http.Interfaces": "0.4.4" }, "runtime": { "Wabbajack.Networking.GitHub.dll": {} } }, - "Wabbajack.Networking.Http/0.4.3": { + "Wabbajack.Networking.Http/0.4.4": { "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.4.3", - "Wabbajack.Downloaders.Interfaces": "0.4.3", - "Wabbajack.Hashing.xxHash64": "0.4.3", - "Wabbajack.Networking.Http.Interfaces": "0.4.3", - "Wabbajack.Paths": "0.4.3", - "Wabbajack.Paths.IO": "0.4.3" + "Wabbajack.Configuration": "0.4.4", + "Wabbajack.Downloaders.Interfaces": "0.4.4", + "Wabbajack.Hashing.xxHash64": "0.4.4", + "Wabbajack.Networking.Http.Interfaces": "0.4.4", + "Wabbajack.Paths": "0.4.4", + "Wabbajack.Paths.IO": "0.4.4" }, "runtime": { "Wabbajack.Networking.Http.dll": {} } }, - "Wabbajack.Networking.Http.Interfaces/0.4.3": { + "Wabbajack.Networking.Http.Interfaces/0.4.4": { "dependencies": { - "Wabbajack.Hashing.xxHash64": "0.4.3" + "Wabbajack.Hashing.xxHash64": "0.4.4" }, "runtime": { "Wabbajack.Networking.Http.Interfaces.dll": {} } }, - "Wabbajack.Networking.NexusApi/0.4.3": { + "Wabbajack.Networking.NexusApi/0.4.4": { "dependencies": { "Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.Logging.Abstractions": "9.0.1", - "Wabbajack.DTOs": "0.4.3", - "Wabbajack.Networking.Http": "0.4.3", - "Wabbajack.Networking.Http.Interfaces": "0.4.3", - "Wabbajack.Networking.WabbajackClientApi": "0.4.3" + "Wabbajack.DTOs": "0.4.4", + "Wabbajack.Networking.Http": "0.4.4", + "Wabbajack.Networking.Http.Interfaces": "0.4.4", + "Wabbajack.Networking.WabbajackClientApi": "0.4.4" }, "runtime": { "Wabbajack.Networking.NexusApi.dll": {} } }, - "Wabbajack.Networking.WabbajackClientApi/0.4.3": { + "Wabbajack.Networking.WabbajackClientApi/0.4.4": { "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.4.3", - "Wabbajack.DTOs": "0.4.3", - "Wabbajack.Paths.IO": "0.4.3", - "Wabbajack.VFS.Interfaces": "0.4.3", + "Wabbajack.Common": "0.4.4", + "Wabbajack.DTOs": "0.4.4", + "Wabbajack.Paths.IO": "0.4.4", + "Wabbajack.VFS.Interfaces": "0.4.4", "YamlDotNet": "16.3.0" }, "runtime": { "Wabbajack.Networking.WabbajackClientApi.dll": {} } }, - "Wabbajack.Paths/0.4.3": { + "Wabbajack.Paths/0.4.4": { "runtime": { "Wabbajack.Paths.dll": {} } }, - "Wabbajack.Paths.IO/0.4.3": { + "Wabbajack.Paths.IO/0.4.4": { "dependencies": { - "Wabbajack.Paths": "0.4.3", + "Wabbajack.Paths": "0.4.4", "shortid": "4.0.0" }, "runtime": { "Wabbajack.Paths.IO.dll": {} } }, - "Wabbajack.RateLimiter/0.4.3": { + "Wabbajack.RateLimiter/0.4.4": { "runtime": { "Wabbajack.RateLimiter.dll": {} } }, - "Wabbajack.Server.Lib/0.4.3": { + "Wabbajack.Server.Lib/0.4.4": { "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.4.3", - "Wabbajack.Networking.Http.Interfaces": "0.4.3", - "Wabbajack.Services.OSIntegrated": "0.4.3" + "Wabbajack.Common": "0.4.4", + "Wabbajack.Networking.Http.Interfaces": "0.4.4", + "Wabbajack.Services.OSIntegrated": "0.4.4" }, "runtime": { "Wabbajack.Server.Lib.dll": {} } }, - "Wabbajack.Services.OSIntegrated/0.4.3": { + "Wabbajack.Services.OSIntegrated/0.4.4": { "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.4.3", - "Wabbajack.Downloaders.Dispatcher": "0.4.3", - "Wabbajack.Installer": "0.4.3", - "Wabbajack.Networking.BethesdaNet": "0.4.3", - "Wabbajack.Networking.Discord": "0.4.3", - "Wabbajack.VFS": "0.4.3" + "Wabbajack.Compiler": "0.4.4", + "Wabbajack.Downloaders.Dispatcher": "0.4.4", + "Wabbajack.Installer": "0.4.4", + "Wabbajack.Networking.BethesdaNet": "0.4.4", + "Wabbajack.Networking.Discord": "0.4.4", + "Wabbajack.VFS": "0.4.4" }, "runtime": { "Wabbajack.Services.OSIntegrated.dll": {} } }, - "Wabbajack.VFS/0.4.3": { + "Wabbajack.VFS/0.4.4": { "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.4.3", - "Wabbajack.FileExtractor": "0.4.3", - "Wabbajack.Hashing.PHash": "0.4.3", - "Wabbajack.Hashing.xxHash64": "0.4.3", - "Wabbajack.Paths": "0.4.3", - "Wabbajack.Paths.IO": "0.4.3", - "Wabbajack.VFS.Interfaces": "0.4.3" + "Wabbajack.Common": "0.4.4", + "Wabbajack.FileExtractor": "0.4.4", + "Wabbajack.Hashing.PHash": "0.4.4", + "Wabbajack.Hashing.xxHash64": "0.4.4", + "Wabbajack.Paths": "0.4.4", + "Wabbajack.Paths.IO": "0.4.4", + "Wabbajack.VFS.Interfaces": "0.4.4" }, "runtime": { "Wabbajack.VFS.dll": {} } }, - "Wabbajack.VFS.Interfaces/0.4.3": { + "Wabbajack.VFS.Interfaces/0.4.4": { "dependencies": { "Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", - "Wabbajack.DTOs": "0.4.3", - "Wabbajack.Hashing.xxHash64": "0.4.3", - "Wabbajack.Paths": "0.4.3" + "Wabbajack.DTOs": "0.4.4", + "Wabbajack.Hashing.xxHash64": "0.4.4", + "Wabbajack.Paths": "0.4.4" }, "runtime": { "Wabbajack.VFS.Interfaces.dll": {} @@ -2332,7 +2332,7 @@ } }, "libraries": { - "jackify-engine/0.4.3": { + "jackify-engine/0.4.4": { "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.4.3": { + "Wabbajack.CLI.Builder/0.4.4": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.Common/0.4.3": { + "Wabbajack.Common/0.4.4": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.Compiler/0.4.3": { + "Wabbajack.Compiler/0.4.4": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.Compression.BSA/0.4.3": { + "Wabbajack.Compression.BSA/0.4.4": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.Compression.Zip/0.4.3": { + "Wabbajack.Compression.Zip/0.4.4": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.Configuration/0.4.3": { + "Wabbajack.Configuration/0.4.4": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.Downloaders.Bethesda/0.4.3": { + "Wabbajack.Downloaders.Bethesda/0.4.4": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.Downloaders.Dispatcher/0.4.3": { + "Wabbajack.Downloaders.Dispatcher/0.4.4": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.Downloaders.GameFile/0.4.3": { + "Wabbajack.Downloaders.GameFile/0.4.4": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.Downloaders.GoogleDrive/0.4.3": { + "Wabbajack.Downloaders.GoogleDrive/0.4.4": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.Downloaders.Http/0.4.3": { + "Wabbajack.Downloaders.Http/0.4.4": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.Downloaders.Interfaces/0.4.3": { + "Wabbajack.Downloaders.Interfaces/0.4.4": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.Downloaders.IPS4OAuth2Downloader/0.4.3": { + "Wabbajack.Downloaders.IPS4OAuth2Downloader/0.4.4": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.Downloaders.Manual/0.4.3": { + "Wabbajack.Downloaders.Manual/0.4.4": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.Downloaders.MediaFire/0.4.3": { + "Wabbajack.Downloaders.MediaFire/0.4.4": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.Downloaders.Mega/0.4.3": { + "Wabbajack.Downloaders.Mega/0.4.4": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.Downloaders.ModDB/0.4.3": { + "Wabbajack.Downloaders.ModDB/0.4.4": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.Downloaders.Nexus/0.4.3": { + "Wabbajack.Downloaders.Nexus/0.4.4": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.Downloaders.VerificationCache/0.4.3": { + "Wabbajack.Downloaders.VerificationCache/0.4.4": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.Downloaders.WabbajackCDN/0.4.3": { + "Wabbajack.Downloaders.WabbajackCDN/0.4.4": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.DTOs/0.4.3": { + "Wabbajack.DTOs/0.4.4": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.FileExtractor/0.4.3": { + "Wabbajack.FileExtractor/0.4.4": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.Hashing.PHash/0.4.3": { + "Wabbajack.Hashing.PHash/0.4.4": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.Hashing.xxHash64/0.4.3": { + "Wabbajack.Hashing.xxHash64/0.4.4": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.Installer/0.4.3": { + "Wabbajack.Installer/0.4.4": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.IO.Async/0.4.3": { + "Wabbajack.IO.Async/0.4.4": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.Networking.BethesdaNet/0.4.3": { + "Wabbajack.Networking.BethesdaNet/0.4.4": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.Networking.Discord/0.4.3": { + "Wabbajack.Networking.Discord/0.4.4": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.Networking.GitHub/0.4.3": { + "Wabbajack.Networking.GitHub/0.4.4": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.Networking.Http/0.4.3": { + "Wabbajack.Networking.Http/0.4.4": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.Networking.Http.Interfaces/0.4.3": { + "Wabbajack.Networking.Http.Interfaces/0.4.4": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.Networking.NexusApi/0.4.3": { + "Wabbajack.Networking.NexusApi/0.4.4": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.Networking.WabbajackClientApi/0.4.3": { + "Wabbajack.Networking.WabbajackClientApi/0.4.4": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.Paths/0.4.3": { + "Wabbajack.Paths/0.4.4": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.Paths.IO/0.4.3": { + "Wabbajack.Paths.IO/0.4.4": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.RateLimiter/0.4.3": { + "Wabbajack.RateLimiter/0.4.4": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.Server.Lib/0.4.3": { + "Wabbajack.Server.Lib/0.4.4": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.Services.OSIntegrated/0.4.3": { + "Wabbajack.Services.OSIntegrated/0.4.4": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.VFS/0.4.3": { + "Wabbajack.VFS/0.4.4": { "type": "project", "serviceable": false, "sha512": "" }, - "Wabbajack.VFS.Interfaces/0.4.3": { + "Wabbajack.VFS.Interfaces/0.4.4": { "type": "project", "serviceable": false, "sha512": "" diff --git a/jackify/engine/jackify-engine.dll b/jackify/engine/jackify-engine.dll index b06e9c6..7a39610 100644 Binary files a/jackify/engine/jackify-engine.dll and b/jackify/engine/jackify-engine.dll differ diff --git a/jackify/frontends/gui/main.py b/jackify/frontends/gui/main.py index fa0d350..9bb53d4 100644 --- a/jackify/frontends/gui/main.py +++ b/jackify/frontends/gui/main.py @@ -904,20 +904,22 @@ class SettingsDialog(QDialog): if best_proton: resolved_install_path = str(best_proton['path']) resolved_install_version = best_proton['name'] + self.config_handler.set("proton_path", resolved_install_path) + self.config_handler.set("proton_version", resolved_install_version) else: - resolved_install_path = "auto" - resolved_install_version = "auto" - except: - resolved_install_path = "auto" - resolved_install_version = "auto" + # No Proton found - don't write anything, let engine auto-detect + logger.warning("Auto Proton selection failed: No Proton versions found") + # Don't modify existing config values + except Exception as e: + # Exception during detection - log it and don't write anything + logger.error(f"Auto Proton selection failed with exception: {e}", exc_info=True) + # Don't modify existing config values else: # 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() - - self.config_handler.set("proton_path", resolved_install_path) - self.config_handler.set("proton_version", resolved_install_version) + 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() @@ -1038,6 +1040,10 @@ class JackifyMainWindow(QMainWindow): self._details_extra_height = 360 self._initial_show_adjusted = False + # Track open dialogs to prevent duplicates + self._settings_dialog = None + self._about_dialog = None + # Ensure GNOME/Ubuntu exposes full set of window controls (avoid hidden buttons) self._apply_standard_window_flags() try: @@ -1606,23 +1612,74 @@ class JackifyMainWindow(QMainWindow): event.accept() def open_settings_dialog(self): + """Open settings dialog, preventing duplicate instances""" try: + # Check if dialog already exists and is visible + if self._settings_dialog is not None: + try: + if self._settings_dialog.isVisible(): + # Dialog is already open - raise it to front + self._settings_dialog.raise_() + self._settings_dialog.activateWindow() + return + else: + # Dialog exists but is closed - clean up reference + self._settings_dialog = None + except RuntimeError: + # Dialog was deleted - clean up reference + self._settings_dialog = None + + # Create new dialog dlg = SettingsDialog(self) + self._settings_dialog = dlg + + # Clean up reference when dialog is closed + def on_dialog_finished(): + self._settings_dialog = None + + dlg.finished.connect(on_dialog_finished) dlg.exec() except Exception as e: print(f"[ERROR] Exception in open_settings_dialog: {e}") import traceback traceback.print_exc() + self._settings_dialog = None def open_about_dialog(self): + """Open about dialog, preventing duplicate instances""" try: from jackify.frontends.gui.dialogs.about_dialog import AboutDialog + + # Check if dialog already exists and is visible + if self._about_dialog is not None: + try: + if self._about_dialog.isVisible(): + # Dialog is already open - raise it to front + self._about_dialog.raise_() + self._about_dialog.activateWindow() + return + else: + # Dialog exists but is closed - clean up reference + self._about_dialog = None + except RuntimeError: + # Dialog was deleted - clean up reference + self._about_dialog = None + + # Create new dialog dlg = AboutDialog(self.system_info, self) + self._about_dialog = dlg + + # Clean up reference when dialog is closed + def on_dialog_finished(): + self._about_dialog = None + + dlg.finished.connect(on_dialog_finished) dlg.exec() except Exception as e: print(f"[ERROR] Exception in open_about_dialog: {e}") import traceback traceback.print_exc() + self._about_dialog = None def _open_url(self, url: str): """Open URL with clean environment to avoid AppImage library conflicts.""" diff --git a/jackify/frontends/gui/screens/modlist_gallery.py b/jackify/frontends/gui/screens/modlist_gallery.py index 6c7ebdf..57e5533 100644 --- a/jackify/frontends/gui/screens/modlist_gallery.py +++ b/jackify/frontends/gui/screens/modlist_gallery.py @@ -833,6 +833,8 @@ class ModlistGalleryDialog(QDialog): self._validation_update_timer = None # Timer for background validation updates self._setup_ui() + # Disable filter controls during initial load to prevent race conditions + self._set_filter_controls_enabled(False) # Lazy load - fetch modlists when dialog is shown def _apply_initial_size(self): @@ -1168,6 +1170,9 @@ class ModlistGalleryDialog(QDialog): # Reconnect filter handler self.game_combo.currentIndexChanged.connect(self._apply_filters) + # Enable filter controls now that data is loaded + self._set_filter_controls_enabled(True) + # Apply filters (will show all modlists for selected game initially) self._apply_filters() @@ -1389,8 +1394,23 @@ class ModlistGalleryDialog(QDialog): self._filter_mods_list() # Refresh mod list based on NSFW state self._apply_filters() # Apply all filters + def _set_filter_controls_enabled(self, enabled: bool): + """Enable or disable all filter controls""" + self.search_box.setEnabled(enabled) + self.game_combo.setEnabled(enabled) + self.show_official_only.setEnabled(enabled) + self.show_nsfw.setEnabled(enabled) + self.hide_unavailable.setEnabled(enabled) + self.tags_list.setEnabled(enabled) + self.mod_search.setEnabled(enabled) + self.mods_list.setEnabled(enabled) + def _apply_filters(self): """Apply current filters to modlist display""" + # CRITICAL: Guard against race condition - don't filter if modlists aren't loaded yet + if not self.all_modlists: + return + filtered = self.all_modlists # Search filter @@ -1480,15 +1500,25 @@ class ModlistGalleryDialog(QDialog): def _update_grid(self): """Update grid by removing all cards and re-adding only visible ones""" + # CRITICAL: Guard against race condition - don't update if cards aren't ready yet + if not self.all_cards: + return + # Disable updates during grid update self.grid_widget.setUpdatesEnabled(False) try: # Remove all cards from layout + # CRITICAL FIX: Properly remove widgets to prevent overlapping and orphaned windows + # We need to explicitly remove widgets from the layout before taking items + # to ensure they're fully cleaned up, but we don't setParent(None) because + # widgets are immediately re-added to the grid (Qt will reparent them). while self.grid_layout.count(): item = self.grid_layout.takeAt(0) - if item.widget(): - item.widget().setParent(None) + widget = item.widget() if item else None + if widget: + # Explicitly remove widget from layout to prevent overlapping + self.grid_layout.removeWidget(widget) del item # Calculate number of columns based on available width @@ -1528,6 +1558,16 @@ class ModlistGalleryDialog(QDialog): card = self.all_cards.get(modlist.machineURL) if card: + # Ensure widget is not already in the layout (prevent overlapping) + # If it is, remove it first (shouldn't happen after takeAt, but safety check) + if card.parent() == self.grid_widget: + # Widget is already a child of grid_widget, check if it's in layout + for i in range(self.grid_layout.count()): + item = self.grid_layout.itemAt(i) + if item and item.widget() == card: + # Already in layout, remove it first + self.grid_layout.removeWidget(card) + break self.grid_layout.addWidget(card, row, col) # Set column stretch - don't stretch card columns, but add a spacer column diff --git a/jackify/frontends/gui/widgets/file_progress_list.py b/jackify/frontends/gui/widgets/file_progress_list.py index c077c2b..31fc0e1 100644 --- a/jackify/frontends/gui/widgets/file_progress_list.py +++ b/jackify/frontends/gui/widgets/file_progress_list.py @@ -20,6 +20,13 @@ from PySide6.QtGui import QFont from jackify.shared.progress_models import FileProgress, OperationType from ..shared_theme import JACKIFY_COLOR_BLUE +def _debug_log(message): + """Log message only if debug mode is enabled""" + from jackify.backend.handlers.config_handler import ConfigHandler + config_handler = ConfigHandler() + if config_handler.get('debug_mode', False): + print(message) + class SummaryProgressWidget(QWidget): """Widget showing summary progress for phases like Installing.""" @@ -484,7 +491,28 @@ class FileProgressList(QWidget): return # Widget doesn't exist - create it (only clear when creating new widget) + # CRITICAL FIX: Remove all item widgets before clear() to prevent orphaned widgets + _debug_log(f"[WIDGET_FIX] About to clear list_widget for summary widget - count={self.list_widget.count()}") + for i in range(self.list_widget.count()): + item = self.list_widget.item(i) + if item: + widget = self.list_widget.itemWidget(item) + if widget: + _debug_log(f"[WIDGET_FIX] Removing widget before clear (summary) - widget={widget}, parent={widget.parent()}, isWindow()={widget.isWindow()}") + self.list_widget.removeItemWidget(item) + if widget.isWindow(): + print(f"[WIDGET_FIX] ERROR: Widget became top-level window after removeItemWidget() before clear()!") + import traceback + traceback.print_stack() self.list_widget.clear() + # Check widgets in _file_items dict after clear + for key, widget in list(self._file_items.items()): + if widget: + _debug_log(f"[WIDGET_FIX] Widget in _file_items after clear - key={key}, widget={widget}, parent={widget.parent()}, isWindow()={widget.isWindow()}") + if widget.isWindow(): + print(f"[WIDGET_FIX] ERROR: Widget in _file_items is a top-level window after clear()! This is the bug!") + import traceback + traceback.print_stack() self._file_items.clear() # Create new summary widget @@ -510,7 +538,22 @@ class FileProgressList(QWidget): for i in range(self.list_widget.count()): item = self.list_widget.item(i) if item and item.data(Qt.UserRole) == "__summary__": + # CRITICAL FIX: Call removeItemWidget() before takeItem() to prevent orphaned widgets + widget = self.list_widget.itemWidget(item) + if widget: + _debug_log(f"[WIDGET_FIX] Removing summary widget - widget={widget}, parent={widget.parent()}, isWindow()={widget.isWindow()}") + self.list_widget.removeItemWidget(item) + if widget.isWindow(): + print(f"[WIDGET_FIX] ERROR: Summary widget became top-level window after removeItemWidget()!") + import traceback + traceback.print_stack() self.list_widget.takeItem(i) + if widget: + _debug_log(f"[WIDGET_FIX] After takeItem (summary) - widget.parent()={widget.parent()}, isWindow()={widget.isWindow()}") + if widget.isWindow(): + print(f"[WIDGET_FIX] ERROR: Summary widget is still a top-level window after takeItem()!") + import traceback + traceback.print_stack() break self._summary_widget = None else: @@ -522,7 +565,22 @@ class FileProgressList(QWidget): for i in range(self.list_widget.count()): item = self.list_widget.item(i) if item and item.data(Qt.UserRole) == "__transition__": + # CRITICAL FIX: Call removeItemWidget() before takeItem() to prevent orphaned widgets + widget = self.list_widget.itemWidget(item) + if widget: + _debug_log(f"[WIDGET_FIX] Removing transition label - widget={widget}, parent={widget.parent()}, isWindow()={widget.isWindow()}") + self.list_widget.removeItemWidget(item) + if widget.isWindow(): + print(f"[WIDGET_FIX] ERROR: Transition label became top-level window after removeItemWidget()!") + import traceback + traceback.print_stack() self.list_widget.takeItem(i) + if widget: + _debug_log(f"[WIDGET_FIX] After takeItem (transition) - widget.parent()={widget.parent()}, isWindow()={widget.isWindow()}") + if widget.isWindow(): + print(f"[WIDGET_FIX] ERROR: Transition label is still a top-level window after takeItem()!") + import traceback + traceback.print_stack() break self._transition_label = None @@ -533,7 +591,28 @@ class FileProgressList(QWidget): self._show_transition_message(current_phase) else: # Show empty state but keep header stable + # CRITICAL FIX: Remove all item widgets before clear() to prevent orphaned widgets + _debug_log(f"[WIDGET_FIX] About to clear list_widget (empty state) - count={self.list_widget.count()}") + for i in range(self.list_widget.count()): + item = self.list_widget.item(i) + if item: + widget = self.list_widget.itemWidget(item) + if widget: + _debug_log(f"[WIDGET_FIX] Removing widget before clear (empty) - widget={widget}, parent={widget.parent()}, isWindow()={widget.isWindow()}") + self.list_widget.removeItemWidget(item) + if widget.isWindow(): + print(f"[WIDGET_FIX] ERROR: Widget became top-level window after removeItemWidget() before clear()!") + import traceback + traceback.print_stack() self.list_widget.clear() + # Check widgets in _file_items dict after clear + for key, widget in list(self._file_items.items()): + if widget: + _debug_log(f"[WIDGET_FIX] Widget in _file_items after clear (empty) - key={key}, widget={widget}, parent={widget.parent()}, isWindow()={widget.isWindow()}") + if widget.isWindow(): + print(f"[WIDGET_FIX] ERROR: Widget in _file_items is a top-level window after clear()! This is the bug!") + import traceback + traceback.print_stack() self._file_items.clear() # Update last phase tracker @@ -579,7 +658,24 @@ class FileProgressList(QWidget): for i in range(self.list_widget.count()): item = self.list_widget.item(i) if item and item.data(Qt.UserRole) == item_key: + # CRITICAL FIX: Call removeItemWidget() before takeItem() to prevent orphaned widgets + widget = self.list_widget.itemWidget(item) + if widget: + _debug_log(f"[WIDGET_FIX] Removing widget for item_key={item_key} - widget={widget}, parent={widget.parent()}, isWindow()={widget.isWindow()}") + self.list_widget.removeItemWidget(item) + # Check if widget became orphaned after removal + if widget.isWindow(): + print(f"[WIDGET_FIX] ERROR: Widget became top-level window after removeItemWidget()! widget={widget}") + import traceback + traceback.print_stack() self.list_widget.takeItem(i) + # Final check after takeItem + if widget: + _debug_log(f"[WIDGET_FIX] After takeItem - widget.parent()={widget.parent()}, isWindow()={widget.isWindow()}") + if widget.isWindow(): + print(f"[WIDGET_FIX] ERROR: Widget is still a top-level window after takeItem()! This is the bug!") + import traceback + traceback.print_stack() break del self._file_items[item_key] @@ -638,7 +734,28 @@ class FileProgressList(QWidget): def _show_transition_message(self, new_phase: str): """Show a brief 'Preparing...' message during phase transitions.""" + # CRITICAL FIX: Remove all item widgets before clear() to prevent orphaned widgets + _debug_log(f"[WIDGET_FIX] About to clear list_widget (transition) - count={self.list_widget.count()}") + for i in range(self.list_widget.count()): + item = self.list_widget.item(i) + if item: + widget = self.list_widget.itemWidget(item) + if widget: + _debug_log(f"[WIDGET_FIX] Removing widget before clear (transition) - widget={widget}, parent={widget.parent()}, isWindow()={widget.isWindow()}") + self.list_widget.removeItemWidget(item) + if widget.isWindow(): + print(f"[WIDGET_FIX] ERROR: Widget became top-level window after removeItemWidget() before clear()!") + import traceback + traceback.print_stack() self.list_widget.clear() + # Check widgets in _file_items dict after clear + for key, widget in list(self._file_items.items()): + if widget: + _debug_log(f"[WIDGET_FIX] Widget in _file_items after clear (transition) - key={key}, widget={widget}, parent={widget.parent()}, isWindow()={widget.isWindow()}") + if widget.isWindow(): + print(f"[WIDGET_FIX] ERROR: Widget in _file_items is a top-level window after clear()! This is the bug!") + import traceback + traceback.print_stack() self._file_items.clear() # Header removed - tab label provides context @@ -663,9 +780,42 @@ class FileProgressList(QWidget): def clear(self): """Clear all file items.""" + # CRITICAL FIX: Remove all item widgets before clear() to prevent orphaned widgets + _debug_log(f"[WIDGET_FIX] clear() called - count={self.list_widget.count()}") + for i in range(self.list_widget.count()): + item = self.list_widget.item(i) + if item: + widget = self.list_widget.itemWidget(item) + if widget: + _debug_log(f"[WIDGET_FIX] Removing widget before clear() - widget={widget}, parent={widget.parent()}, isWindow()={widget.isWindow()}") + self.list_widget.removeItemWidget(item) + if widget.isWindow(): + print(f"[WIDGET_FIX] ERROR: Widget became top-level window after removeItemWidget() before clear()!") + import traceback + traceback.print_stack() self.list_widget.clear() + # Check widgets in _file_items dict after clear + for key, widget in list(self._file_items.items()): + if widget: + _debug_log(f"[WIDGET_FIX] Widget in _file_items after clear() - key={key}, widget={widget}, parent={widget.parent()}, isWindow()={widget.isWindow()}") + if widget.isWindow(): + print(f"[WIDGET_FIX] ERROR: Widget in _file_items is a top-level window after clear()! This is the bug!") + import traceback + traceback.print_stack() self._file_items.clear() + if self._summary_widget: + _debug_log(f"[WIDGET_FIX] Clearing summary_widget - widget={self._summary_widget}, parent={self._summary_widget.parent()}, isWindow()={self._summary_widget.isWindow()}") + if self._summary_widget.isWindow(): + print(f"[WIDGET_FIX] ERROR: Summary widget is a top-level window in clear()!") + import traceback + traceback.print_stack() self._summary_widget = None + if self._transition_label: + _debug_log(f"[WIDGET_FIX] Clearing transition_label - widget={self._transition_label}, parent={self._transition_label.parent()}, isWindow()={self._transition_label.isWindow()}") + if self._transition_label.isWindow(): + print(f"[WIDGET_FIX] ERROR: Transition label is a top-level window in clear()!") + import traceback + traceback.print_stack() self._transition_label = None self._last_phase = None # Header removed - tab label provides context