Sync from development - prepare for v0.1.5.2

This commit is contained in:
Omni
2025-10-01 22:11:14 +01:00
parent 8661f8963e
commit 80914bc76f
56 changed files with 666 additions and 524 deletions

2
.gitignore vendored
View File

@@ -35,7 +35,7 @@ Thumbs.db
docs/ docs/
testing/ testing/
# PyInstaller build files (development only) # Build files (development only)
*.spec *.spec
hook-*.py hook-*.py
requirements-packaging.txt requirements-packaging.txt

View File

@@ -1,5 +1,26 @@
# Jackify Changelog # Jackify Changelog
## v0.1.5.2 - Proton Configuration & Engine Updates
**Release Date:** September 30, 2025
### Critical Bug Fixes
- **Fixed Proton Version Selection**: Wine component installation now properly honors user-selected Proton version from Settings dialog
- Previously, changing from GE-Proton to Proton Experimental in settings would still use the old version for component installation
- Fixed ConfigHandler to reload fresh configuration from disk instead of using stale cache
- Updated all Proton path retrieval across codebase to use fresh-reading methods
### Engine Updates
- **jackify-engine v0.3.16**: Updated to latest engine version with important reliability improvements
- **Sanity Check Fallback**: Added Proton 7z.exe fallback for case sensitivity extraction failures
- **Enhanced Error Messages**: Improved texconv/texdiag error messages to include original texture file names and conversion parameters
### Technical Improvements
- Enhanced configuration system reliability for multi-instance scenarios
- Improved error diagnostics for texture processing operations
- Fix Qt platform plugin discovery in AppImage distribution for improved compatibility
---
## v0.1.5.1 - Bug Fixes ## v0.1.5.1 - Bug Fixes
**Release Date:** September 28, 2025 **Release Date:** September 28, 2025

View File

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

View File

@@ -30,7 +30,7 @@ def _get_user_proton_version():
from jackify.backend.handlers.wine_utils import WineUtils from jackify.backend.handlers.wine_utils import WineUtils
config_handler = ConfigHandler() config_handler = ConfigHandler()
user_proton_path = config_handler.get('proton_path', 'auto') user_proton_path = config_handler.get_proton_path()
if user_proton_path == 'auto': if user_proton_path == 'auto':
# Use enhanced fallback logic with GE-Proton preference # Use enhanced fallback logic with GE-Proton preference

View File

@@ -496,6 +496,42 @@ class ConfigHandler:
logger.error(f"Error saving modlist downloads base directory: {e}") logger.error(f"Error saving modlist downloads base directory: {e}")
return False return False
def get_proton_path(self):
"""
Retrieve the saved Proton path from configuration
Always reads fresh from disk to pick up changes from Settings dialog
Returns:
str: Saved Proton path or 'auto' if not saved
"""
try:
# Reload config from disk to pick up changes from Settings dialog
self._load_config()
proton_path = self.settings.get("proton_path", "auto")
logger.debug(f"Retrieved fresh proton_path from config: {proton_path}")
return proton_path
except Exception as e:
logger.error(f"Error retrieving proton_path: {e}")
return "auto"
def get_proton_version(self):
"""
Retrieve the saved Proton version from configuration
Always reads fresh from disk to pick up changes from Settings dialog
Returns:
str: Saved Proton version or 'auto' if not saved
"""
try:
# Reload config from disk to pick up changes from Settings dialog
self._load_config()
proton_version = self.settings.get("proton_version", "auto")
logger.debug(f"Retrieved fresh proton_version from config: {proton_version}")
return proton_version
except Exception as e:
logger.error(f"Error retrieving proton_version: {e}")
return "auto"
def _auto_detect_proton(self): def _auto_detect_proton(self):
"""Auto-detect and set best Proton version (includes GE-Proton and Valve Proton)""" """Auto-detect and set best Proton version (includes GE-Proton and Valve Proton)"""
try: try:

View File

@@ -692,10 +692,32 @@ class ModlistHandler:
print("Error: Could not determine wine prefix location.") print("Error: Could not determine wine prefix location.")
return False return False
if not self.winetricks_handler.install_wine_components(wineprefix, self.game_var_full, specific_components=components): # Try winetricks first (preferred method with current fix)
self.logger.error("Failed to install Wine components. Configuration aborted.") winetricks_success = False
print("Error: Failed to install necessary Wine components.") try:
return False # Abort on failure self.logger.info("Attempting Wine component installation using winetricks...")
winetricks_success = self.winetricks_handler.install_wine_components(wineprefix, self.game_var_full, specific_components=components)
if winetricks_success:
self.logger.info("Winetricks installation completed successfully")
except Exception as e:
self.logger.warning(f"Winetricks installation failed with exception: {e}")
winetricks_success = False
# Fallback to protontricks if winetricks failed
if not winetricks_success:
self.logger.warning("Winetricks failed, falling back to protontricks for Wine component installation...")
try:
protontricks_success = self.protontricks_handler.install_wine_components(target_appid, self.game_var_full, specific_components=components)
if protontricks_success:
self.logger.info("Protontricks fallback installation completed successfully")
else:
self.logger.error("Both winetricks and protontricks failed to install Wine components.")
print("Error: Failed to install necessary Wine components using both winetricks and protontricks.")
return False
except Exception as e:
self.logger.error(f"Protontricks fallback also failed with exception: {e}")
print("Error: Failed to install necessary Wine components using both winetricks and protontricks.")
return False
self.logger.info("Step 4: Installing Wine components... Done") self.logger.info("Step 4: Installing Wine components... Done")
# Step 5: Ensure permissions of Modlist directory # Step 5: Ensure permissions of Modlist directory

View File

@@ -630,47 +630,64 @@ class PathHandler:
# Moved _find_shortcuts_vdf here from ShortcutHandler # Moved _find_shortcuts_vdf here from ShortcutHandler
def _find_shortcuts_vdf(self) -> Optional[str]: def _find_shortcuts_vdf(self) -> Optional[str]:
"""Helper to find the active shortcuts.vdf file for a user. """Helper to find the active shortcuts.vdf file for the current Steam user.
Iterates through userdata directories and returns the path to the Uses proper multi-user detection to find the correct Steam user instead
first found shortcuts.vdf file. of just taking the first found user directory.
Returns: Returns:
Optional[str]: The full path to the shortcuts.vdf file, or None if not found. Optional[str]: The full path to the shortcuts.vdf file, or None if not found.
""" """
# This implementation was moved from ShortcutHandler try:
userdata_base_paths = [ # Use native Steam service for proper multi-user detection
os.path.expanduser("~/.steam/steam/userdata"), from jackify.backend.services.native_steam_service import NativeSteamService
os.path.expanduser("~/.local/share/Steam/userdata"), steam_service = NativeSteamService()
os.path.expanduser("~/.var/app/com.valvesoftware.Steam/.local/share/Steam/userdata") shortcuts_path = steam_service.get_shortcuts_vdf_path()
]
found_vdf_path = None if shortcuts_path:
for base_path in userdata_base_paths: logger.info(f"Found shortcuts.vdf using multi-user detection: {shortcuts_path}")
if not os.path.isdir(base_path): return str(shortcuts_path)
logger.debug(f"Userdata base path not found or not a directory: {base_path}") else:
continue logger.error("Could not determine shortcuts.vdf path using multi-user detection")
logger.debug(f"Searching for user IDs in: {base_path}") return None
try:
for item in os.listdir(base_path): except Exception as e:
user_path = os.path.join(base_path, item) logger.error(f"Error using multi-user detection for shortcuts.vdf: {e}")
if os.path.isdir(user_path) and item.isdigit():
logger.debug(f"Checking user directory: {user_path}") # Fallback to legacy behavior if multi-user detection fails
config_path = os.path.join(user_path, "config") logger.warning("Falling back to legacy shortcuts.vdf detection (first-found user)")
shortcuts_file = os.path.join(config_path, "shortcuts.vdf") userdata_base_paths = [
if os.path.isfile(shortcuts_file): os.path.expanduser("~/.steam/steam/userdata"),
logger.info(f"Found shortcuts.vdf at: {shortcuts_file}") os.path.expanduser("~/.local/share/Steam/userdata"),
found_vdf_path = shortcuts_file os.path.expanduser("~/.var/app/com.valvesoftware.Steam/.local/share/Steam/userdata")
break # Found it for this base path ]
else: found_vdf_path = None
logger.debug(f"shortcuts.vdf not found in {config_path}") for base_path in userdata_base_paths:
except OSError as e: if not os.path.isdir(base_path):
logger.warning(f"Could not access directory {base_path}: {e}") logger.debug(f"Userdata base path not found or not a directory: {base_path}")
continue # Try next base path continue
if found_vdf_path: logger.debug(f"Searching for user IDs in: {base_path}")
break # Found it in this base path try:
if not found_vdf_path: for item in os.listdir(base_path):
logger.error("Could not find any shortcuts.vdf file in common Steam locations.") user_path = os.path.join(base_path, item)
return found_vdf_path if os.path.isdir(user_path) and item.isdigit():
logger.debug(f"Checking user directory: {user_path}")
config_path = os.path.join(user_path, "config")
shortcuts_file = os.path.join(config_path, "shortcuts.vdf")
if os.path.isfile(shortcuts_file):
logger.info(f"Found shortcuts.vdf at: {shortcuts_file}")
found_vdf_path = shortcuts_file
break # Found it for this base path
else:
logger.debug(f"shortcuts.vdf not found in {config_path}")
except OSError as e:
logger.warning(f"Could not access directory {base_path}: {e}")
continue # Try next base path
if found_vdf_path:
break # Found it in this base path
if not found_vdf_path:
logger.error("Could not find any shortcuts.vdf file in common Steam locations.")
return found_vdf_path
@staticmethod @staticmethod
def find_game_install_paths(target_appids: Dict[str, str]) -> Dict[str, Path]: def find_game_install_paths(target_appids: Dict[str, str]) -> Dict[str, Path]:

View File

@@ -222,15 +222,21 @@ class ValidationHandler:
def validate_steam_shortcut(self, app_id: str) -> Tuple[bool, str]: def validate_steam_shortcut(self, app_id: str) -> Tuple[bool, str]:
"""Validate a Steam shortcut.""" """Validate a Steam shortcut."""
try: try:
# Check if shortcuts.vdf exists # Use native Steam service to get proper shortcuts.vdf path with multi-user support
shortcuts_path = Path.home() / '.steam' / 'steam' / 'userdata' / '75424832' / 'config' / 'shortcuts.vdf' from jackify.backend.services.native_steam_service import NativeSteamService
steam_service = NativeSteamService()
shortcuts_path = steam_service.get_shortcuts_vdf_path()
if not shortcuts_path:
return False, "Could not determine shortcuts.vdf path (no active Steam user found)"
if not shortcuts_path.exists(): if not shortcuts_path.exists():
return False, "shortcuts.vdf not found" return False, "shortcuts.vdf not found"
# Check if shortcuts.vdf is accessible # Check if shortcuts.vdf is accessible
if not os.access(shortcuts_path, os.R_OK | os.W_OK): if not os.access(shortcuts_path, os.R_OK | os.W_OK):
return False, "shortcuts.vdf is not accessible" return False, "shortcuts.vdf is not accessible"
# Parse shortcuts.vdf using VDFHandler # Parse shortcuts.vdf using VDFHandler
shortcuts_data = VDFHandler.load(str(shortcuts_path), binary=True) shortcuts_data = VDFHandler.load(str(shortcuts_path), binary=True)

View File

@@ -709,7 +709,7 @@ class WineUtils:
try: try:
from .config_handler import ConfigHandler from .config_handler import ConfigHandler
config = ConfigHandler() config = ConfigHandler()
fallback_path = config.get('proton_path', 'auto') fallback_path = config.get_proton_path()
if fallback_path != 'auto': if fallback_path != 'auto':
fallback_wine_bin = Path(fallback_path) / "files/bin/wine" fallback_wine_bin = Path(fallback_path) / "files/bin/wine"
if fallback_wine_bin.is_file(): if fallback_wine_bin.is_file():

View File

@@ -137,7 +137,7 @@ class WinetricksHandler:
from ..handlers.wine_utils import WineUtils from ..handlers.wine_utils import WineUtils
config = ConfigHandler() config = ConfigHandler()
user_proton_path = config.get('proton_path', 'auto') user_proton_path = config.get_proton_path()
# If user selected a specific Proton, try that first # If user selected a specific Proton, try that first
wine_binary = None wine_binary = None
@@ -181,6 +181,49 @@ class WinetricksHandler:
env['WINE'] = str(wine_binary) env['WINE'] = str(wine_binary)
self.logger.info(f"Using Proton wine binary for winetricks: {wine_binary}") self.logger.info(f"Using Proton wine binary for winetricks: {wine_binary}")
# CRITICAL: Set up protontricks-compatible environment
proton_dist_path = os.path.dirname(os.path.dirname(wine_binary)) # e.g., /path/to/proton/dist/bin/wine -> /path/to/proton/dist
self.logger.debug(f"Proton dist path: {proton_dist_path}")
# Set WINEDLLPATH like protontricks does
env['WINEDLLPATH'] = f"{proton_dist_path}/lib64/wine:{proton_dist_path}/lib/wine"
# Ensure Proton bin directory is first in PATH
env['PATH'] = f"{proton_dist_path}/bin:{env.get('PATH', '')}"
# Set DLL overrides exactly like protontricks
dll_overrides = {
"beclient": "b,n",
"beclient_x64": "b,n",
"dxgi": "n",
"d3d9": "n",
"d3d10core": "n",
"d3d11": "n",
"d3d12": "n",
"d3d12core": "n",
"nvapi": "n",
"nvapi64": "n",
"nvofapi64": "n",
"nvcuda": "b"
}
# Merge with existing overrides
existing_overrides = env.get('WINEDLLOVERRIDES', '')
if existing_overrides:
# Parse existing overrides
for override in existing_overrides.split(';'):
if '=' in override:
name, value = override.split('=', 1)
dll_overrides[name] = value
env['WINEDLLOVERRIDES'] = ';'.join(f"{name}={setting}" for name, setting in dll_overrides.items())
# Set Wine defaults from protontricks
env['WINE_LARGE_ADDRESS_AWARE'] = '1'
env['DXVK_ENABLE_NVAPI'] = '1'
self.logger.debug(f"Set protontricks environment: WINEDLLPATH={env['WINEDLLPATH']}")
except Exception as e: except Exception as e:
self.logger.error(f"Cannot run winetricks: Failed to get Proton wine binary: {e}") self.logger.error(f"Cannot run winetricks: Failed to get Proton wine binary: {e}")
return False return False
@@ -426,7 +469,8 @@ class WinetricksHandler:
except Exception as e: except Exception as e:
self.logger.warning(f"Could not read winetricks.log: {e}") self.logger.warning(f"Could not read winetricks.log: {e}")
self.logger.error(f"{component} failed (attempt {attempt}): {result.stderr.strip()[:200]}") self.logger.error(f"{component} failed (attempt {attempt}): {result.stderr.strip()}")
self.logger.debug(f"Full stdout for {component}: {result.stdout.strip()}")
except Exception as e: except Exception as e:
self.logger.error(f"Error installing {component} (attempt {attempt}): {e}") self.logger.error(f"Error installing {component} (attempt {attempt}): {e}")

View File

@@ -46,7 +46,7 @@ class AutomatedPrefixService:
from jackify.backend.handlers.wine_utils import WineUtils from jackify.backend.handlers.wine_utils import WineUtils
config_handler = ConfigHandler() config_handler = ConfigHandler()
user_proton_path = config_handler.get('proton_path', 'auto') user_proton_path = config_handler.get_proton_path()
if user_proton_path == 'auto': if user_proton_path == 'auto':
# Use enhanced fallback logic with GE-Proton preference # Use enhanced fallback logic with GE-Proton preference
@@ -2705,7 +2705,7 @@ echo Prefix creation complete.
from jackify.backend.handlers.wine_utils import WineUtils from jackify.backend.handlers.wine_utils import WineUtils
config = ConfigHandler() config = ConfigHandler()
user_proton_path = config.get('proton_path', 'auto') user_proton_path = config.get_proton_path()
# If user selected a specific Proton, try that first # If user selected a specific Proton, try that first
if user_proton_path != 'auto': if user_proton_path != 'auto':

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -7,7 +7,7 @@
"targets": { "targets": {
".NETCoreApp,Version=v8.0": {}, ".NETCoreApp,Version=v8.0": {},
".NETCoreApp,Version=v8.0/linux-x64": { ".NETCoreApp,Version=v8.0/linux-x64": {
"jackify-engine/0.3.15": { "jackify-engine/0.3.16": {
"dependencies": { "dependencies": {
"Markdig": "0.40.0", "Markdig": "0.40.0",
"Microsoft.Extensions.Configuration.Json": "9.0.1", "Microsoft.Extensions.Configuration.Json": "9.0.1",
@@ -22,16 +22,16 @@
"SixLabors.ImageSharp": "3.1.6", "SixLabors.ImageSharp": "3.1.6",
"System.CommandLine": "2.0.0-beta4.22272.1", "System.CommandLine": "2.0.0-beta4.22272.1",
"System.CommandLine.NamingConventionBinder": "2.0.0-beta4.22272.1", "System.CommandLine.NamingConventionBinder": "2.0.0-beta4.22272.1",
"Wabbajack.CLI.Builder": "0.3.15", "Wabbajack.CLI.Builder": "0.3.16",
"Wabbajack.Downloaders.Bethesda": "0.3.15", "Wabbajack.Downloaders.Bethesda": "0.3.16",
"Wabbajack.Downloaders.Dispatcher": "0.3.15", "Wabbajack.Downloaders.Dispatcher": "0.3.16",
"Wabbajack.Hashing.xxHash64": "0.3.15", "Wabbajack.Hashing.xxHash64": "0.3.16",
"Wabbajack.Networking.Discord": "0.3.15", "Wabbajack.Networking.Discord": "0.3.16",
"Wabbajack.Networking.GitHub": "0.3.15", "Wabbajack.Networking.GitHub": "0.3.16",
"Wabbajack.Paths.IO": "0.3.15", "Wabbajack.Paths.IO": "0.3.16",
"Wabbajack.Server.Lib": "0.3.15", "Wabbajack.Server.Lib": "0.3.16",
"Wabbajack.Services.OSIntegrated": "0.3.15", "Wabbajack.Services.OSIntegrated": "0.3.16",
"Wabbajack.VFS": "0.3.15", "Wabbajack.VFS": "0.3.16",
"MegaApiClient": "1.0.0.0", "MegaApiClient": "1.0.0.0",
"runtimepack.Microsoft.NETCore.App.Runtime.linux-x64": "8.0.19" "runtimepack.Microsoft.NETCore.App.Runtime.linux-x64": "8.0.19"
}, },
@@ -1781,7 +1781,7 @@
} }
} }
}, },
"Wabbajack.CLI.Builder/0.3.15": { "Wabbajack.CLI.Builder/0.3.16": {
"dependencies": { "dependencies": {
"Microsoft.Extensions.Configuration.Json": "9.0.1", "Microsoft.Extensions.Configuration.Json": "9.0.1",
"Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection": "9.0.1",
@@ -1791,109 +1791,109 @@
"Microsoft.Extensions.Logging.Abstractions": "9.0.1", "Microsoft.Extensions.Logging.Abstractions": "9.0.1",
"System.CommandLine": "2.0.0-beta4.22272.1", "System.CommandLine": "2.0.0-beta4.22272.1",
"System.CommandLine.NamingConventionBinder": "2.0.0-beta4.22272.1", "System.CommandLine.NamingConventionBinder": "2.0.0-beta4.22272.1",
"Wabbajack.Paths": "0.3.15" "Wabbajack.Paths": "0.3.16"
}, },
"runtime": { "runtime": {
"Wabbajack.CLI.Builder.dll": {} "Wabbajack.CLI.Builder.dll": {}
} }
}, },
"Wabbajack.Common/0.3.15": { "Wabbajack.Common/0.3.16": {
"dependencies": { "dependencies": {
"Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Microsoft.Extensions.Logging.Abstractions": "9.0.1", "Microsoft.Extensions.Logging.Abstractions": "9.0.1",
"System.Reactive": "6.0.1", "System.Reactive": "6.0.1",
"Wabbajack.DTOs": "0.3.15", "Wabbajack.DTOs": "0.3.16",
"Wabbajack.Networking.Http": "0.3.15", "Wabbajack.Networking.Http": "0.3.16",
"Wabbajack.Paths.IO": "0.3.15" "Wabbajack.Paths.IO": "0.3.16"
}, },
"runtime": { "runtime": {
"Wabbajack.Common.dll": {} "Wabbajack.Common.dll": {}
} }
}, },
"Wabbajack.Compiler/0.3.15": { "Wabbajack.Compiler/0.3.16": {
"dependencies": { "dependencies": {
"F23.StringSimilarity": "6.0.0", "F23.StringSimilarity": "6.0.0",
"Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Newtonsoft.Json": "13.0.3", "Newtonsoft.Json": "13.0.3",
"SixLabors.ImageSharp": "3.1.6", "SixLabors.ImageSharp": "3.1.6",
"Wabbajack.Downloaders.Dispatcher": "0.3.15", "Wabbajack.Downloaders.Dispatcher": "0.3.16",
"Wabbajack.Installer": "0.3.15", "Wabbajack.Installer": "0.3.16",
"Wabbajack.VFS": "0.3.15", "Wabbajack.VFS": "0.3.16",
"ini-parser-netstandard": "2.5.2" "ini-parser-netstandard": "2.5.2"
}, },
"runtime": { "runtime": {
"Wabbajack.Compiler.dll": {} "Wabbajack.Compiler.dll": {}
} }
}, },
"Wabbajack.Compression.BSA/0.3.15": { "Wabbajack.Compression.BSA/0.3.16": {
"dependencies": { "dependencies": {
"K4os.Compression.LZ4.Streams": "1.3.8", "K4os.Compression.LZ4.Streams": "1.3.8",
"Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"SharpZipLib": "1.4.2", "SharpZipLib": "1.4.2",
"Wabbajack.Common": "0.3.15", "Wabbajack.Common": "0.3.16",
"Wabbajack.DTOs": "0.3.15" "Wabbajack.DTOs": "0.3.16"
}, },
"runtime": { "runtime": {
"Wabbajack.Compression.BSA.dll": {} "Wabbajack.Compression.BSA.dll": {}
} }
}, },
"Wabbajack.Compression.Zip/0.3.15": { "Wabbajack.Compression.Zip/0.3.16": {
"dependencies": { "dependencies": {
"Wabbajack.IO.Async": "0.3.15" "Wabbajack.IO.Async": "0.3.16"
}, },
"runtime": { "runtime": {
"Wabbajack.Compression.Zip.dll": {} "Wabbajack.Compression.Zip.dll": {}
} }
}, },
"Wabbajack.Configuration/0.3.15": { "Wabbajack.Configuration/0.3.16": {
"runtime": { "runtime": {
"Wabbajack.Configuration.dll": {} "Wabbajack.Configuration.dll": {}
} }
}, },
"Wabbajack.Downloaders.Bethesda/0.3.15": { "Wabbajack.Downloaders.Bethesda/0.3.16": {
"dependencies": { "dependencies": {
"LibAES-CTR": "1.1.0", "LibAES-CTR": "1.1.0",
"Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"SharpZipLib": "1.4.2", "SharpZipLib": "1.4.2",
"Wabbajack.Common": "0.3.15", "Wabbajack.Common": "0.3.16",
"Wabbajack.Downloaders.Interfaces": "0.3.15", "Wabbajack.Downloaders.Interfaces": "0.3.16",
"Wabbajack.Networking.BethesdaNet": "0.3.15" "Wabbajack.Networking.BethesdaNet": "0.3.16"
}, },
"runtime": { "runtime": {
"Wabbajack.Downloaders.Bethesda.dll": {} "Wabbajack.Downloaders.Bethesda.dll": {}
} }
}, },
"Wabbajack.Downloaders.Dispatcher/0.3.15": { "Wabbajack.Downloaders.Dispatcher/0.3.16": {
"dependencies": { "dependencies": {
"Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Microsoft.Extensions.Logging.Abstractions": "9.0.1", "Microsoft.Extensions.Logging.Abstractions": "9.0.1",
"Newtonsoft.Json": "13.0.3", "Newtonsoft.Json": "13.0.3",
"SixLabors.ImageSharp": "3.1.6", "SixLabors.ImageSharp": "3.1.6",
"Wabbajack.Downloaders.Bethesda": "0.3.15", "Wabbajack.Downloaders.Bethesda": "0.3.16",
"Wabbajack.Downloaders.GameFile": "0.3.15", "Wabbajack.Downloaders.GameFile": "0.3.16",
"Wabbajack.Downloaders.GoogleDrive": "0.3.15", "Wabbajack.Downloaders.GoogleDrive": "0.3.16",
"Wabbajack.Downloaders.Http": "0.3.15", "Wabbajack.Downloaders.Http": "0.3.16",
"Wabbajack.Downloaders.IPS4OAuth2Downloader": "0.3.15", "Wabbajack.Downloaders.IPS4OAuth2Downloader": "0.3.16",
"Wabbajack.Downloaders.Interfaces": "0.3.15", "Wabbajack.Downloaders.Interfaces": "0.3.16",
"Wabbajack.Downloaders.Manual": "0.3.15", "Wabbajack.Downloaders.Manual": "0.3.16",
"Wabbajack.Downloaders.MediaFire": "0.3.15", "Wabbajack.Downloaders.MediaFire": "0.3.16",
"Wabbajack.Downloaders.Mega": "0.3.15", "Wabbajack.Downloaders.Mega": "0.3.16",
"Wabbajack.Downloaders.ModDB": "0.3.15", "Wabbajack.Downloaders.ModDB": "0.3.16",
"Wabbajack.Downloaders.Nexus": "0.3.15", "Wabbajack.Downloaders.Nexus": "0.3.16",
"Wabbajack.Downloaders.VerificationCache": "0.3.15", "Wabbajack.Downloaders.VerificationCache": "0.3.16",
"Wabbajack.Downloaders.WabbajackCDN": "0.3.15", "Wabbajack.Downloaders.WabbajackCDN": "0.3.16",
"Wabbajack.Networking.WabbajackClientApi": "0.3.15" "Wabbajack.Networking.WabbajackClientApi": "0.3.16"
}, },
"runtime": { "runtime": {
"Wabbajack.Downloaders.Dispatcher.dll": {} "Wabbajack.Downloaders.Dispatcher.dll": {}
} }
}, },
"Wabbajack.Downloaders.GameFile/0.3.15": { "Wabbajack.Downloaders.GameFile/0.3.16": {
"dependencies": { "dependencies": {
"GameFinder.StoreHandlers.EADesktop": "4.5.0", "GameFinder.StoreHandlers.EADesktop": "4.5.0",
"GameFinder.StoreHandlers.EGS": "4.5.0", "GameFinder.StoreHandlers.EGS": "4.5.0",
@@ -1903,360 +1903,360 @@
"Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"SixLabors.ImageSharp": "3.1.6", "SixLabors.ImageSharp": "3.1.6",
"Wabbajack.Downloaders.Interfaces": "0.3.15", "Wabbajack.Downloaders.Interfaces": "0.3.16",
"Wabbajack.VFS": "0.3.15" "Wabbajack.VFS": "0.3.16"
}, },
"runtime": { "runtime": {
"Wabbajack.Downloaders.GameFile.dll": {} "Wabbajack.Downloaders.GameFile.dll": {}
} }
}, },
"Wabbajack.Downloaders.GoogleDrive/0.3.15": { "Wabbajack.Downloaders.GoogleDrive/0.3.16": {
"dependencies": { "dependencies": {
"HtmlAgilityPack": "1.11.72", "HtmlAgilityPack": "1.11.72",
"Microsoft.AspNetCore.Http.Extensions": "2.3.0", "Microsoft.AspNetCore.Http.Extensions": "2.3.0",
"Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Microsoft.Extensions.Logging.Abstractions": "9.0.1", "Microsoft.Extensions.Logging.Abstractions": "9.0.1",
"Wabbajack.Common": "0.3.15", "Wabbajack.Common": "0.3.16",
"Wabbajack.DTOs": "0.3.15", "Wabbajack.DTOs": "0.3.16",
"Wabbajack.Downloaders.Interfaces": "0.3.15", "Wabbajack.Downloaders.Interfaces": "0.3.16",
"Wabbajack.Networking.Http": "0.3.15", "Wabbajack.Networking.Http": "0.3.16",
"Wabbajack.Networking.Http.Interfaces": "0.3.15" "Wabbajack.Networking.Http.Interfaces": "0.3.16"
}, },
"runtime": { "runtime": {
"Wabbajack.Downloaders.GoogleDrive.dll": {} "Wabbajack.Downloaders.GoogleDrive.dll": {}
} }
}, },
"Wabbajack.Downloaders.Http/0.3.15": { "Wabbajack.Downloaders.Http/0.3.16": {
"dependencies": { "dependencies": {
"Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Microsoft.Extensions.Logging.Abstractions": "9.0.1", "Microsoft.Extensions.Logging.Abstractions": "9.0.1",
"Wabbajack.Common": "0.3.15", "Wabbajack.Common": "0.3.16",
"Wabbajack.DTOs": "0.3.15", "Wabbajack.DTOs": "0.3.16",
"Wabbajack.Downloaders.Interfaces": "0.3.15", "Wabbajack.Downloaders.Interfaces": "0.3.16",
"Wabbajack.Networking.BethesdaNet": "0.3.15", "Wabbajack.Networking.BethesdaNet": "0.3.16",
"Wabbajack.Networking.Http.Interfaces": "0.3.15", "Wabbajack.Networking.Http.Interfaces": "0.3.16",
"Wabbajack.Paths.IO": "0.3.15" "Wabbajack.Paths.IO": "0.3.16"
}, },
"runtime": { "runtime": {
"Wabbajack.Downloaders.Http.dll": {} "Wabbajack.Downloaders.Http.dll": {}
} }
}, },
"Wabbajack.Downloaders.Interfaces/0.3.15": { "Wabbajack.Downloaders.Interfaces/0.3.16": {
"dependencies": { "dependencies": {
"Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Wabbajack.Compression.Zip": "0.3.15", "Wabbajack.Compression.Zip": "0.3.16",
"Wabbajack.DTOs": "0.3.15", "Wabbajack.DTOs": "0.3.16",
"Wabbajack.Paths.IO": "0.3.15" "Wabbajack.Paths.IO": "0.3.16"
}, },
"runtime": { "runtime": {
"Wabbajack.Downloaders.Interfaces.dll": {} "Wabbajack.Downloaders.Interfaces.dll": {}
} }
}, },
"Wabbajack.Downloaders.IPS4OAuth2Downloader/0.3.15": { "Wabbajack.Downloaders.IPS4OAuth2Downloader/0.3.16": {
"dependencies": { "dependencies": {
"F23.StringSimilarity": "6.0.0", "F23.StringSimilarity": "6.0.0",
"Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Microsoft.Extensions.Logging.Abstractions": "9.0.1", "Microsoft.Extensions.Logging.Abstractions": "9.0.1",
"Wabbajack.Common": "0.3.15", "Wabbajack.Common": "0.3.16",
"Wabbajack.Downloaders.Interfaces": "0.3.15", "Wabbajack.Downloaders.Interfaces": "0.3.16",
"Wabbajack.Networking.Http": "0.3.15", "Wabbajack.Networking.Http": "0.3.16",
"Wabbajack.Networking.Http.Interfaces": "0.3.15" "Wabbajack.Networking.Http.Interfaces": "0.3.16"
}, },
"runtime": { "runtime": {
"Wabbajack.Downloaders.IPS4OAuth2Downloader.dll": {} "Wabbajack.Downloaders.IPS4OAuth2Downloader.dll": {}
} }
}, },
"Wabbajack.Downloaders.Manual/0.3.15": { "Wabbajack.Downloaders.Manual/0.3.16": {
"dependencies": { "dependencies": {
"Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Microsoft.Extensions.Logging.Abstractions": "9.0.1", "Microsoft.Extensions.Logging.Abstractions": "9.0.1",
"Wabbajack.Common": "0.3.15", "Wabbajack.Common": "0.3.16",
"Wabbajack.Downloaders.Interfaces": "0.3.15" "Wabbajack.Downloaders.Interfaces": "0.3.16"
}, },
"runtime": { "runtime": {
"Wabbajack.Downloaders.Manual.dll": {} "Wabbajack.Downloaders.Manual.dll": {}
} }
}, },
"Wabbajack.Downloaders.MediaFire/0.3.15": { "Wabbajack.Downloaders.MediaFire/0.3.16": {
"dependencies": { "dependencies": {
"HtmlAgilityPack": "1.11.72", "HtmlAgilityPack": "1.11.72",
"Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Microsoft.Extensions.Logging.Abstractions": "9.0.1", "Microsoft.Extensions.Logging.Abstractions": "9.0.1",
"Wabbajack.Common": "0.3.15", "Wabbajack.Common": "0.3.16",
"Wabbajack.Downloaders.Interfaces": "0.3.15", "Wabbajack.Downloaders.Interfaces": "0.3.16",
"Wabbajack.Networking.Http.Interfaces": "0.3.15" "Wabbajack.Networking.Http.Interfaces": "0.3.16"
}, },
"runtime": { "runtime": {
"Wabbajack.Downloaders.MediaFire.dll": {} "Wabbajack.Downloaders.MediaFire.dll": {}
} }
}, },
"Wabbajack.Downloaders.Mega/0.3.15": { "Wabbajack.Downloaders.Mega/0.3.16": {
"dependencies": { "dependencies": {
"Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Microsoft.Extensions.Logging.Abstractions": "9.0.1", "Microsoft.Extensions.Logging.Abstractions": "9.0.1",
"Newtonsoft.Json": "13.0.3", "Newtonsoft.Json": "13.0.3",
"Wabbajack.Common": "0.3.15", "Wabbajack.Common": "0.3.16",
"Wabbajack.Downloaders.Interfaces": "0.3.15", "Wabbajack.Downloaders.Interfaces": "0.3.16",
"Wabbajack.Paths.IO": "0.3.15" "Wabbajack.Paths.IO": "0.3.16"
}, },
"runtime": { "runtime": {
"Wabbajack.Downloaders.Mega.dll": {} "Wabbajack.Downloaders.Mega.dll": {}
} }
}, },
"Wabbajack.Downloaders.ModDB/0.3.15": { "Wabbajack.Downloaders.ModDB/0.3.16": {
"dependencies": { "dependencies": {
"HtmlAgilityPack": "1.11.72", "HtmlAgilityPack": "1.11.72",
"Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Microsoft.Extensions.Logging.Abstractions": "9.0.1", "Microsoft.Extensions.Logging.Abstractions": "9.0.1",
"Newtonsoft.Json": "13.0.3", "Newtonsoft.Json": "13.0.3",
"Wabbajack.Common": "0.3.15", "Wabbajack.Common": "0.3.16",
"Wabbajack.Downloaders.Interfaces": "0.3.15", "Wabbajack.Downloaders.Interfaces": "0.3.16",
"Wabbajack.Networking.Http": "0.3.15", "Wabbajack.Networking.Http": "0.3.16",
"Wabbajack.Networking.Http.Interfaces": "0.3.15" "Wabbajack.Networking.Http.Interfaces": "0.3.16"
}, },
"runtime": { "runtime": {
"Wabbajack.Downloaders.ModDB.dll": {} "Wabbajack.Downloaders.ModDB.dll": {}
} }
}, },
"Wabbajack.Downloaders.Nexus/0.3.15": { "Wabbajack.Downloaders.Nexus/0.3.16": {
"dependencies": { "dependencies": {
"Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Wabbajack.DTOs": "0.3.15", "Wabbajack.DTOs": "0.3.16",
"Wabbajack.Downloaders.Interfaces": "0.3.15", "Wabbajack.Downloaders.Interfaces": "0.3.16",
"Wabbajack.Hashing.xxHash64": "0.3.15", "Wabbajack.Hashing.xxHash64": "0.3.16",
"Wabbajack.Networking.Http": "0.3.15", "Wabbajack.Networking.Http": "0.3.16",
"Wabbajack.Networking.Http.Interfaces": "0.3.15", "Wabbajack.Networking.Http.Interfaces": "0.3.16",
"Wabbajack.Networking.NexusApi": "0.3.15", "Wabbajack.Networking.NexusApi": "0.3.16",
"Wabbajack.Paths": "0.3.15" "Wabbajack.Paths": "0.3.16"
}, },
"runtime": { "runtime": {
"Wabbajack.Downloaders.Nexus.dll": {} "Wabbajack.Downloaders.Nexus.dll": {}
} }
}, },
"Wabbajack.Downloaders.VerificationCache/0.3.15": { "Wabbajack.Downloaders.VerificationCache/0.3.16": {
"dependencies": { "dependencies": {
"Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Microsoft.Extensions.Logging.Abstractions": "9.0.1", "Microsoft.Extensions.Logging.Abstractions": "9.0.1",
"Stub.System.Data.SQLite.Core.NetStandard": "1.0.119", "Stub.System.Data.SQLite.Core.NetStandard": "1.0.119",
"Wabbajack.DTOs": "0.3.15", "Wabbajack.DTOs": "0.3.16",
"Wabbajack.Paths.IO": "0.3.15" "Wabbajack.Paths.IO": "0.3.16"
}, },
"runtime": { "runtime": {
"Wabbajack.Downloaders.VerificationCache.dll": {} "Wabbajack.Downloaders.VerificationCache.dll": {}
} }
}, },
"Wabbajack.Downloaders.WabbajackCDN/0.3.15": { "Wabbajack.Downloaders.WabbajackCDN/0.3.16": {
"dependencies": { "dependencies": {
"Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Microsoft.Extensions.Logging.Abstractions": "9.0.1", "Microsoft.Extensions.Logging.Abstractions": "9.0.1",
"Microsoft.Toolkit.HighPerformance": "7.1.2", "Microsoft.Toolkit.HighPerformance": "7.1.2",
"Wabbajack.Common": "0.3.15", "Wabbajack.Common": "0.3.16",
"Wabbajack.Downloaders.Interfaces": "0.3.15", "Wabbajack.Downloaders.Interfaces": "0.3.16",
"Wabbajack.Networking.Http": "0.3.15", "Wabbajack.Networking.Http": "0.3.16",
"Wabbajack.RateLimiter": "0.3.15" "Wabbajack.RateLimiter": "0.3.16"
}, },
"runtime": { "runtime": {
"Wabbajack.Downloaders.WabbajackCDN.dll": {} "Wabbajack.Downloaders.WabbajackCDN.dll": {}
} }
}, },
"Wabbajack.DTOs/0.3.15": { "Wabbajack.DTOs/0.3.16": {
"dependencies": { "dependencies": {
"Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Wabbajack.Hashing.xxHash64": "0.3.15", "Wabbajack.Hashing.xxHash64": "0.3.16",
"Wabbajack.Paths": "0.3.15" "Wabbajack.Paths": "0.3.16"
}, },
"runtime": { "runtime": {
"Wabbajack.DTOs.dll": {} "Wabbajack.DTOs.dll": {}
} }
}, },
"Wabbajack.FileExtractor/0.3.15": { "Wabbajack.FileExtractor/0.3.16": {
"dependencies": { "dependencies": {
"Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Microsoft.Extensions.Logging.Abstractions": "9.0.1", "Microsoft.Extensions.Logging.Abstractions": "9.0.1",
"OMODFramework": "3.0.1", "OMODFramework": "3.0.1",
"Wabbajack.Common": "0.3.15", "Wabbajack.Common": "0.3.16",
"Wabbajack.Compression.BSA": "0.3.15", "Wabbajack.Compression.BSA": "0.3.16",
"Wabbajack.Hashing.PHash": "0.3.15", "Wabbajack.Hashing.PHash": "0.3.16",
"Wabbajack.Paths": "0.3.15" "Wabbajack.Paths": "0.3.16"
}, },
"runtime": { "runtime": {
"Wabbajack.FileExtractor.dll": {} "Wabbajack.FileExtractor.dll": {}
} }
}, },
"Wabbajack.Hashing.PHash/0.3.15": { "Wabbajack.Hashing.PHash/0.3.16": {
"dependencies": { "dependencies": {
"BCnEncoder.Net.ImageSharp": "1.1.1", "BCnEncoder.Net.ImageSharp": "1.1.1",
"Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Shipwreck.Phash": "0.5.0", "Shipwreck.Phash": "0.5.0",
"SixLabors.ImageSharp": "3.1.6", "SixLabors.ImageSharp": "3.1.6",
"Wabbajack.Common": "0.3.15", "Wabbajack.Common": "0.3.16",
"Wabbajack.DTOs": "0.3.15", "Wabbajack.DTOs": "0.3.16",
"Wabbajack.Paths": "0.3.15", "Wabbajack.Paths": "0.3.16",
"Wabbajack.Paths.IO": "0.3.15" "Wabbajack.Paths.IO": "0.3.16"
}, },
"runtime": { "runtime": {
"Wabbajack.Hashing.PHash.dll": {} "Wabbajack.Hashing.PHash.dll": {}
} }
}, },
"Wabbajack.Hashing.xxHash64/0.3.15": { "Wabbajack.Hashing.xxHash64/0.3.16": {
"dependencies": { "dependencies": {
"Wabbajack.Paths": "0.3.15", "Wabbajack.Paths": "0.3.16",
"Wabbajack.RateLimiter": "0.3.15" "Wabbajack.RateLimiter": "0.3.16"
}, },
"runtime": { "runtime": {
"Wabbajack.Hashing.xxHash64.dll": {} "Wabbajack.Hashing.xxHash64.dll": {}
} }
}, },
"Wabbajack.Installer/0.3.15": { "Wabbajack.Installer/0.3.16": {
"dependencies": { "dependencies": {
"Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Newtonsoft.Json": "13.0.3", "Newtonsoft.Json": "13.0.3",
"Octopus.Octodiff": "2.0.548", "Octopus.Octodiff": "2.0.548",
"SixLabors.ImageSharp": "3.1.6", "SixLabors.ImageSharp": "3.1.6",
"Wabbajack.DTOs": "0.3.15", "Wabbajack.DTOs": "0.3.16",
"Wabbajack.Downloaders.Dispatcher": "0.3.15", "Wabbajack.Downloaders.Dispatcher": "0.3.16",
"Wabbajack.Downloaders.GameFile": "0.3.15", "Wabbajack.Downloaders.GameFile": "0.3.16",
"Wabbajack.FileExtractor": "0.3.15", "Wabbajack.FileExtractor": "0.3.16",
"Wabbajack.Networking.WabbajackClientApi": "0.3.15", "Wabbajack.Networking.WabbajackClientApi": "0.3.16",
"Wabbajack.Paths": "0.3.15", "Wabbajack.Paths": "0.3.16",
"Wabbajack.Paths.IO": "0.3.15", "Wabbajack.Paths.IO": "0.3.16",
"Wabbajack.VFS": "0.3.15", "Wabbajack.VFS": "0.3.16",
"ini-parser-netstandard": "2.5.2" "ini-parser-netstandard": "2.5.2"
}, },
"runtime": { "runtime": {
"Wabbajack.Installer.dll": {} "Wabbajack.Installer.dll": {}
} }
}, },
"Wabbajack.IO.Async/0.3.15": { "Wabbajack.IO.Async/0.3.16": {
"runtime": { "runtime": {
"Wabbajack.IO.Async.dll": {} "Wabbajack.IO.Async.dll": {}
} }
}, },
"Wabbajack.Networking.BethesdaNet/0.3.15": { "Wabbajack.Networking.BethesdaNet/0.3.16": {
"dependencies": { "dependencies": {
"Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Wabbajack.DTOs": "0.3.15", "Wabbajack.DTOs": "0.3.16",
"Wabbajack.Networking.Http": "0.3.15", "Wabbajack.Networking.Http": "0.3.16",
"Wabbajack.Networking.Http.Interfaces": "0.3.15" "Wabbajack.Networking.Http.Interfaces": "0.3.16"
}, },
"runtime": { "runtime": {
"Wabbajack.Networking.BethesdaNet.dll": {} "Wabbajack.Networking.BethesdaNet.dll": {}
} }
}, },
"Wabbajack.Networking.Discord/0.3.15": { "Wabbajack.Networking.Discord/0.3.16": {
"dependencies": { "dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Microsoft.Extensions.Logging.Abstractions": "9.0.1", "Microsoft.Extensions.Logging.Abstractions": "9.0.1",
"Wabbajack.Networking.Http.Interfaces": "0.3.15" "Wabbajack.Networking.Http.Interfaces": "0.3.16"
}, },
"runtime": { "runtime": {
"Wabbajack.Networking.Discord.dll": {} "Wabbajack.Networking.Discord.dll": {}
} }
}, },
"Wabbajack.Networking.GitHub/0.3.15": { "Wabbajack.Networking.GitHub/0.3.16": {
"dependencies": { "dependencies": {
"Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Microsoft.Extensions.Logging.Abstractions": "9.0.1", "Microsoft.Extensions.Logging.Abstractions": "9.0.1",
"Octokit": "14.0.0", "Octokit": "14.0.0",
"Wabbajack.DTOs": "0.3.15", "Wabbajack.DTOs": "0.3.16",
"Wabbajack.Networking.Http.Interfaces": "0.3.15" "Wabbajack.Networking.Http.Interfaces": "0.3.16"
}, },
"runtime": { "runtime": {
"Wabbajack.Networking.GitHub.dll": {} "Wabbajack.Networking.GitHub.dll": {}
} }
}, },
"Wabbajack.Networking.Http/0.3.15": { "Wabbajack.Networking.Http/0.3.16": {
"dependencies": { "dependencies": {
"Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Microsoft.Extensions.Http": "9.0.1", "Microsoft.Extensions.Http": "9.0.1",
"Microsoft.Extensions.Logging": "9.0.1", "Microsoft.Extensions.Logging": "9.0.1",
"Wabbajack.Configuration": "0.3.15", "Wabbajack.Configuration": "0.3.16",
"Wabbajack.Downloaders.Interfaces": "0.3.15", "Wabbajack.Downloaders.Interfaces": "0.3.16",
"Wabbajack.Hashing.xxHash64": "0.3.15", "Wabbajack.Hashing.xxHash64": "0.3.16",
"Wabbajack.Networking.Http.Interfaces": "0.3.15", "Wabbajack.Networking.Http.Interfaces": "0.3.16",
"Wabbajack.Paths": "0.3.15", "Wabbajack.Paths": "0.3.16",
"Wabbajack.Paths.IO": "0.3.15" "Wabbajack.Paths.IO": "0.3.16"
}, },
"runtime": { "runtime": {
"Wabbajack.Networking.Http.dll": {} "Wabbajack.Networking.Http.dll": {}
} }
}, },
"Wabbajack.Networking.Http.Interfaces/0.3.15": { "Wabbajack.Networking.Http.Interfaces/0.3.16": {
"dependencies": { "dependencies": {
"Wabbajack.Hashing.xxHash64": "0.3.15" "Wabbajack.Hashing.xxHash64": "0.3.16"
}, },
"runtime": { "runtime": {
"Wabbajack.Networking.Http.Interfaces.dll": {} "Wabbajack.Networking.Http.Interfaces.dll": {}
} }
}, },
"Wabbajack.Networking.NexusApi/0.3.15": { "Wabbajack.Networking.NexusApi/0.3.16": {
"dependencies": { "dependencies": {
"Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Microsoft.Extensions.Logging.Abstractions": "9.0.1", "Microsoft.Extensions.Logging.Abstractions": "9.0.1",
"Wabbajack.DTOs": "0.3.15", "Wabbajack.DTOs": "0.3.16",
"Wabbajack.Networking.Http": "0.3.15", "Wabbajack.Networking.Http": "0.3.16",
"Wabbajack.Networking.Http.Interfaces": "0.3.15", "Wabbajack.Networking.Http.Interfaces": "0.3.16",
"Wabbajack.Networking.WabbajackClientApi": "0.3.15" "Wabbajack.Networking.WabbajackClientApi": "0.3.16"
}, },
"runtime": { "runtime": {
"Wabbajack.Networking.NexusApi.dll": {} "Wabbajack.Networking.NexusApi.dll": {}
} }
}, },
"Wabbajack.Networking.WabbajackClientApi/0.3.15": { "Wabbajack.Networking.WabbajackClientApi/0.3.16": {
"dependencies": { "dependencies": {
"Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Microsoft.Extensions.Logging.Abstractions": "9.0.1", "Microsoft.Extensions.Logging.Abstractions": "9.0.1",
"Octokit": "14.0.0", "Octokit": "14.0.0",
"Wabbajack.Common": "0.3.15", "Wabbajack.Common": "0.3.16",
"Wabbajack.DTOs": "0.3.15", "Wabbajack.DTOs": "0.3.16",
"Wabbajack.Paths.IO": "0.3.15", "Wabbajack.Paths.IO": "0.3.16",
"Wabbajack.VFS.Interfaces": "0.3.15", "Wabbajack.VFS.Interfaces": "0.3.16",
"YamlDotNet": "16.3.0" "YamlDotNet": "16.3.0"
}, },
"runtime": { "runtime": {
"Wabbajack.Networking.WabbajackClientApi.dll": {} "Wabbajack.Networking.WabbajackClientApi.dll": {}
} }
}, },
"Wabbajack.Paths/0.3.15": { "Wabbajack.Paths/0.3.16": {
"runtime": { "runtime": {
"Wabbajack.Paths.dll": {} "Wabbajack.Paths.dll": {}
} }
}, },
"Wabbajack.Paths.IO/0.3.15": { "Wabbajack.Paths.IO/0.3.16": {
"dependencies": { "dependencies": {
"Wabbajack.Paths": "0.3.15", "Wabbajack.Paths": "0.3.16",
"shortid": "4.0.0" "shortid": "4.0.0"
}, },
"runtime": { "runtime": {
"Wabbajack.Paths.IO.dll": {} "Wabbajack.Paths.IO.dll": {}
} }
}, },
"Wabbajack.RateLimiter/0.3.15": { "Wabbajack.RateLimiter/0.3.16": {
"runtime": { "runtime": {
"Wabbajack.RateLimiter.dll": {} "Wabbajack.RateLimiter.dll": {}
} }
}, },
"Wabbajack.Server.Lib/0.3.15": { "Wabbajack.Server.Lib/0.3.16": {
"dependencies": { "dependencies": {
"FluentFTP": "52.0.0", "FluentFTP": "52.0.0",
"Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection": "9.0.1",
@@ -2264,58 +2264,58 @@
"Nettle": "3.0.0", "Nettle": "3.0.0",
"Newtonsoft.Json": "13.0.3", "Newtonsoft.Json": "13.0.3",
"SixLabors.ImageSharp": "3.1.6", "SixLabors.ImageSharp": "3.1.6",
"Wabbajack.Common": "0.3.15", "Wabbajack.Common": "0.3.16",
"Wabbajack.Networking.Http.Interfaces": "0.3.15", "Wabbajack.Networking.Http.Interfaces": "0.3.16",
"Wabbajack.Services.OSIntegrated": "0.3.15" "Wabbajack.Services.OSIntegrated": "0.3.16"
}, },
"runtime": { "runtime": {
"Wabbajack.Server.Lib.dll": {} "Wabbajack.Server.Lib.dll": {}
} }
}, },
"Wabbajack.Services.OSIntegrated/0.3.15": { "Wabbajack.Services.OSIntegrated/0.3.16": {
"dependencies": { "dependencies": {
"DeviceId": "6.8.0", "DeviceId": "6.8.0",
"Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Newtonsoft.Json": "13.0.3", "Newtonsoft.Json": "13.0.3",
"SixLabors.ImageSharp": "3.1.6", "SixLabors.ImageSharp": "3.1.6",
"Wabbajack.Compiler": "0.3.15", "Wabbajack.Compiler": "0.3.16",
"Wabbajack.Downloaders.Dispatcher": "0.3.15", "Wabbajack.Downloaders.Dispatcher": "0.3.16",
"Wabbajack.Installer": "0.3.15", "Wabbajack.Installer": "0.3.16",
"Wabbajack.Networking.BethesdaNet": "0.3.15", "Wabbajack.Networking.BethesdaNet": "0.3.16",
"Wabbajack.Networking.Discord": "0.3.15", "Wabbajack.Networking.Discord": "0.3.16",
"Wabbajack.VFS": "0.3.15" "Wabbajack.VFS": "0.3.16"
}, },
"runtime": { "runtime": {
"Wabbajack.Services.OSIntegrated.dll": {} "Wabbajack.Services.OSIntegrated.dll": {}
} }
}, },
"Wabbajack.VFS/0.3.15": { "Wabbajack.VFS/0.3.16": {
"dependencies": { "dependencies": {
"Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Microsoft.Extensions.Logging.Abstractions": "9.0.1", "Microsoft.Extensions.Logging.Abstractions": "9.0.1",
"SixLabors.ImageSharp": "3.1.6", "SixLabors.ImageSharp": "3.1.6",
"System.Data.SQLite.Core": "1.0.119", "System.Data.SQLite.Core": "1.0.119",
"Wabbajack.Common": "0.3.15", "Wabbajack.Common": "0.3.16",
"Wabbajack.FileExtractor": "0.3.15", "Wabbajack.FileExtractor": "0.3.16",
"Wabbajack.Hashing.PHash": "0.3.15", "Wabbajack.Hashing.PHash": "0.3.16",
"Wabbajack.Hashing.xxHash64": "0.3.15", "Wabbajack.Hashing.xxHash64": "0.3.16",
"Wabbajack.Paths": "0.3.15", "Wabbajack.Paths": "0.3.16",
"Wabbajack.Paths.IO": "0.3.15", "Wabbajack.Paths.IO": "0.3.16",
"Wabbajack.VFS.Interfaces": "0.3.15" "Wabbajack.VFS.Interfaces": "0.3.16"
}, },
"runtime": { "runtime": {
"Wabbajack.VFS.dll": {} "Wabbajack.VFS.dll": {}
} }
}, },
"Wabbajack.VFS.Interfaces/0.3.15": { "Wabbajack.VFS.Interfaces/0.3.16": {
"dependencies": { "dependencies": {
"Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Wabbajack.DTOs": "0.3.15", "Wabbajack.DTOs": "0.3.16",
"Wabbajack.Hashing.xxHash64": "0.3.15", "Wabbajack.Hashing.xxHash64": "0.3.16",
"Wabbajack.Paths": "0.3.15" "Wabbajack.Paths": "0.3.16"
}, },
"runtime": { "runtime": {
"Wabbajack.VFS.Interfaces.dll": {} "Wabbajack.VFS.Interfaces.dll": {}
@@ -2332,7 +2332,7 @@
} }
}, },
"libraries": { "libraries": {
"jackify-engine/0.3.15": { "jackify-engine/0.3.16": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
@@ -3021,202 +3021,202 @@
"path": "yamldotnet/16.3.0", "path": "yamldotnet/16.3.0",
"hashPath": "yamldotnet.16.3.0.nupkg.sha512" "hashPath": "yamldotnet.16.3.0.nupkg.sha512"
}, },
"Wabbajack.CLI.Builder/0.3.15": { "Wabbajack.CLI.Builder/0.3.16": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Common/0.3.15": { "Wabbajack.Common/0.3.16": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Compiler/0.3.15": { "Wabbajack.Compiler/0.3.16": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Compression.BSA/0.3.15": { "Wabbajack.Compression.BSA/0.3.16": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Compression.Zip/0.3.15": { "Wabbajack.Compression.Zip/0.3.16": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Configuration/0.3.15": { "Wabbajack.Configuration/0.3.16": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Downloaders.Bethesda/0.3.15": { "Wabbajack.Downloaders.Bethesda/0.3.16": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Downloaders.Dispatcher/0.3.15": { "Wabbajack.Downloaders.Dispatcher/0.3.16": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Downloaders.GameFile/0.3.15": { "Wabbajack.Downloaders.GameFile/0.3.16": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Downloaders.GoogleDrive/0.3.15": { "Wabbajack.Downloaders.GoogleDrive/0.3.16": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Downloaders.Http/0.3.15": { "Wabbajack.Downloaders.Http/0.3.16": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Downloaders.Interfaces/0.3.15": { "Wabbajack.Downloaders.Interfaces/0.3.16": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Downloaders.IPS4OAuth2Downloader/0.3.15": { "Wabbajack.Downloaders.IPS4OAuth2Downloader/0.3.16": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Downloaders.Manual/0.3.15": { "Wabbajack.Downloaders.Manual/0.3.16": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Downloaders.MediaFire/0.3.15": { "Wabbajack.Downloaders.MediaFire/0.3.16": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Downloaders.Mega/0.3.15": { "Wabbajack.Downloaders.Mega/0.3.16": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Downloaders.ModDB/0.3.15": { "Wabbajack.Downloaders.ModDB/0.3.16": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Downloaders.Nexus/0.3.15": { "Wabbajack.Downloaders.Nexus/0.3.16": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Downloaders.VerificationCache/0.3.15": { "Wabbajack.Downloaders.VerificationCache/0.3.16": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Downloaders.WabbajackCDN/0.3.15": { "Wabbajack.Downloaders.WabbajackCDN/0.3.16": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.DTOs/0.3.15": { "Wabbajack.DTOs/0.3.16": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.FileExtractor/0.3.15": { "Wabbajack.FileExtractor/0.3.16": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Hashing.PHash/0.3.15": { "Wabbajack.Hashing.PHash/0.3.16": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Hashing.xxHash64/0.3.15": { "Wabbajack.Hashing.xxHash64/0.3.16": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Installer/0.3.15": { "Wabbajack.Installer/0.3.16": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.IO.Async/0.3.15": { "Wabbajack.IO.Async/0.3.16": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Networking.BethesdaNet/0.3.15": { "Wabbajack.Networking.BethesdaNet/0.3.16": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Networking.Discord/0.3.15": { "Wabbajack.Networking.Discord/0.3.16": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Networking.GitHub/0.3.15": { "Wabbajack.Networking.GitHub/0.3.16": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Networking.Http/0.3.15": { "Wabbajack.Networking.Http/0.3.16": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Networking.Http.Interfaces/0.3.15": { "Wabbajack.Networking.Http.Interfaces/0.3.16": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Networking.NexusApi/0.3.15": { "Wabbajack.Networking.NexusApi/0.3.16": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Networking.WabbajackClientApi/0.3.15": { "Wabbajack.Networking.WabbajackClientApi/0.3.16": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Paths/0.3.15": { "Wabbajack.Paths/0.3.16": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Paths.IO/0.3.15": { "Wabbajack.Paths.IO/0.3.16": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.RateLimiter/0.3.15": { "Wabbajack.RateLimiter/0.3.16": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Server.Lib/0.3.15": { "Wabbajack.Server.Lib/0.3.16": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Services.OSIntegrated/0.3.15": { "Wabbajack.Services.OSIntegrated/0.3.16": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.VFS/0.3.15": { "Wabbajack.VFS/0.3.16": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.VFS.Interfaces/0.3.15": { "Wabbajack.VFS.Interfaces/0.3.16": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""

Binary file not shown.

View File

@@ -102,7 +102,7 @@ sys.path.insert(0, str(src_dir))
from PySide6.QtWidgets import ( from PySide6.QtWidgets import (
QApplication, QMainWindow, QWidget, QLabel, QVBoxLayout, QPushButton, QApplication, QMainWindow, QWidget, QLabel, QVBoxLayout, QPushButton,
QStackedWidget, QHBoxLayout, QDialog, QFormLayout, QLineEdit, QCheckBox, QSpinBox, QMessageBox, QGroupBox, QGridLayout, QFileDialog, QToolButton, QStyle, QComboBox QStackedWidget, QHBoxLayout, QDialog, QFormLayout, QLineEdit, QCheckBox, QSpinBox, QMessageBox, QGroupBox, QGridLayout, QFileDialog, QToolButton, QStyle, QComboBox, QTabWidget
) )
from PySide6.QtCore import Qt, QEvent from PySide6.QtCore import Qt, QEvent
from PySide6.QtGui import QIcon from PySide6.QtGui import QIcon
@@ -167,221 +167,26 @@ class SettingsDialog(QDialog):
self._original_debug_mode = self.config_handler.get('debug_mode', False) self._original_debug_mode = self.config_handler.get('debug_mode', False)
self.setWindowTitle("Settings") self.setWindowTitle("Settings")
self.setModal(True) self.setModal(True)
self.setMinimumWidth(750) self.setMinimumWidth(650) # Reduced width for Steam Deck compatibility
self.setMaximumWidth(800) # Maximum width to prevent excessive stretching
self.setStyleSheet("QDialog { background-color: #232323; color: #eee; } QPushButton:hover { background-color: #333; }") self.setStyleSheet("QDialog { background-color: #232323; color: #eee; } QPushButton:hover { background-color: #333; }")
main_layout = QVBoxLayout() main_layout = QVBoxLayout()
self.setLayout(main_layout) self.setLayout(main_layout)
# --- Resource Limits Section --- # Create tab widget
resource_group = QGroupBox("Resource Limits") self.tab_widget = QTabWidget()
resource_group.setStyleSheet("QGroupBox { border: 1px solid #555; border-radius: 6px; margin-top: 8px; padding: 8px; background: #23282d; } QGroupBox:title { subcontrol-origin: margin; left: 10px; padding: 0 3px 0 3px; font-weight: bold; color: #fff; }") self.tab_widget.setStyleSheet("""
resource_layout = QGridLayout() QTabWidget::pane { border: 1px solid #555; background: #232323; }
resource_group.setLayout(resource_layout) QTabBar::tab { background: #333; color: #eee; padding: 8px 16px; margin: 2px; }
resource_layout.setVerticalSpacing(4) QTabBar::tab:selected { background: #555; }
resource_layout.setHorizontalSpacing(8) QTabBar::tab:hover { background: #444; }
resource_layout.addWidget(self._bold_label("Resource"), 0, 0, 1, 1, Qt.AlignLeft) """)
resource_layout.addWidget(self._bold_label("Max Tasks"), 0, 1, 1, 1, Qt.AlignLeft) main_layout.addWidget(self.tab_widget)
self.resource_settings_path = os.path.expanduser("~/.config/jackify/resource_settings.json")
self.resource_settings = self._load_json(self.resource_settings_path)
self.resource_edits = {}
resource_row_index = 0
for resource_row_index, (k, v) in enumerate(self.resource_settings.items(), start=1):
try:
# Create resource label with optional inline checkbox for File Extractor
if k == "File Extractor":
# Create horizontal layout for File Extractor with inline checkbox
resource_row = QHBoxLayout()
resource_label = QLabel(f"{k}:", parent=self)
resource_row.addWidget(resource_label)
resource_row.addSpacing(10) # Add some spacing
multithreading_checkbox = QCheckBox("Multithreading (Experimental)")
multithreading_checkbox.setChecked(v.get('_7zzMultiThread', 'off') == 'on')
multithreading_checkbox.setToolTip("Enables multithreaded file extraction using 7-Zip. May improve extraction speed on multi-core systems but could be less stable.")
multithreading_checkbox.setStyleSheet("color: #fff;")
resource_row.addWidget(multithreading_checkbox)
resource_row.addStretch() # Push checkbox to the left
# Add the horizontal layout to the grid
resource_layout.addLayout(resource_row, resource_row_index, 0)
else:
resource_layout.addWidget(QLabel(f"{k}:", parent=self), resource_row_index, 0, 1, 1, Qt.AlignLeft)
max_tasks_spin = QSpinBox()
max_tasks_spin.setMinimum(1)
max_tasks_spin.setMaximum(128)
max_tasks_spin.setValue(v.get('MaxTasks', 16))
max_tasks_spin.setToolTip("Maximum number of concurrent tasks for this resource.")
max_tasks_spin.setFixedWidth(160)
resource_layout.addWidget(max_tasks_spin, resource_row_index, 1)
# Store the widgets (checkbox for File Extractor, None for others)
if k == "File Extractor":
self.resource_edits[k] = (multithreading_checkbox, max_tasks_spin)
else:
self.resource_edits[k] = (None, max_tasks_spin)
except Exception as e:
print(f"[ERROR] Failed to create widgets for resource '{k}': {e}")
continue
# If no resources exist, show helpful message
if not self.resource_edits:
info_label = QLabel("Resource Limit settings will be generated once a modlist install action is performed")
info_label.setStyleSheet("color: #aaa; font-style: italic; padding: 20px; font-size: 11pt;")
info_label.setWordWrap(True)
info_label.setAlignment(Qt.AlignCenter)
info_label.setMinimumHeight(60) # Ensure enough height to prevent cutoff
resource_layout.addWidget(info_label, 1, 0, 3, 2) # Span more rows for better space
# Bandwidth limiter row (only show if Downloads resource exists)
if "Downloads" in self.resource_settings:
downloads_throughput = self.resource_settings["Downloads"].get("MaxThroughput", 0)
self.bandwidth_spin = QSpinBox()
self.bandwidth_spin.setMinimum(0)
self.bandwidth_spin.setMaximum(1000000)
self.bandwidth_spin.setValue(downloads_throughput)
self.bandwidth_spin.setSuffix(" KB/s")
self.bandwidth_spin.setFixedWidth(160)
self.bandwidth_spin.setToolTip("Set the maximum download speed for modlist downloads. 0 = unlimited.")
bandwidth_note = QLabel("(0 = unlimited)")
bandwidth_note.setStyleSheet("color: #aaa; font-size: 10pt;")
# Create horizontal layout for bandwidth row
bandwidth_row = QHBoxLayout()
bandwidth_row.addWidget(self.bandwidth_spin)
bandwidth_row.addWidget(bandwidth_note)
bandwidth_row.addStretch() # Push to the left
resource_layout.addWidget(QLabel("Bandwidth Limit:", parent=self), resource_row_index+1, 0, 1, 1, Qt.AlignLeft)
resource_layout.addLayout(bandwidth_row, resource_row_index+1, 1)
else:
self.bandwidth_spin = None # No bandwidth UI if Downloads resource doesn't exist
main_layout.addWidget(resource_group)
main_layout.addSpacing(12)
# --- Debug & Diagnostics Section --- # Create tabs
debug_group = QGroupBox("Debug & Diagnostics") self._create_general_tab()
debug_group.setStyleSheet("QGroupBox { border: 1px solid #555; border-radius: 6px; margin-top: 8px; padding: 8px; background: #23282d; } QGroupBox:title { subcontrol-origin: margin; left: 10px; padding: 0 3px 0 3px; font-weight: bold; color: #fff; }") self._create_advanced_tab()
debug_layout = QVBoxLayout()
debug_group.setLayout(debug_layout)
self.debug_checkbox = QCheckBox("Enable debug mode (requires restart)")
# Load debug_mode from config
self.debug_checkbox.setChecked(self.config_handler.get('debug_mode', False))
self.debug_checkbox.setToolTip("Enable verbose debug logging. Requires Jackify restart to take effect.")
self.debug_checkbox.setStyleSheet("color: #fff;")
debug_layout.addWidget(self.debug_checkbox)
main_layout.addWidget(debug_group)
main_layout.addSpacing(12)
# --- Nexus API Key Section ---
api_group = QGroupBox("Nexus API Key")
api_group.setStyleSheet("QGroupBox { border: 1px solid #555; border-radius: 6px; margin-top: 8px; padding: 8px; background: #23282d; } QGroupBox:title { subcontrol-origin: margin; left: 10px; padding: 0 3px 0 3px; font-weight: bold; color: #fff; }")
api_layout = QHBoxLayout()
api_group.setLayout(api_layout)
self.api_key_edit = QLineEdit()
self.api_key_edit.setEchoMode(QLineEdit.Password)
api_key = self.config_handler.get_api_key()
if api_key:
self.api_key_edit.setText(api_key)
else:
self.api_key_edit.setText("")
self.api_key_edit.setToolTip("Your Nexus API Key (obfuscated by default, click Show to reveal)")
# Connect for immediate saving when text changes
self.api_key_edit.textChanged.connect(self._on_api_key_changed)
self.api_show_btn = QToolButton()
self.api_show_btn.setCheckable(True)
self.api_show_btn.setIcon(QIcon.fromTheme("view-visible"))
self.api_show_btn.setToolTip("Show or hide your API key")
self.api_show_btn.toggled.connect(self._toggle_api_key_visibility)
self.api_show_btn.setStyleSheet("")
clear_api_btn = QPushButton("Clear API Key")
clear_api_btn.clicked.connect(self._clear_api_key)
api_layout.addWidget(QLabel("Nexus API Key:"))
api_layout.addWidget(self.api_key_edit)
api_layout.addWidget(self.api_show_btn)
api_layout.addWidget(clear_api_btn)
main_layout.addWidget(api_group)
main_layout.addSpacing(12)
# --- Proton Version Section ---
proton_group = QGroupBox("Proton Version")
proton_group.setStyleSheet("QGroupBox { border: 1px solid #555; border-radius: 6px; margin-top: 8px; padding: 8px; background: #23282d; } QGroupBox:title { subcontrol-origin: margin; left: 10px; padding: 0 3px 0 3px; font-weight: bold; color: #fff; }")
proton_layout = QHBoxLayout()
proton_group.setLayout(proton_layout)
self.proton_dropdown = QComboBox()
self.proton_dropdown.setToolTip("Select Proton version for shortcut creation and texture processing")
self.proton_dropdown.setMinimumWidth(200)
# Populate Proton dropdown
self._populate_proton_dropdown()
# Refresh button for Proton detection
refresh_btn = QPushButton("")
refresh_btn.setFixedSize(30, 30)
refresh_btn.setToolTip("Refresh Proton version list")
refresh_btn.clicked.connect(self._refresh_proton_dropdown)
proton_layout.addWidget(QLabel("Proton Version:"))
proton_layout.addWidget(self.proton_dropdown)
proton_layout.addWidget(refresh_btn)
proton_layout.addStretch()
main_layout.addWidget(proton_group)
main_layout.addSpacing(12)
# --- Directories & Paths Section ---
dir_group = QGroupBox("Directories & Paths")
dir_group.setStyleSheet("QGroupBox { border: 1px solid #555; border-radius: 6px; margin-top: 8px; padding: 8px; background: #23282d; } QGroupBox:title { subcontrol-origin: margin; left: 10px; padding: 0 3px 0 3px; font-weight: bold; color: #fff; }")
dir_layout = QFormLayout()
dir_group.setLayout(dir_layout)
self.install_dir_edit = QLineEdit(self.config_handler.get("modlist_install_base_dir", ""))
self.install_dir_edit.setToolTip("Default directory for modlist installations.")
self.install_dir_btn = QPushButton()
self.install_dir_btn.setIcon(QIcon.fromTheme("folder-open"))
self.install_dir_btn.setToolTip("Browse for directory")
self.install_dir_btn.setFixedWidth(32)
self.install_dir_btn.clicked.connect(lambda: self._pick_directory(self.install_dir_edit))
install_dir_row = QHBoxLayout()
install_dir_row.addWidget(self.install_dir_edit)
install_dir_row.addWidget(self.install_dir_btn)
dir_layout.addRow(QLabel("Install Base Dir:"), install_dir_row)
self.download_dir_edit = QLineEdit(self.config_handler.get("modlist_downloads_base_dir", ""))
self.download_dir_edit.setToolTip("Default directory for modlist downloads.")
self.download_dir_btn = QPushButton()
self.download_dir_btn.setIcon(QIcon.fromTheme("folder-open"))
self.download_dir_btn.setToolTip("Browse for directory")
self.download_dir_btn.setFixedWidth(32)
self.download_dir_btn.clicked.connect(lambda: self._pick_directory(self.download_dir_edit))
download_dir_row = QHBoxLayout()
download_dir_row.addWidget(self.download_dir_edit)
download_dir_row.addWidget(self.download_dir_btn)
dir_layout.addRow(QLabel("Downloads Base Dir:"), download_dir_row)
# Jackify Data Directory
from jackify.shared.paths import get_jackify_data_dir
current_jackify_dir = str(get_jackify_data_dir())
self.jackify_data_dir_edit = QLineEdit(current_jackify_dir)
self.jackify_data_dir_edit.setToolTip("Directory for Jackify data (logs, downloads, temp files). Default: ~/Jackify")
self.jackify_data_dir_btn = QPushButton()
self.jackify_data_dir_btn.setIcon(QIcon.fromTheme("folder-open"))
self.jackify_data_dir_btn.setToolTip("Browse for directory")
self.jackify_data_dir_btn.setFixedWidth(32)
self.jackify_data_dir_btn.clicked.connect(lambda: self._pick_directory(self.jackify_data_dir_edit))
jackify_data_dir_row = QHBoxLayout()
jackify_data_dir_row.addWidget(self.jackify_data_dir_edit)
jackify_data_dir_row.addWidget(self.jackify_data_dir_btn)
# Reset to default button
reset_jackify_dir_btn = QPushButton("Reset")
reset_jackify_dir_btn.setToolTip("Reset to default (~/ Jackify)")
reset_jackify_dir_btn.setFixedWidth(50)
reset_jackify_dir_btn.clicked.connect(lambda: self.jackify_data_dir_edit.setText(str(Path.home() / "Jackify")))
jackify_data_dir_row.addWidget(reset_jackify_dir_btn)
dir_layout.addRow(QLabel("Jackify Data Dir:"), jackify_data_dir_row)
main_layout.addWidget(dir_group)
main_layout.addSpacing(12)
# --- Save/Close/Help Buttons --- # --- Save/Close/Help Buttons ---
btn_layout = QHBoxLayout() btn_layout = QHBoxLayout()
@@ -396,36 +201,224 @@ class SettingsDialog(QDialog):
close_btn.clicked.connect(self.reject) close_btn.clicked.connect(self.reject)
btn_layout.addWidget(save_btn) btn_layout.addWidget(save_btn)
btn_layout.addWidget(close_btn) btn_layout.addWidget(close_btn)
# Add error label for validation messages
self.error_label = QLabel("")
self.error_label.setStyleSheet("QLabel { color: #ff6b6b; }")
main_layout.addWidget(self.error_label)
main_layout.addSpacing(10) main_layout.addSpacing(10)
main_layout.addLayout(btn_layout) main_layout.addLayout(btn_layout)
# Set tab order for accessibility
# Get the first resource's widgets if any exist
if self.resource_edits:
first_resource_key = list(self.resource_edits.keys())[0]
first_multithreading, first_max_tasks = self.resource_edits[first_resource_key]
# Set tab order starting with the first max tasks spinner
self.setTabOrder(first_max_tasks, self.bandwidth_spin)
# Continue with bandwidth spinner regardless of resources
self.setTabOrder(self.bandwidth_spin, self.debug_checkbox)
self.setTabOrder(self.debug_checkbox, self.api_key_edit)
self.setTabOrder(self.api_key_edit, self.api_show_btn)
self.setTabOrder(self.api_show_btn, clear_api_btn)
self.setTabOrder(clear_api_btn, self.install_dir_edit)
self.setTabOrder(self.install_dir_edit, self.install_dir_btn)
self.setTabOrder(self.install_dir_btn, self.download_dir_edit)
self.setTabOrder(self.download_dir_edit, self.download_dir_btn)
self.setTabOrder(self.download_dir_btn, save_btn)
self.setTabOrder(save_btn, close_btn)
self.error_label = QLabel("")
self.error_label.setStyleSheet("color: #f55; font-weight: bold;")
main_layout.insertWidget(0, self.error_label)
except Exception as e: except Exception as e:
print(f"[ERROR] Exception in SettingsDialog __init__: {e}") print(f"[ERROR] Exception in SettingsDialog.__init__: {e}")
import traceback import traceback
traceback.print_exc() traceback.print_exc()
raise
def _create_general_tab(self):
"""Create the General settings tab"""
general_tab = QWidget()
general_layout = QVBoxLayout(general_tab)
# --- Directory Paths Section (moved to top as most essential) ---
dir_group = QGroupBox("Directory Paths")
dir_group.setStyleSheet("QGroupBox { border: 1px solid #555; border-radius: 6px; margin-top: 8px; padding: 8px; background: #23282d; } QGroupBox:title { subcontrol-origin: margin; left: 10px; padding: 0 3px 0 3px; font-weight: bold; color: #fff; }")
dir_layout = QFormLayout()
dir_group.setLayout(dir_layout)
self.install_dir_edit = QLineEdit(self.config_handler.get("modlist_install_base_dir", ""))
self.install_dir_edit.setToolTip("Default directory for modlist installations.")
self.install_dir_btn = QPushButton()
self.install_dir_btn.setIcon(QIcon.fromTheme("folder-open"))
self.install_dir_btn.setToolTip("Browse for directory")
self.install_dir_btn.setFixedWidth(32)
self.install_dir_btn.clicked.connect(lambda: self._pick_directory(self.install_dir_edit))
install_dir_row = QHBoxLayout()
install_dir_row.addWidget(self.install_dir_edit)
install_dir_row.addWidget(self.install_dir_btn)
dir_layout.addRow(QLabel("Install Base Dir:"), install_dir_row)
self.download_dir_edit = QLineEdit(self.config_handler.get("modlist_downloads_base_dir", ""))
self.download_dir_edit.setToolTip("Default directory for modlist downloads.")
self.download_dir_btn = QPushButton()
self.download_dir_btn.setIcon(QIcon.fromTheme("folder-open"))
self.download_dir_btn.setToolTip("Browse for directory")
self.download_dir_btn.setFixedWidth(32)
self.download_dir_btn.clicked.connect(lambda: self._pick_directory(self.download_dir_edit))
download_dir_row = QHBoxLayout()
download_dir_row.addWidget(self.download_dir_edit)
download_dir_row.addWidget(self.download_dir_btn)
dir_layout.addRow(QLabel("Downloads Base Dir:"), download_dir_row)
# Jackify Data Directory
from jackify.shared.paths import get_jackify_data_dir
current_jackify_dir = str(get_jackify_data_dir())
self.jackify_data_dir_edit = QLineEdit(current_jackify_dir)
self.jackify_data_dir_edit.setToolTip("Directory for Jackify data (logs, downloads, temp files). Default: ~/Jackify")
self.jackify_data_dir_btn = QPushButton()
self.jackify_data_dir_btn.setIcon(QIcon.fromTheme("folder-open"))
self.jackify_data_dir_btn.setToolTip("Browse for directory")
self.jackify_data_dir_btn.setFixedWidth(32)
self.jackify_data_dir_btn.clicked.connect(lambda: self._pick_directory(self.jackify_data_dir_edit))
jackify_data_dir_row = QHBoxLayout()
jackify_data_dir_row.addWidget(self.jackify_data_dir_edit)
jackify_data_dir_row.addWidget(self.jackify_data_dir_btn)
# Reset to default button
reset_jackify_dir_btn = QPushButton("Reset")
reset_jackify_dir_btn.setToolTip("Reset to default (~/ Jackify)")
reset_jackify_dir_btn.setFixedWidth(50)
reset_jackify_dir_btn.clicked.connect(lambda: self.jackify_data_dir_edit.setText(str(Path.home() / "Jackify")))
jackify_data_dir_row.addWidget(reset_jackify_dir_btn)
dir_layout.addRow(QLabel("Jackify Data Dir:"), jackify_data_dir_row)
general_layout.addWidget(dir_group)
general_layout.addSpacing(12)
# --- Nexus API Key Section ---
api_group = QGroupBox("Nexus API Key")
api_group.setStyleSheet("QGroupBox { border: 1px solid #555; border-radius: 6px; margin-top: 8px; padding: 8px; background: #23282d; } QGroupBox:title { subcontrol-origin: margin; left: 10px; padding: 0 3px 0 3px; font-weight: bold; color: #fff; }")
api_layout = QHBoxLayout()
api_group.setLayout(api_layout)
self.api_key_edit = QLineEdit()
self.api_key_edit.setEchoMode(QLineEdit.Password)
api_key = self.config_handler.get_api_key()
if api_key:
self.api_key_edit.setText(api_key)
else:
self.api_key_edit.setText("")
self.api_key_edit.setToolTip("Your Nexus API Key (obfuscated by default, click Show to reveal)")
# Connect for immediate saving when text changes
self.api_key_edit.textChanged.connect(self._on_api_key_changed)
self.api_show_btn = QToolButton()
self.api_show_btn.setCheckable(True)
self.api_show_btn.setIcon(QIcon.fromTheme("view-visible"))
self.api_show_btn.setToolTip("Show or hide your API key")
self.api_show_btn.toggled.connect(self._toggle_api_key_visibility)
self.api_show_btn.setStyleSheet("")
clear_api_btn = QPushButton("Clear API Key")
clear_api_btn.clicked.connect(self._clear_api_key)
api_layout.addWidget(QLabel("Nexus API Key:"))
api_layout.addWidget(self.api_key_edit)
api_layout.addWidget(self.api_show_btn)
api_layout.addWidget(clear_api_btn)
general_layout.addWidget(api_group)
general_layout.addSpacing(12)
# --- Default Proton Version Section ---
proton_group = QGroupBox("Default Proton Version")
proton_group.setStyleSheet("QGroupBox { border: 1px solid #555; border-radius: 6px; margin-top: 8px; padding: 8px; background: #23282d; } QGroupBox:title { subcontrol-origin: margin; left: 10px; padding: 0 3px 0 3px; font-weight: bold; color: #fff; }")
proton_layout = QHBoxLayout()
proton_group.setLayout(proton_layout)
self.proton_dropdown = QComboBox()
self.proton_dropdown.setToolTip("Select default Proton version for shortcut creation and texture processing")
self.proton_dropdown.setMinimumWidth(200)
# Populate Proton dropdown
self._populate_proton_dropdown()
# Refresh button for Proton detection
refresh_btn = QPushButton("")
refresh_btn.setFixedSize(30, 30)
refresh_btn.setToolTip("Refresh Proton version list")
refresh_btn.clicked.connect(self._refresh_proton_dropdown)
proton_layout.addWidget(QLabel("Proton Version:"))
proton_layout.addWidget(self.proton_dropdown)
proton_layout.addWidget(refresh_btn)
proton_layout.addStretch()
general_layout.addWidget(proton_group)
general_layout.addSpacing(12)
# --- Enable Debug Section (moved to bottom as advanced option) ---
debug_group = QGroupBox("Enable Debug")
debug_group.setStyleSheet("QGroupBox { border: 1px solid #555; border-radius: 6px; margin-top: 8px; padding: 8px; background: #23282d; } QGroupBox:title { subcontrol-origin: margin; left: 10px; padding: 0 3px 0 3px; font-weight: bold; color: #fff; }")
debug_layout = QVBoxLayout()
debug_group.setLayout(debug_layout)
self.debug_checkbox = QCheckBox("Enable debug mode (requires restart)")
# Load debug_mode from config
self.debug_checkbox.setChecked(self.config_handler.get('debug_mode', False))
self.debug_checkbox.setToolTip("Enable verbose debug logging. Requires Jackify restart to take effect.")
self.debug_checkbox.setStyleSheet("color: #fff;")
debug_layout.addWidget(self.debug_checkbox)
general_layout.addWidget(debug_group)
general_layout.addStretch() # Add stretch to push content to top
self.tab_widget.addTab(general_tab, "General")
def _create_advanced_tab(self):
"""Create the Advanced settings tab"""
advanced_tab = QWidget()
advanced_layout = QVBoxLayout(advanced_tab)
resource_group = QGroupBox("Resource Limits")
resource_group.setStyleSheet("QGroupBox { border: 1px solid #555; border-radius: 6px; margin-top: 8px; padding: 8px; background: #23282d; } QGroupBox:title { subcontrol-origin: margin; left: 10px; padding: 0 3px 0 3px; font-weight: bold; color: #fff; }")
resource_layout = QGridLayout()
resource_group.setLayout(resource_layout)
resource_layout.setVerticalSpacing(4)
resource_layout.setHorizontalSpacing(8)
resource_layout.addWidget(self._bold_label("Resource"), 0, 0, 1, 1, Qt.AlignLeft)
resource_layout.addWidget(self._bold_label("Max Tasks"), 0, 1, 1, 1, Qt.AlignLeft)
self.resource_settings_path = os.path.expanduser("~/.config/jackify/resource_settings.json")
self.resource_settings = self._load_json(self.resource_settings_path)
self.resource_edits = {}
resource_row_index = 0
for resource_row_index, (k, v) in enumerate(self.resource_settings.items(), start=1):
try:
# Create resource label
resource_layout.addWidget(QLabel(f"{k}:", parent=self), resource_row_index, 0, 1, 1, Qt.AlignLeft)
max_tasks_spin = QSpinBox()
max_tasks_spin.setMinimum(1)
max_tasks_spin.setMaximum(128)
max_tasks_spin.setValue(v.get('MaxTasks', 16))
max_tasks_spin.setToolTip("Maximum number of concurrent tasks for this resource.")
max_tasks_spin.setFixedWidth(160)
resource_layout.addWidget(max_tasks_spin, resource_row_index, 1)
# Store the widgets
self.resource_edits[k] = (None, max_tasks_spin)
except Exception as e:
print(f"[ERROR] Failed to create widgets for resource '{k}': {e}")
continue
# If no resources exist, show helpful message
if not self.resource_edits:
info_label = QLabel("Resource Limit settings will be generated once a modlist install action is performed")
info_label.setStyleSheet("color: #aaa; font-style: italic; padding: 20px; font-size: 11pt;")
info_label.setWordWrap(True)
info_label.setAlignment(Qt.AlignCenter)
info_label.setMinimumHeight(60) # Ensure enough height to prevent cutoff
resource_layout.addWidget(info_label, 1, 0, 3, 2) # Span more rows for better space
# Bandwidth limiter row (only show if Downloads resource exists)
if "Downloads" in self.resource_settings:
downloads_throughput = self.resource_settings["Downloads"].get("MaxThroughput", 0)
self.bandwidth_spin = QSpinBox()
self.bandwidth_spin.setMinimum(0)
self.bandwidth_spin.setMaximum(1000000)
self.bandwidth_spin.setValue(downloads_throughput)
self.bandwidth_spin.setSuffix(" KB/s")
self.bandwidth_spin.setFixedWidth(160)
self.bandwidth_spin.setToolTip("Set the maximum download speed for modlist downloads. 0 = unlimited.")
bandwidth_note = QLabel("(0 = unlimited)")
bandwidth_note.setStyleSheet("color: #aaa; font-size: 10pt;")
# Create horizontal layout for bandwidth row
bandwidth_row = QHBoxLayout()
bandwidth_row.addWidget(self.bandwidth_spin)
bandwidth_row.addWidget(bandwidth_note)
bandwidth_row.addStretch() # Push to the left
resource_layout.addWidget(QLabel("Bandwidth Limit:", parent=self), resource_row_index+1, 0, 1, 1, Qt.AlignLeft)
resource_layout.addLayout(bandwidth_row, resource_row_index+1, 1)
else:
self.bandwidth_spin = None # No bandwidth UI if Downloads resource doesn't exist
advanced_layout.addWidget(resource_group)
advanced_layout.addStretch() # Add stretch to push content to top
self.tab_widget.addTab(advanced_tab, "Advanced")
def _toggle_api_key_visibility(self, checked): def _toggle_api_key_visibility(self, checked):
# Always use the same eyeball icon, only change color when toggled # Always use the same eyeball icon, only change color when toggled
@@ -572,13 +565,6 @@ class SettingsDialog(QDialog):
for k, (multithreading_checkbox, max_tasks_spin) in self.resource_edits.items(): for k, (multithreading_checkbox, max_tasks_spin) in self.resource_edits.items():
resource_data = self.resource_settings.get(k, {}) resource_data = self.resource_settings.get(k, {})
resource_data['MaxTasks'] = max_tasks_spin.value() resource_data['MaxTasks'] = max_tasks_spin.value()
# Only add multithreading setting for File Extractor
if k == "File Extractor" and multithreading_checkbox:
if multithreading_checkbox.isChecked():
resource_data['_7zzMultiThread'] = 'on'
else:
# Remove the setting if unchecked (don't add 'off')
resource_data.pop('_7zzMultiThread', None)
self.resource_settings[k] = resource_data self.resource_settings[k] = resource_data
# Save bandwidth limit to Downloads resource MaxThroughput (only if bandwidth UI exists) # Save bandwidth limit to Downloads resource MaxThroughput (only if bandwidth UI exists)

View File

@@ -1757,6 +1757,10 @@ class InstallModlistScreen(QWidget):
MessageService.critical(self, "Steam Restart Failed", "Failed to restart Steam automatically. Please restart Steam manually, then try again.") MessageService.critical(self, "Steam Restart Failed", "Failed to restart Steam automatically. Please restart Steam manually, then try again.")
def start_automated_prefix_workflow(self): def start_automated_prefix_workflow(self):
# Ensure _current_resolution is always set before starting workflow
if not hasattr(self, '_current_resolution') or self._current_resolution is None:
resolution = self.resolution_combo.currentText() if hasattr(self, 'resolution_combo') else None
self._current_resolution = resolution.split()[0] if resolution and resolution != "Leave unchanged" else None
"""Start the automated prefix creation workflow""" """Start the automated prefix creation workflow"""
try: try:
# Disable controls during installation # Disable controls during installation
@@ -2382,7 +2386,7 @@ class InstallModlistScreen(QWidget):
# This shouldn't happen since automated prefix creation is complete # This shouldn't happen since automated prefix creation is complete
self.progress_update.emit(f"Unexpected manual steps callback for {modlist_name}") self.progress_update.emit(f"Unexpected manual steps callback for {modlist_name}")
# Call the service method for post-Steam configuration # Call the service method for post-Steam configuration
result = modlist_service.configure_modlist_post_steam( result = modlist_service.configure_modlist_post_steam(
context=modlist_context, context=modlist_context,
progress_callback=progress_callback, progress_callback=progress_callback,

View File

@@ -222,15 +222,21 @@ class ValidationHandler:
def validate_steam_shortcut(self, app_id: str) -> Tuple[bool, str]: def validate_steam_shortcut(self, app_id: str) -> Tuple[bool, str]:
"""Validate a Steam shortcut.""" """Validate a Steam shortcut."""
try: try:
# Check if shortcuts.vdf exists # Use native Steam service to get proper shortcuts.vdf path with multi-user support
shortcuts_path = Path.home() / '.steam' / 'steam' / 'userdata' / '75424832' / 'config' / 'shortcuts.vdf' from jackify.backend.services.native_steam_service import NativeSteamService
steam_service = NativeSteamService()
shortcuts_path = steam_service.get_shortcuts_vdf_path()
if not shortcuts_path:
return False, "Could not determine shortcuts.vdf path (no active Steam user found)"
if not shortcuts_path.exists(): if not shortcuts_path.exists():
return False, "shortcuts.vdf not found" return False, "shortcuts.vdf not found"
# Check if shortcuts.vdf is accessible # Check if shortcuts.vdf is accessible
if not os.access(shortcuts_path, os.R_OK | os.W_OK): if not os.access(shortcuts_path, os.R_OK | os.W_OK):
return False, "shortcuts.vdf is not accessible" return False, "shortcuts.vdf is not accessible"
# Parse shortcuts.vdf using VDFHandler # Parse shortcuts.vdf using VDFHandler
shortcuts_data = VDFHandler.load(str(shortcuts_path), binary=True) shortcuts_data = VDFHandler.load(str(shortcuts_path), binary=True)