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
+1 -1
View File
@@ -5,4 +5,4 @@ This package provides both CLI and GUI interfaces for managing
Wabbajack modlists natively on Linux systems.
"""
__version__ = "0.1.5.1"
__version__ = "0.1.5.2"
+1 -1
View File
@@ -30,7 +30,7 @@ def _get_user_proton_version():
from jackify.backend.handlers.wine_utils import WineUtils
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':
# Use enhanced fallback logic with GE-Proton preference
@@ -496,6 +496,42 @@ class ConfigHandler:
logger.error(f"Error saving modlist downloads base directory: {e}")
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):
"""Auto-detect and set best Proton version (includes GE-Proton and Valve Proton)"""
try:
+26 -4
View File
@@ -692,10 +692,32 @@ class ModlistHandler:
print("Error: Could not determine wine prefix location.")
return False
if not self.winetricks_handler.install_wine_components(wineprefix, self.game_var_full, specific_components=components):
self.logger.error("Failed to install Wine components. Configuration aborted.")
print("Error: Failed to install necessary Wine components.")
return False # Abort on failure
# Try winetricks first (preferred method with current fix)
winetricks_success = False
try:
self.logger.info("Attempting Wine component installation using winetricks...")
winetricks_success = self.winetricks_handler.install_wine_components(wineprefix, self.game_var_full, specific_components=components)
if winetricks_success:
self.logger.info("Winetricks installation completed successfully")
except Exception as e:
self.logger.warning(f"Winetricks installation failed with exception: {e}")
winetricks_success = False
# Fallback to protontricks if winetricks failed
if not winetricks_success:
self.logger.warning("Winetricks failed, falling back to protontricks for Wine component installation...")
try:
protontricks_success = self.protontricks_handler.install_wine_components(target_appid, self.game_var_full, specific_components=components)
if protontricks_success:
self.logger.info("Protontricks fallback installation completed successfully")
else:
self.logger.error("Both winetricks and protontricks failed to install Wine components.")
print("Error: Failed to install necessary Wine components using both winetricks and protontricks.")
return False
except Exception as e:
self.logger.error(f"Protontricks fallback also failed with exception: {e}")
print("Error: Failed to install necessary Wine components using both winetricks and protontricks.")
return False
self.logger.info("Step 4: Installing Wine components... Done")
# Step 5: Ensure permissions of Modlist directory
+53 -36
View File
@@ -630,47 +630,64 @@ class PathHandler:
# Moved _find_shortcuts_vdf here from ShortcutHandler
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
first found shortcuts.vdf file.
Uses proper multi-user detection to find the correct Steam user instead
of just taking the first found user directory.
Returns:
Optional[str]: The full path to the shortcuts.vdf file, or None if not found.
"""
# This implementation was moved from ShortcutHandler
userdata_base_paths = [
os.path.expanduser("~/.steam/steam/userdata"),
os.path.expanduser("~/.local/share/Steam/userdata"),
os.path.expanduser("~/.var/app/com.valvesoftware.Steam/.local/share/Steam/userdata")
]
found_vdf_path = None
for base_path in userdata_base_paths:
if not os.path.isdir(base_path):
logger.debug(f"Userdata base path not found or not a directory: {base_path}")
continue
logger.debug(f"Searching for user IDs in: {base_path}")
try:
for item in os.listdir(base_path):
user_path = os.path.join(base_path, item)
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
try:
# Use native Steam service for proper multi-user detection
from jackify.backend.services.native_steam_service import NativeSteamService
steam_service = NativeSteamService()
shortcuts_path = steam_service.get_shortcuts_vdf_path()
if shortcuts_path:
logger.info(f"Found shortcuts.vdf using multi-user detection: {shortcuts_path}")
return str(shortcuts_path)
else:
logger.error("Could not determine shortcuts.vdf path using multi-user detection")
return None
except Exception as e:
logger.error(f"Error using multi-user detection for shortcuts.vdf: {e}")
# Fallback to legacy behavior if multi-user detection fails
logger.warning("Falling back to legacy shortcuts.vdf detection (first-found user)")
userdata_base_paths = [
os.path.expanduser("~/.steam/steam/userdata"),
os.path.expanduser("~/.local/share/Steam/userdata"),
os.path.expanduser("~/.var/app/com.valvesoftware.Steam/.local/share/Steam/userdata")
]
found_vdf_path = None
for base_path in userdata_base_paths:
if not os.path.isdir(base_path):
logger.debug(f"Userdata base path not found or not a directory: {base_path}")
continue
logger.debug(f"Searching for user IDs in: {base_path}")
try:
for item in os.listdir(base_path):
user_path = os.path.join(base_path, item)
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
def find_game_install_paths(target_appids: Dict[str, str]) -> Dict[str, Path]:
+10 -4
View File
@@ -222,15 +222,21 @@ class ValidationHandler:
def validate_steam_shortcut(self, app_id: str) -> Tuple[bool, str]:
"""Validate a Steam shortcut."""
try:
# Check if shortcuts.vdf exists
shortcuts_path = Path.home() / '.steam' / 'steam' / 'userdata' / '75424832' / 'config' / 'shortcuts.vdf'
# Use native Steam service to get proper shortcuts.vdf path with multi-user support
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():
return False, "shortcuts.vdf not found"
# Check if shortcuts.vdf is accessible
if not os.access(shortcuts_path, os.R_OK | os.W_OK):
return False, "shortcuts.vdf is not accessible"
# Parse shortcuts.vdf using VDFHandler
shortcuts_data = VDFHandler.load(str(shortcuts_path), binary=True)
+1 -1
View File
@@ -709,7 +709,7 @@ class WineUtils:
try:
from .config_handler import ConfigHandler
config = ConfigHandler()
fallback_path = config.get('proton_path', 'auto')
fallback_path = config.get_proton_path()
if fallback_path != 'auto':
fallback_wine_bin = Path(fallback_path) / "files/bin/wine"
if fallback_wine_bin.is_file():
+46 -2
View File
@@ -137,7 +137,7 @@ class WinetricksHandler:
from ..handlers.wine_utils import WineUtils
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
wine_binary = None
@@ -181,6 +181,49 @@ class WinetricksHandler:
env['WINE'] = str(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:
self.logger.error(f"Cannot run winetricks: Failed to get Proton wine binary: {e}")
return False
@@ -426,7 +469,8 @@ class WinetricksHandler:
except Exception as 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:
self.logger.error(f"Error installing {component} (attempt {attempt}): {e}")
@@ -46,7 +46,7 @@ class AutomatedPrefixService:
from jackify.backend.handlers.wine_utils import WineUtils
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':
# Use enhanced fallback logic with GE-Proton preference
@@ -2705,7 +2705,7 @@ echo Prefix creation complete.
from jackify.backend.handlers.wine_utils import WineUtils
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_proton_path != 'auto':
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+225 -225
View File
@@ -7,7 +7,7 @@
"targets": {
".NETCoreApp,Version=v8.0": {},
".NETCoreApp,Version=v8.0/linux-x64": {
"jackify-engine/0.3.15": {
"jackify-engine/0.3.16": {
"dependencies": {
"Markdig": "0.40.0",
"Microsoft.Extensions.Configuration.Json": "9.0.1",
@@ -22,16 +22,16 @@
"SixLabors.ImageSharp": "3.1.6",
"System.CommandLine": "2.0.0-beta4.22272.1",
"System.CommandLine.NamingConventionBinder": "2.0.0-beta4.22272.1",
"Wabbajack.CLI.Builder": "0.3.15",
"Wabbajack.Downloaders.Bethesda": "0.3.15",
"Wabbajack.Downloaders.Dispatcher": "0.3.15",
"Wabbajack.Hashing.xxHash64": "0.3.15",
"Wabbajack.Networking.Discord": "0.3.15",
"Wabbajack.Networking.GitHub": "0.3.15",
"Wabbajack.Paths.IO": "0.3.15",
"Wabbajack.Server.Lib": "0.3.15",
"Wabbajack.Services.OSIntegrated": "0.3.15",
"Wabbajack.VFS": "0.3.15",
"Wabbajack.CLI.Builder": "0.3.16",
"Wabbajack.Downloaders.Bethesda": "0.3.16",
"Wabbajack.Downloaders.Dispatcher": "0.3.16",
"Wabbajack.Hashing.xxHash64": "0.3.16",
"Wabbajack.Networking.Discord": "0.3.16",
"Wabbajack.Networking.GitHub": "0.3.16",
"Wabbajack.Paths.IO": "0.3.16",
"Wabbajack.Server.Lib": "0.3.16",
"Wabbajack.Services.OSIntegrated": "0.3.16",
"Wabbajack.VFS": "0.3.16",
"MegaApiClient": "1.0.0.0",
"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": {
"Microsoft.Extensions.Configuration.Json": "9.0.1",
"Microsoft.Extensions.DependencyInjection": "9.0.1",
@@ -1791,109 +1791,109 @@
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
"System.CommandLine": "2.0.0-beta4.22272.1",
"System.CommandLine.NamingConventionBinder": "2.0.0-beta4.22272.1",
"Wabbajack.Paths": "0.3.15"
"Wabbajack.Paths": "0.3.16"
},
"runtime": {
"Wabbajack.CLI.Builder.dll": {}
}
},
"Wabbajack.Common/0.3.15": {
"Wabbajack.Common/0.3.16": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
"System.Reactive": "6.0.1",
"Wabbajack.DTOs": "0.3.15",
"Wabbajack.Networking.Http": "0.3.15",
"Wabbajack.Paths.IO": "0.3.15"
"Wabbajack.DTOs": "0.3.16",
"Wabbajack.Networking.Http": "0.3.16",
"Wabbajack.Paths.IO": "0.3.16"
},
"runtime": {
"Wabbajack.Common.dll": {}
}
},
"Wabbajack.Compiler/0.3.15": {
"Wabbajack.Compiler/0.3.16": {
"dependencies": {
"F23.StringSimilarity": "6.0.0",
"Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Newtonsoft.Json": "13.0.3",
"SixLabors.ImageSharp": "3.1.6",
"Wabbajack.Downloaders.Dispatcher": "0.3.15",
"Wabbajack.Installer": "0.3.15",
"Wabbajack.VFS": "0.3.15",
"Wabbajack.Downloaders.Dispatcher": "0.3.16",
"Wabbajack.Installer": "0.3.16",
"Wabbajack.VFS": "0.3.16",
"ini-parser-netstandard": "2.5.2"
},
"runtime": {
"Wabbajack.Compiler.dll": {}
}
},
"Wabbajack.Compression.BSA/0.3.15": {
"Wabbajack.Compression.BSA/0.3.16": {
"dependencies": {
"K4os.Compression.LZ4.Streams": "1.3.8",
"Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"SharpZipLib": "1.4.2",
"Wabbajack.Common": "0.3.15",
"Wabbajack.DTOs": "0.3.15"
"Wabbajack.Common": "0.3.16",
"Wabbajack.DTOs": "0.3.16"
},
"runtime": {
"Wabbajack.Compression.BSA.dll": {}
}
},
"Wabbajack.Compression.Zip/0.3.15": {
"Wabbajack.Compression.Zip/0.3.16": {
"dependencies": {
"Wabbajack.IO.Async": "0.3.15"
"Wabbajack.IO.Async": "0.3.16"
},
"runtime": {
"Wabbajack.Compression.Zip.dll": {}
}
},
"Wabbajack.Configuration/0.3.15": {
"Wabbajack.Configuration/0.3.16": {
"runtime": {
"Wabbajack.Configuration.dll": {}
}
},
"Wabbajack.Downloaders.Bethesda/0.3.15": {
"Wabbajack.Downloaders.Bethesda/0.3.16": {
"dependencies": {
"LibAES-CTR": "1.1.0",
"Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"SharpZipLib": "1.4.2",
"Wabbajack.Common": "0.3.15",
"Wabbajack.Downloaders.Interfaces": "0.3.15",
"Wabbajack.Networking.BethesdaNet": "0.3.15"
"Wabbajack.Common": "0.3.16",
"Wabbajack.Downloaders.Interfaces": "0.3.16",
"Wabbajack.Networking.BethesdaNet": "0.3.16"
},
"runtime": {
"Wabbajack.Downloaders.Bethesda.dll": {}
}
},
"Wabbajack.Downloaders.Dispatcher/0.3.15": {
"Wabbajack.Downloaders.Dispatcher/0.3.16": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
"Newtonsoft.Json": "13.0.3",
"SixLabors.ImageSharp": "3.1.6",
"Wabbajack.Downloaders.Bethesda": "0.3.15",
"Wabbajack.Downloaders.GameFile": "0.3.15",
"Wabbajack.Downloaders.GoogleDrive": "0.3.15",
"Wabbajack.Downloaders.Http": "0.3.15",
"Wabbajack.Downloaders.IPS4OAuth2Downloader": "0.3.15",
"Wabbajack.Downloaders.Interfaces": "0.3.15",
"Wabbajack.Downloaders.Manual": "0.3.15",
"Wabbajack.Downloaders.MediaFire": "0.3.15",
"Wabbajack.Downloaders.Mega": "0.3.15",
"Wabbajack.Downloaders.ModDB": "0.3.15",
"Wabbajack.Downloaders.Nexus": "0.3.15",
"Wabbajack.Downloaders.VerificationCache": "0.3.15",
"Wabbajack.Downloaders.WabbajackCDN": "0.3.15",
"Wabbajack.Networking.WabbajackClientApi": "0.3.15"
"Wabbajack.Downloaders.Bethesda": "0.3.16",
"Wabbajack.Downloaders.GameFile": "0.3.16",
"Wabbajack.Downloaders.GoogleDrive": "0.3.16",
"Wabbajack.Downloaders.Http": "0.3.16",
"Wabbajack.Downloaders.IPS4OAuth2Downloader": "0.3.16",
"Wabbajack.Downloaders.Interfaces": "0.3.16",
"Wabbajack.Downloaders.Manual": "0.3.16",
"Wabbajack.Downloaders.MediaFire": "0.3.16",
"Wabbajack.Downloaders.Mega": "0.3.16",
"Wabbajack.Downloaders.ModDB": "0.3.16",
"Wabbajack.Downloaders.Nexus": "0.3.16",
"Wabbajack.Downloaders.VerificationCache": "0.3.16",
"Wabbajack.Downloaders.WabbajackCDN": "0.3.16",
"Wabbajack.Networking.WabbajackClientApi": "0.3.16"
},
"runtime": {
"Wabbajack.Downloaders.Dispatcher.dll": {}
}
},
"Wabbajack.Downloaders.GameFile/0.3.15": {
"Wabbajack.Downloaders.GameFile/0.3.16": {
"dependencies": {
"GameFinder.StoreHandlers.EADesktop": "4.5.0",
"GameFinder.StoreHandlers.EGS": "4.5.0",
@@ -1903,360 +1903,360 @@
"Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"SixLabors.ImageSharp": "3.1.6",
"Wabbajack.Downloaders.Interfaces": "0.3.15",
"Wabbajack.VFS": "0.3.15"
"Wabbajack.Downloaders.Interfaces": "0.3.16",
"Wabbajack.VFS": "0.3.16"
},
"runtime": {
"Wabbajack.Downloaders.GameFile.dll": {}
}
},
"Wabbajack.Downloaders.GoogleDrive/0.3.15": {
"Wabbajack.Downloaders.GoogleDrive/0.3.16": {
"dependencies": {
"HtmlAgilityPack": "1.11.72",
"Microsoft.AspNetCore.Http.Extensions": "2.3.0",
"Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
"Wabbajack.Common": "0.3.15",
"Wabbajack.DTOs": "0.3.15",
"Wabbajack.Downloaders.Interfaces": "0.3.15",
"Wabbajack.Networking.Http": "0.3.15",
"Wabbajack.Networking.Http.Interfaces": "0.3.15"
"Wabbajack.Common": "0.3.16",
"Wabbajack.DTOs": "0.3.16",
"Wabbajack.Downloaders.Interfaces": "0.3.16",
"Wabbajack.Networking.Http": "0.3.16",
"Wabbajack.Networking.Http.Interfaces": "0.3.16"
},
"runtime": {
"Wabbajack.Downloaders.GoogleDrive.dll": {}
}
},
"Wabbajack.Downloaders.Http/0.3.15": {
"Wabbajack.Downloaders.Http/0.3.16": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
"Wabbajack.Common": "0.3.15",
"Wabbajack.DTOs": "0.3.15",
"Wabbajack.Downloaders.Interfaces": "0.3.15",
"Wabbajack.Networking.BethesdaNet": "0.3.15",
"Wabbajack.Networking.Http.Interfaces": "0.3.15",
"Wabbajack.Paths.IO": "0.3.15"
"Wabbajack.Common": "0.3.16",
"Wabbajack.DTOs": "0.3.16",
"Wabbajack.Downloaders.Interfaces": "0.3.16",
"Wabbajack.Networking.BethesdaNet": "0.3.16",
"Wabbajack.Networking.Http.Interfaces": "0.3.16",
"Wabbajack.Paths.IO": "0.3.16"
},
"runtime": {
"Wabbajack.Downloaders.Http.dll": {}
}
},
"Wabbajack.Downloaders.Interfaces/0.3.15": {
"Wabbajack.Downloaders.Interfaces/0.3.16": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Wabbajack.Compression.Zip": "0.3.15",
"Wabbajack.DTOs": "0.3.15",
"Wabbajack.Paths.IO": "0.3.15"
"Wabbajack.Compression.Zip": "0.3.16",
"Wabbajack.DTOs": "0.3.16",
"Wabbajack.Paths.IO": "0.3.16"
},
"runtime": {
"Wabbajack.Downloaders.Interfaces.dll": {}
}
},
"Wabbajack.Downloaders.IPS4OAuth2Downloader/0.3.15": {
"Wabbajack.Downloaders.IPS4OAuth2Downloader/0.3.16": {
"dependencies": {
"F23.StringSimilarity": "6.0.0",
"Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
"Wabbajack.Common": "0.3.15",
"Wabbajack.Downloaders.Interfaces": "0.3.15",
"Wabbajack.Networking.Http": "0.3.15",
"Wabbajack.Networking.Http.Interfaces": "0.3.15"
"Wabbajack.Common": "0.3.16",
"Wabbajack.Downloaders.Interfaces": "0.3.16",
"Wabbajack.Networking.Http": "0.3.16",
"Wabbajack.Networking.Http.Interfaces": "0.3.16"
},
"runtime": {
"Wabbajack.Downloaders.IPS4OAuth2Downloader.dll": {}
}
},
"Wabbajack.Downloaders.Manual/0.3.15": {
"Wabbajack.Downloaders.Manual/0.3.16": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
"Wabbajack.Common": "0.3.15",
"Wabbajack.Downloaders.Interfaces": "0.3.15"
"Wabbajack.Common": "0.3.16",
"Wabbajack.Downloaders.Interfaces": "0.3.16"
},
"runtime": {
"Wabbajack.Downloaders.Manual.dll": {}
}
},
"Wabbajack.Downloaders.MediaFire/0.3.15": {
"Wabbajack.Downloaders.MediaFire/0.3.16": {
"dependencies": {
"HtmlAgilityPack": "1.11.72",
"Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
"Wabbajack.Common": "0.3.15",
"Wabbajack.Downloaders.Interfaces": "0.3.15",
"Wabbajack.Networking.Http.Interfaces": "0.3.15"
"Wabbajack.Common": "0.3.16",
"Wabbajack.Downloaders.Interfaces": "0.3.16",
"Wabbajack.Networking.Http.Interfaces": "0.3.16"
},
"runtime": {
"Wabbajack.Downloaders.MediaFire.dll": {}
}
},
"Wabbajack.Downloaders.Mega/0.3.15": {
"Wabbajack.Downloaders.Mega/0.3.16": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
"Newtonsoft.Json": "13.0.3",
"Wabbajack.Common": "0.3.15",
"Wabbajack.Downloaders.Interfaces": "0.3.15",
"Wabbajack.Paths.IO": "0.3.15"
"Wabbajack.Common": "0.3.16",
"Wabbajack.Downloaders.Interfaces": "0.3.16",
"Wabbajack.Paths.IO": "0.3.16"
},
"runtime": {
"Wabbajack.Downloaders.Mega.dll": {}
}
},
"Wabbajack.Downloaders.ModDB/0.3.15": {
"Wabbajack.Downloaders.ModDB/0.3.16": {
"dependencies": {
"HtmlAgilityPack": "1.11.72",
"Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
"Newtonsoft.Json": "13.0.3",
"Wabbajack.Common": "0.3.15",
"Wabbajack.Downloaders.Interfaces": "0.3.15",
"Wabbajack.Networking.Http": "0.3.15",
"Wabbajack.Networking.Http.Interfaces": "0.3.15"
"Wabbajack.Common": "0.3.16",
"Wabbajack.Downloaders.Interfaces": "0.3.16",
"Wabbajack.Networking.Http": "0.3.16",
"Wabbajack.Networking.Http.Interfaces": "0.3.16"
},
"runtime": {
"Wabbajack.Downloaders.ModDB.dll": {}
}
},
"Wabbajack.Downloaders.Nexus/0.3.15": {
"Wabbajack.Downloaders.Nexus/0.3.16": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Wabbajack.DTOs": "0.3.15",
"Wabbajack.Downloaders.Interfaces": "0.3.15",
"Wabbajack.Hashing.xxHash64": "0.3.15",
"Wabbajack.Networking.Http": "0.3.15",
"Wabbajack.Networking.Http.Interfaces": "0.3.15",
"Wabbajack.Networking.NexusApi": "0.3.15",
"Wabbajack.Paths": "0.3.15"
"Wabbajack.DTOs": "0.3.16",
"Wabbajack.Downloaders.Interfaces": "0.3.16",
"Wabbajack.Hashing.xxHash64": "0.3.16",
"Wabbajack.Networking.Http": "0.3.16",
"Wabbajack.Networking.Http.Interfaces": "0.3.16",
"Wabbajack.Networking.NexusApi": "0.3.16",
"Wabbajack.Paths": "0.3.16"
},
"runtime": {
"Wabbajack.Downloaders.Nexus.dll": {}
}
},
"Wabbajack.Downloaders.VerificationCache/0.3.15": {
"Wabbajack.Downloaders.VerificationCache/0.3.16": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
"Stub.System.Data.SQLite.Core.NetStandard": "1.0.119",
"Wabbajack.DTOs": "0.3.15",
"Wabbajack.Paths.IO": "0.3.15"
"Wabbajack.DTOs": "0.3.16",
"Wabbajack.Paths.IO": "0.3.16"
},
"runtime": {
"Wabbajack.Downloaders.VerificationCache.dll": {}
}
},
"Wabbajack.Downloaders.WabbajackCDN/0.3.15": {
"Wabbajack.Downloaders.WabbajackCDN/0.3.16": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
"Microsoft.Toolkit.HighPerformance": "7.1.2",
"Wabbajack.Common": "0.3.15",
"Wabbajack.Downloaders.Interfaces": "0.3.15",
"Wabbajack.Networking.Http": "0.3.15",
"Wabbajack.RateLimiter": "0.3.15"
"Wabbajack.Common": "0.3.16",
"Wabbajack.Downloaders.Interfaces": "0.3.16",
"Wabbajack.Networking.Http": "0.3.16",
"Wabbajack.RateLimiter": "0.3.16"
},
"runtime": {
"Wabbajack.Downloaders.WabbajackCDN.dll": {}
}
},
"Wabbajack.DTOs/0.3.15": {
"Wabbajack.DTOs/0.3.16": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Wabbajack.Hashing.xxHash64": "0.3.15",
"Wabbajack.Paths": "0.3.15"
"Wabbajack.Hashing.xxHash64": "0.3.16",
"Wabbajack.Paths": "0.3.16"
},
"runtime": {
"Wabbajack.DTOs.dll": {}
}
},
"Wabbajack.FileExtractor/0.3.15": {
"Wabbajack.FileExtractor/0.3.16": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
"OMODFramework": "3.0.1",
"Wabbajack.Common": "0.3.15",
"Wabbajack.Compression.BSA": "0.3.15",
"Wabbajack.Hashing.PHash": "0.3.15",
"Wabbajack.Paths": "0.3.15"
"Wabbajack.Common": "0.3.16",
"Wabbajack.Compression.BSA": "0.3.16",
"Wabbajack.Hashing.PHash": "0.3.16",
"Wabbajack.Paths": "0.3.16"
},
"runtime": {
"Wabbajack.FileExtractor.dll": {}
}
},
"Wabbajack.Hashing.PHash/0.3.15": {
"Wabbajack.Hashing.PHash/0.3.16": {
"dependencies": {
"BCnEncoder.Net.ImageSharp": "1.1.1",
"Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Shipwreck.Phash": "0.5.0",
"SixLabors.ImageSharp": "3.1.6",
"Wabbajack.Common": "0.3.15",
"Wabbajack.DTOs": "0.3.15",
"Wabbajack.Paths": "0.3.15",
"Wabbajack.Paths.IO": "0.3.15"
"Wabbajack.Common": "0.3.16",
"Wabbajack.DTOs": "0.3.16",
"Wabbajack.Paths": "0.3.16",
"Wabbajack.Paths.IO": "0.3.16"
},
"runtime": {
"Wabbajack.Hashing.PHash.dll": {}
}
},
"Wabbajack.Hashing.xxHash64/0.3.15": {
"Wabbajack.Hashing.xxHash64/0.3.16": {
"dependencies": {
"Wabbajack.Paths": "0.3.15",
"Wabbajack.RateLimiter": "0.3.15"
"Wabbajack.Paths": "0.3.16",
"Wabbajack.RateLimiter": "0.3.16"
},
"runtime": {
"Wabbajack.Hashing.xxHash64.dll": {}
}
},
"Wabbajack.Installer/0.3.15": {
"Wabbajack.Installer/0.3.16": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Newtonsoft.Json": "13.0.3",
"Octopus.Octodiff": "2.0.548",
"SixLabors.ImageSharp": "3.1.6",
"Wabbajack.DTOs": "0.3.15",
"Wabbajack.Downloaders.Dispatcher": "0.3.15",
"Wabbajack.Downloaders.GameFile": "0.3.15",
"Wabbajack.FileExtractor": "0.3.15",
"Wabbajack.Networking.WabbajackClientApi": "0.3.15",
"Wabbajack.Paths": "0.3.15",
"Wabbajack.Paths.IO": "0.3.15",
"Wabbajack.VFS": "0.3.15",
"Wabbajack.DTOs": "0.3.16",
"Wabbajack.Downloaders.Dispatcher": "0.3.16",
"Wabbajack.Downloaders.GameFile": "0.3.16",
"Wabbajack.FileExtractor": "0.3.16",
"Wabbajack.Networking.WabbajackClientApi": "0.3.16",
"Wabbajack.Paths": "0.3.16",
"Wabbajack.Paths.IO": "0.3.16",
"Wabbajack.VFS": "0.3.16",
"ini-parser-netstandard": "2.5.2"
},
"runtime": {
"Wabbajack.Installer.dll": {}
}
},
"Wabbajack.IO.Async/0.3.15": {
"Wabbajack.IO.Async/0.3.16": {
"runtime": {
"Wabbajack.IO.Async.dll": {}
}
},
"Wabbajack.Networking.BethesdaNet/0.3.15": {
"Wabbajack.Networking.BethesdaNet/0.3.16": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Wabbajack.DTOs": "0.3.15",
"Wabbajack.Networking.Http": "0.3.15",
"Wabbajack.Networking.Http.Interfaces": "0.3.15"
"Wabbajack.DTOs": "0.3.16",
"Wabbajack.Networking.Http": "0.3.16",
"Wabbajack.Networking.Http.Interfaces": "0.3.16"
},
"runtime": {
"Wabbajack.Networking.BethesdaNet.dll": {}
}
},
"Wabbajack.Networking.Discord/0.3.15": {
"Wabbajack.Networking.Discord/0.3.16": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection.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": {
"Wabbajack.Networking.Discord.dll": {}
}
},
"Wabbajack.Networking.GitHub/0.3.15": {
"Wabbajack.Networking.GitHub/0.3.16": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
"Octokit": "14.0.0",
"Wabbajack.DTOs": "0.3.15",
"Wabbajack.Networking.Http.Interfaces": "0.3.15"
"Wabbajack.DTOs": "0.3.16",
"Wabbajack.Networking.Http.Interfaces": "0.3.16"
},
"runtime": {
"Wabbajack.Networking.GitHub.dll": {}
}
},
"Wabbajack.Networking.Http/0.3.15": {
"Wabbajack.Networking.Http/0.3.16": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Microsoft.Extensions.Http": "9.0.1",
"Microsoft.Extensions.Logging": "9.0.1",
"Wabbajack.Configuration": "0.3.15",
"Wabbajack.Downloaders.Interfaces": "0.3.15",
"Wabbajack.Hashing.xxHash64": "0.3.15",
"Wabbajack.Networking.Http.Interfaces": "0.3.15",
"Wabbajack.Paths": "0.3.15",
"Wabbajack.Paths.IO": "0.3.15"
"Wabbajack.Configuration": "0.3.16",
"Wabbajack.Downloaders.Interfaces": "0.3.16",
"Wabbajack.Hashing.xxHash64": "0.3.16",
"Wabbajack.Networking.Http.Interfaces": "0.3.16",
"Wabbajack.Paths": "0.3.16",
"Wabbajack.Paths.IO": "0.3.16"
},
"runtime": {
"Wabbajack.Networking.Http.dll": {}
}
},
"Wabbajack.Networking.Http.Interfaces/0.3.15": {
"Wabbajack.Networking.Http.Interfaces/0.3.16": {
"dependencies": {
"Wabbajack.Hashing.xxHash64": "0.3.15"
"Wabbajack.Hashing.xxHash64": "0.3.16"
},
"runtime": {
"Wabbajack.Networking.Http.Interfaces.dll": {}
}
},
"Wabbajack.Networking.NexusApi/0.3.15": {
"Wabbajack.Networking.NexusApi/0.3.16": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
"Wabbajack.DTOs": "0.3.15",
"Wabbajack.Networking.Http": "0.3.15",
"Wabbajack.Networking.Http.Interfaces": "0.3.15",
"Wabbajack.Networking.WabbajackClientApi": "0.3.15"
"Wabbajack.DTOs": "0.3.16",
"Wabbajack.Networking.Http": "0.3.16",
"Wabbajack.Networking.Http.Interfaces": "0.3.16",
"Wabbajack.Networking.WabbajackClientApi": "0.3.16"
},
"runtime": {
"Wabbajack.Networking.NexusApi.dll": {}
}
},
"Wabbajack.Networking.WabbajackClientApi/0.3.15": {
"Wabbajack.Networking.WabbajackClientApi/0.3.16": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
"Octokit": "14.0.0",
"Wabbajack.Common": "0.3.15",
"Wabbajack.DTOs": "0.3.15",
"Wabbajack.Paths.IO": "0.3.15",
"Wabbajack.VFS.Interfaces": "0.3.15",
"Wabbajack.Common": "0.3.16",
"Wabbajack.DTOs": "0.3.16",
"Wabbajack.Paths.IO": "0.3.16",
"Wabbajack.VFS.Interfaces": "0.3.16",
"YamlDotNet": "16.3.0"
},
"runtime": {
"Wabbajack.Networking.WabbajackClientApi.dll": {}
}
},
"Wabbajack.Paths/0.3.15": {
"Wabbajack.Paths/0.3.16": {
"runtime": {
"Wabbajack.Paths.dll": {}
}
},
"Wabbajack.Paths.IO/0.3.15": {
"Wabbajack.Paths.IO/0.3.16": {
"dependencies": {
"Wabbajack.Paths": "0.3.15",
"Wabbajack.Paths": "0.3.16",
"shortid": "4.0.0"
},
"runtime": {
"Wabbajack.Paths.IO.dll": {}
}
},
"Wabbajack.RateLimiter/0.3.15": {
"Wabbajack.RateLimiter/0.3.16": {
"runtime": {
"Wabbajack.RateLimiter.dll": {}
}
},
"Wabbajack.Server.Lib/0.3.15": {
"Wabbajack.Server.Lib/0.3.16": {
"dependencies": {
"FluentFTP": "52.0.0",
"Microsoft.Extensions.DependencyInjection": "9.0.1",
@@ -2264,58 +2264,58 @@
"Nettle": "3.0.0",
"Newtonsoft.Json": "13.0.3",
"SixLabors.ImageSharp": "3.1.6",
"Wabbajack.Common": "0.3.15",
"Wabbajack.Networking.Http.Interfaces": "0.3.15",
"Wabbajack.Services.OSIntegrated": "0.3.15"
"Wabbajack.Common": "0.3.16",
"Wabbajack.Networking.Http.Interfaces": "0.3.16",
"Wabbajack.Services.OSIntegrated": "0.3.16"
},
"runtime": {
"Wabbajack.Server.Lib.dll": {}
}
},
"Wabbajack.Services.OSIntegrated/0.3.15": {
"Wabbajack.Services.OSIntegrated/0.3.16": {
"dependencies": {
"DeviceId": "6.8.0",
"Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Newtonsoft.Json": "13.0.3",
"SixLabors.ImageSharp": "3.1.6",
"Wabbajack.Compiler": "0.3.15",
"Wabbajack.Downloaders.Dispatcher": "0.3.15",
"Wabbajack.Installer": "0.3.15",
"Wabbajack.Networking.BethesdaNet": "0.3.15",
"Wabbajack.Networking.Discord": "0.3.15",
"Wabbajack.VFS": "0.3.15"
"Wabbajack.Compiler": "0.3.16",
"Wabbajack.Downloaders.Dispatcher": "0.3.16",
"Wabbajack.Installer": "0.3.16",
"Wabbajack.Networking.BethesdaNet": "0.3.16",
"Wabbajack.Networking.Discord": "0.3.16",
"Wabbajack.VFS": "0.3.16"
},
"runtime": {
"Wabbajack.Services.OSIntegrated.dll": {}
}
},
"Wabbajack.VFS/0.3.15": {
"Wabbajack.VFS/0.3.16": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
"SixLabors.ImageSharp": "3.1.6",
"System.Data.SQLite.Core": "1.0.119",
"Wabbajack.Common": "0.3.15",
"Wabbajack.FileExtractor": "0.3.15",
"Wabbajack.Hashing.PHash": "0.3.15",
"Wabbajack.Hashing.xxHash64": "0.3.15",
"Wabbajack.Paths": "0.3.15",
"Wabbajack.Paths.IO": "0.3.15",
"Wabbajack.VFS.Interfaces": "0.3.15"
"Wabbajack.Common": "0.3.16",
"Wabbajack.FileExtractor": "0.3.16",
"Wabbajack.Hashing.PHash": "0.3.16",
"Wabbajack.Hashing.xxHash64": "0.3.16",
"Wabbajack.Paths": "0.3.16",
"Wabbajack.Paths.IO": "0.3.16",
"Wabbajack.VFS.Interfaces": "0.3.16"
},
"runtime": {
"Wabbajack.VFS.dll": {}
}
},
"Wabbajack.VFS.Interfaces/0.3.15": {
"Wabbajack.VFS.Interfaces/0.3.16": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Wabbajack.DTOs": "0.3.15",
"Wabbajack.Hashing.xxHash64": "0.3.15",
"Wabbajack.Paths": "0.3.15"
"Wabbajack.DTOs": "0.3.16",
"Wabbajack.Hashing.xxHash64": "0.3.16",
"Wabbajack.Paths": "0.3.16"
},
"runtime": {
"Wabbajack.VFS.Interfaces.dll": {}
@@ -2332,7 +2332,7 @@
}
},
"libraries": {
"jackify-engine/0.3.15": {
"jackify-engine/0.3.16": {
"type": "project",
"serviceable": false,
"sha512": ""
@@ -3021,202 +3021,202 @@
"path": "yamldotnet/16.3.0",
"hashPath": "yamldotnet.16.3.0.nupkg.sha512"
},
"Wabbajack.CLI.Builder/0.3.15": {
"Wabbajack.CLI.Builder/0.3.16": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"Wabbajack.Common/0.3.15": {
"Wabbajack.Common/0.3.16": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"Wabbajack.Compiler/0.3.15": {
"Wabbajack.Compiler/0.3.16": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"Wabbajack.Compression.BSA/0.3.15": {
"Wabbajack.Compression.BSA/0.3.16": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"Wabbajack.Compression.Zip/0.3.15": {
"Wabbajack.Compression.Zip/0.3.16": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"Wabbajack.Configuration/0.3.15": {
"Wabbajack.Configuration/0.3.16": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"Wabbajack.Downloaders.Bethesda/0.3.15": {
"Wabbajack.Downloaders.Bethesda/0.3.16": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"Wabbajack.Downloaders.Dispatcher/0.3.15": {
"Wabbajack.Downloaders.Dispatcher/0.3.16": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"Wabbajack.Downloaders.GameFile/0.3.15": {
"Wabbajack.Downloaders.GameFile/0.3.16": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"Wabbajack.Downloaders.GoogleDrive/0.3.15": {
"Wabbajack.Downloaders.GoogleDrive/0.3.16": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"Wabbajack.Downloaders.Http/0.3.15": {
"Wabbajack.Downloaders.Http/0.3.16": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"Wabbajack.Downloaders.Interfaces/0.3.15": {
"Wabbajack.Downloaders.Interfaces/0.3.16": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"Wabbajack.Downloaders.IPS4OAuth2Downloader/0.3.15": {
"Wabbajack.Downloaders.IPS4OAuth2Downloader/0.3.16": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"Wabbajack.Downloaders.Manual/0.3.15": {
"Wabbajack.Downloaders.Manual/0.3.16": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"Wabbajack.Downloaders.MediaFire/0.3.15": {
"Wabbajack.Downloaders.MediaFire/0.3.16": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"Wabbajack.Downloaders.Mega/0.3.15": {
"Wabbajack.Downloaders.Mega/0.3.16": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"Wabbajack.Downloaders.ModDB/0.3.15": {
"Wabbajack.Downloaders.ModDB/0.3.16": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"Wabbajack.Downloaders.Nexus/0.3.15": {
"Wabbajack.Downloaders.Nexus/0.3.16": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"Wabbajack.Downloaders.VerificationCache/0.3.15": {
"Wabbajack.Downloaders.VerificationCache/0.3.16": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"Wabbajack.Downloaders.WabbajackCDN/0.3.15": {
"Wabbajack.Downloaders.WabbajackCDN/0.3.16": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"Wabbajack.DTOs/0.3.15": {
"Wabbajack.DTOs/0.3.16": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"Wabbajack.FileExtractor/0.3.15": {
"Wabbajack.FileExtractor/0.3.16": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"Wabbajack.Hashing.PHash/0.3.15": {
"Wabbajack.Hashing.PHash/0.3.16": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"Wabbajack.Hashing.xxHash64/0.3.15": {
"Wabbajack.Hashing.xxHash64/0.3.16": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"Wabbajack.Installer/0.3.15": {
"Wabbajack.Installer/0.3.16": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"Wabbajack.IO.Async/0.3.15": {
"Wabbajack.IO.Async/0.3.16": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"Wabbajack.Networking.BethesdaNet/0.3.15": {
"Wabbajack.Networking.BethesdaNet/0.3.16": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"Wabbajack.Networking.Discord/0.3.15": {
"Wabbajack.Networking.Discord/0.3.16": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"Wabbajack.Networking.GitHub/0.3.15": {
"Wabbajack.Networking.GitHub/0.3.16": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"Wabbajack.Networking.Http/0.3.15": {
"Wabbajack.Networking.Http/0.3.16": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"Wabbajack.Networking.Http.Interfaces/0.3.15": {
"Wabbajack.Networking.Http.Interfaces/0.3.16": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"Wabbajack.Networking.NexusApi/0.3.15": {
"Wabbajack.Networking.NexusApi/0.3.16": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"Wabbajack.Networking.WabbajackClientApi/0.3.15": {
"Wabbajack.Networking.WabbajackClientApi/0.3.16": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"Wabbajack.Paths/0.3.15": {
"Wabbajack.Paths/0.3.16": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"Wabbajack.Paths.IO/0.3.15": {
"Wabbajack.Paths.IO/0.3.16": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"Wabbajack.RateLimiter/0.3.15": {
"Wabbajack.RateLimiter/0.3.16": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"Wabbajack.Server.Lib/0.3.15": {
"Wabbajack.Server.Lib/0.3.16": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"Wabbajack.Services.OSIntegrated/0.3.15": {
"Wabbajack.Services.OSIntegrated/0.3.16": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"Wabbajack.VFS/0.3.15": {
"Wabbajack.VFS/0.3.16": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"Wabbajack.VFS.Interfaces/0.3.15": {
"Wabbajack.VFS.Interfaces/0.3.16": {
"type": "project",
"serviceable": false,
"sha512": ""
Binary file not shown.
+228 -242
View File
@@ -102,7 +102,7 @@ sys.path.insert(0, str(src_dir))
from PySide6.QtWidgets import (
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.QtGui import QIcon
@@ -167,221 +167,26 @@ class SettingsDialog(QDialog):
self._original_debug_mode = self.config_handler.get('debug_mode', False)
self.setWindowTitle("Settings")
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; }")
main_layout = QVBoxLayout()
self.setLayout(main_layout)
# --- Resource Limits Section ---
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 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)
# Create tab widget
self.tab_widget = QTabWidget()
self.tab_widget.setStyleSheet("""
QTabWidget::pane { border: 1px solid #555; background: #232323; }
QTabBar::tab { background: #333; color: #eee; padding: 8px 16px; margin: 2px; }
QTabBar::tab:selected { background: #555; }
QTabBar::tab:hover { background: #444; }
""")
main_layout.addWidget(self.tab_widget)
# --- Debug & Diagnostics Section ---
debug_group = QGroupBox("Debug & Diagnostics")
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)
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)
# Create tabs
self._create_general_tab()
self._create_advanced_tab()
# --- Save/Close/Help Buttons ---
btn_layout = QHBoxLayout()
@@ -396,36 +201,224 @@ class SettingsDialog(QDialog):
close_btn.clicked.connect(self.reject)
btn_layout.addWidget(save_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.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:
print(f"[ERROR] Exception in SettingsDialog __init__: {e}")
print(f"[ERROR] Exception in SettingsDialog.__init__: {e}")
import traceback
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):
# 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():
resource_data = self.resource_settings.get(k, {})
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
# Save bandwidth limit to Downloads resource MaxThroughput (only if bandwidth UI exists)
@@ -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.")
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"""
try:
# Disable controls during installation
@@ -2382,7 +2386,7 @@ class InstallModlistScreen(QWidget):
# This shouldn't happen since automated prefix creation is complete
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(
context=modlist_context,
progress_callback=progress_callback,
+10 -4
View File
@@ -222,15 +222,21 @@ class ValidationHandler:
def validate_steam_shortcut(self, app_id: str) -> Tuple[bool, str]:
"""Validate a Steam shortcut."""
try:
# Check if shortcuts.vdf exists
shortcuts_path = Path.home() / '.steam' / 'steam' / 'userdata' / '75424832' / 'config' / 'shortcuts.vdf'
# Use native Steam service to get proper shortcuts.vdf path with multi-user support
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():
return False, "shortcuts.vdf not found"
# Check if shortcuts.vdf is accessible
if not os.access(shortcuts_path, os.R_OK | os.W_OK):
return False, "shortcuts.vdf is not accessible"
# Parse shortcuts.vdf using VDFHandler
shortcuts_data = VDFHandler.load(str(shortcuts_path), binary=True)