diff --git a/CHANGELOG.md b/CHANGELOG.md
index 95a87e9..7889df0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,50 @@
# Jackify Changelog
+## v0.1.4 - GE-Proton Support and Performance Optimization
+**Release Date:** September 22, 2025
+
+### New Features
+- **GE-Proton Detection**: Automatic detection and prioritization of GE-Proton versions
+- **User-selectable Proton version**: Settings dialog displays all available Proton versions with type indicators
+
+### Engine Updates
+- **jackify-engine v0.3.15**: Reads Proton configuration from config.json, adds degree symbol handling for special characters, removes Wine fallback (Proton now required)
+
+### Technical Improvements
+- **Smart Priority**: GE-Proton 10+ → Proton Experimental → Proton 10 → Proton 9
+- **Auto-Configuration**: Fresh installations automatically select optimal Proton version
+
+### Bug Fixes
+- **Steam VDF Compatibility**: Fixed case-sensitivity issues with Steam shortcuts.vdf parsing for Configure Existing Modlist workflows
+
+---
+
+## v0.1.3 - Enhanced Proton Support and System Compatibility
+**Release Date:** September 21, 2025
+
+### New Features
+- **Enhanced Proton Detection**: Automatic fallback system with priority: Experimental → Proton 10 → Proton 9
+- **Guided Proton Installation**: Professional auto-install dialog with Steam protocol integration for missing Proton versions
+- **Enderal Game Support**: Added Enderal to supported games list with special handling for Somnium modlist structure
+- **Proton Version Leniency**: Accept any Proton version 9+ instead of requiring Experimental
+
+### UX Improvements
+- **Resolution System Overhaul**: Eliminated hardcoded 2560x1600 fallbacks across all screens
+- **Steam Deck Detection**: Proper 1280x800 default resolution with 1920x1080 fallback for desktop
+- **Leave Unchanged Logic**: Fixed resolution setting to actually preserve existing user configurations
+
+### Technical Improvements
+- **Resolution Utilities**: New `shared/resolution_utils.py` with centralized resolution management
+- **Protontricks Detection**: Enhanced detection for both native and Flatpak protontricks installations
+- **Real-time Monitoring**: Progress tracking for Proton installation with directory stability detection
+
+### Bug Fixes
+- **Somnium Support**: Automatic detection of `files/ModOrganizer.exe` structure in edge-case modlists
+- **Steam Protocol Integration**: Reliable triggering of Proton installation via `steam://install/` URLs
+- **Manual Fallback**: Clear instructions and recheck functionality when auto-install fails
+
+---
+
## v0.1.2 - About Dialog and System Information
**Release Date:** September 16, 2025
diff --git a/jackify/__init__.py b/jackify/__init__.py
index d5f1880..17e3840 100644
--- a/jackify/__init__.py
+++ b/jackify/__init__.py
@@ -5,4 +5,4 @@ This package provides both CLI and GUI interfaces for managing
Wabbajack modlists natively on Linux systems.
"""
-__version__ = "0.1.2"
+__version__ = "0.1.4"
diff --git a/jackify/backend/core/modlist_operations.py b/jackify/backend/core/modlist_operations.py
index e3923ec..23d0f1a 100644
--- a/jackify/backend/core/modlist_operations.py
+++ b/jackify/backend/core/modlist_operations.py
@@ -23,6 +23,44 @@ from jackify.backend.handlers.config_handler import ConfigHandler
# UI Colors already imported above
+def _get_user_proton_version():
+ """Get user's preferred Proton version from config, with fallback to auto-detection"""
+ try:
+ from jackify.backend.handlers.config_handler import ConfigHandler
+ from jackify.backend.handlers.wine_utils import WineUtils
+
+ config_handler = ConfigHandler()
+ user_proton_path = config_handler.get('proton_path', 'auto')
+
+ if user_proton_path == 'auto':
+ # Use enhanced fallback logic with GE-Proton preference
+ logging.info("User selected auto-detect, using GE-Proton → Experimental → Proton precedence")
+ return WineUtils.select_best_proton()
+ else:
+ # User has selected a specific Proton version
+ # Use the exact directory name for Steam config.vdf
+ try:
+ proton_version = os.path.basename(user_proton_path)
+ # GE-Proton uses exact directory name, Valve Proton needs lowercase conversion
+ if proton_version.startswith('GE-Proton'):
+ # Keep GE-Proton name exactly as-is
+ steam_proton_name = proton_version
+ else:
+ # Convert Valve Proton names to Steam's format
+ steam_proton_name = proton_version.lower().replace(' - ', '_').replace(' ', '_').replace('-', '_')
+ if not steam_proton_name.startswith('proton'):
+ steam_proton_name = f"proton_{steam_proton_name}"
+
+ logging.info(f"Using user-selected Proton: {steam_proton_name}")
+ return steam_proton_name
+ except Exception as e:
+ logging.warning(f"Invalid user Proton path '{user_proton_path}', falling back to auto: {e}")
+ return WineUtils.select_best_proton()
+
+ except Exception as e:
+ logging.error(f"Failed to get user Proton preference, using default: {e}")
+ return "proton_experimental"
+
# Attempt to import readline for tab completion
READLINE_AVAILABLE = False
try:
@@ -1248,13 +1286,16 @@ class ModlistInstallCLI:
from jackify.backend.services.native_steam_service import NativeSteamService
steam_service = NativeSteamService()
+ # Get user's preferred Proton version
+ proton_version = _get_user_proton_version()
+
success, app_id = steam_service.create_shortcut_with_proton(
app_name=config_context['name'],
exe_path=config_context['mo2_exe_path'],
start_dir=os.path.dirname(config_context['mo2_exe_path']),
launch_options="%command%",
tags=["Jackify"],
- proton_version="proton_experimental"
+ proton_version=proton_version
)
if not success or not app_id:
diff --git a/jackify/backend/handlers/config_handler.py b/jackify/backend/handlers/config_handler.py
index 65425b1..d7aea67 100644
--- a/jackify/backend/handlers/config_handler.py
+++ b/jackify/backend/handlers/config_handler.py
@@ -47,8 +47,10 @@ class ConfigHandler:
# If steam_path is not set, detect it
if not self.settings["steam_path"]:
self.settings["steam_path"] = self._detect_steam_path()
- # Save the updated settings
- self.save_config()
+
+ # Auto-detect and set Proton version on first run
+ if not self.settings.get("proton_path"):
+ self._auto_detect_proton()
# If jackify_data_dir is not set, initialize it to default
if not self.settings.get("jackify_data_dir"):
@@ -494,4 +496,28 @@ class ConfigHandler:
logger.error(f"Error saving modlist downloads base directory: {e}")
return False
+ def _auto_detect_proton(self):
+ """Auto-detect and set best Proton version (includes GE-Proton and Valve Proton)"""
+ try:
+ from .wine_utils import WineUtils
+ best_proton = WineUtils.select_best_proton()
+
+ if best_proton:
+ self.settings["proton_path"] = str(best_proton['path'])
+ self.settings["proton_version"] = best_proton['name']
+ proton_type = best_proton.get('type', 'Unknown')
+ logger.info(f"Auto-detected Proton: {best_proton['name']} ({proton_type})")
+ self.save_config()
+ else:
+ # Fallback to auto-detect mode
+ self.settings["proton_path"] = "auto"
+ self.settings["proton_version"] = "auto"
+ logger.info("No compatible Proton versions found, using auto-detect mode")
+ self.save_config()
+
+ except Exception as e:
+ logger.error(f"Failed to auto-detect Proton: {e}")
+ self.settings["proton_path"] = "auto"
+ self.settings["proton_version"] = "auto"
+
\ No newline at end of file
diff --git a/jackify/backend/handlers/modlist_handler.py b/jackify/backend/handlers/modlist_handler.py
index dc88219..ef67ee3 100644
--- a/jackify/backend/handlers/modlist_handler.py
+++ b/jackify/backend/handlers/modlist_handler.py
@@ -1163,7 +1163,7 @@ class ModlistHandler:
# Determine game type
game = (game_var_full or modlist_name or "").lower().replace(" ", "")
# Add game-specific extras
- if "skyrim" in game or "fallout4" in game or "starfield" in game or "oblivion_remastered" in game:
+ if "skyrim" in game or "fallout4" in game or "starfield" in game or "oblivion_remastered" in game or "enderal" in game:
extras += ["d3dcompiler_47", "d3dx11_43", "d3dcompiler_43", "dotnet6", "dotnet7"]
elif "falloutnewvegas" in game or "fnv" in game or "oblivion" in game:
extras += ["d3dx9_43", "d3dx9"]
@@ -1238,6 +1238,12 @@ class ModlistHandler:
# Check ModOrganizer.ini for indicators (nvse/enderal) as an early, robust signal
try:
mo2_ini = modlist_path / "ModOrganizer.ini"
+ # Also check Somnium's non-standard location
+ if not mo2_ini.exists():
+ somnium_mo2_ini = modlist_path / "files" / "ModOrganizer.ini"
+ if somnium_mo2_ini.exists():
+ mo2_ini = somnium_mo2_ini
+
if mo2_ini.exists():
try:
content = mo2_ini.read_text(errors='ignore').lower()
diff --git a/jackify/backend/handlers/shortcut_handler.py b/jackify/backend/handlers/shortcut_handler.py
index ef29ebd..0757988 100644
--- a/jackify/backend/handlers/shortcut_handler.py
+++ b/jackify/backend/handlers/shortcut_handler.py
@@ -988,8 +988,8 @@ class ShortcutHandler:
shortcuts_data = VDFHandler.load(shortcuts_vdf_path, binary=True)
if shortcuts_data and 'shortcuts' in shortcuts_data:
for idx, shortcut in shortcuts_data['shortcuts'].items():
- app_name = shortcut.get('AppName', '').strip()
- exe = shortcut.get('Exe', '').strip('"').strip()
+ app_name = shortcut.get('AppName', shortcut.get('appname', '')).strip()
+ exe = shortcut.get('Exe', shortcut.get('exe', '')).strip('"').strip()
vdf_shortcuts.append((app_name, exe, idx))
except Exception as e:
self.logger.error(f"Error parsing shortcuts.vdf for exe path matching: {e}")
@@ -1054,9 +1054,9 @@ class ShortcutHandler:
self.logger.warning(f"Skipping invalid shortcut entry (not a dict) at index {shortcut_id} in {shortcuts_file}")
continue
- app_name = shortcut.get('AppName')
- exe_path = shortcut.get('Exe', '').strip('"')
- start_dir = shortcut.get('StartDir', '').strip('"')
+ app_name = shortcut.get('AppName', shortcut.get('appname'))
+ exe_path = shortcut.get('Exe', shortcut.get('exe', '')).strip('"')
+ start_dir = shortcut.get('StartDir', shortcut.get('startdir', '')).strip('"')
# Check if the base name of the exe_path matches the target
if app_name and start_dir and os.path.basename(exe_path) == executable_name:
diff --git a/jackify/backend/handlers/wabbajack_parser.py b/jackify/backend/handlers/wabbajack_parser.py
index 9b3f396..951540c 100644
--- a/jackify/backend/handlers/wabbajack_parser.py
+++ b/jackify/backend/handlers/wabbajack_parser.py
@@ -132,7 +132,8 @@ class WabbajackParser:
'falloutnv': 'Fallout New Vegas',
'oblivion': 'Oblivion',
'starfield': 'Starfield',
- 'oblivion_remastered': 'Oblivion Remastered'
+ 'oblivion_remastered': 'Oblivion Remastered',
+ 'enderal': 'Enderal'
}
return [display_names.get(game, game) for game in self.supported_games]
diff --git a/jackify/backend/handlers/wine_utils.py b/jackify/backend/handlers/wine_utils.py
index d5ec8ae..ffde4c0 100644
--- a/jackify/backend/handlers/wine_utils.py
+++ b/jackify/backend/handlers/wine_utils.py
@@ -13,7 +13,7 @@ import shutil
import time
from pathlib import Path
import glob
-from typing import Optional, Tuple
+from typing import Optional, Tuple, List, Dict
from .subprocess_utils import get_clean_subprocess_env
# Initialize logger
@@ -643,7 +643,20 @@ class WineUtils:
wine_bin = subdir / "files/bin/wine"
if wine_bin.is_file():
return str(wine_bin)
- # Fallback: Try 'Proton - Experimental' if present
+ # Fallback: Try user's configured Proton version
+ try:
+ from .config_handler import ConfigHandler
+ config = ConfigHandler()
+ fallback_path = config.get('proton_path', 'auto')
+ if fallback_path != 'auto':
+ fallback_wine_bin = Path(fallback_path) / "files/bin/wine"
+ if fallback_wine_bin.is_file():
+ logger.warning(f"Requested Proton version '{proton_version}' not found. Falling back to user's configured version.")
+ return str(fallback_wine_bin)
+ except Exception:
+ pass
+
+ # Final fallback: Try 'Proton - Experimental' if present
for base_path in steam_common_paths:
wine_bin = base_path / "Proton - Experimental" / "files/bin/wine"
if wine_bin.is_file():
@@ -698,4 +711,276 @@ class WineUtils:
proton_path = str(Path(wine_bin).parent.parent)
logger.debug(f"Found Proton path: {proton_path}")
- return compatdata_path, proton_path, wine_bin
\ No newline at end of file
+ return compatdata_path, proton_path, wine_bin
+
+ @staticmethod
+ def get_steam_library_paths() -> List[Path]:
+ """
+ Get all Steam library paths including standard locations.
+
+ Returns:
+ List of Path objects for Steam library directories
+ """
+ steam_paths = [
+ Path.home() / ".steam/steam/steamapps/common",
+ Path.home() / ".local/share/Steam/steamapps/common",
+ Path.home() / ".steam/root/steamapps/common"
+ ]
+
+ # Return only existing paths
+ return [path for path in steam_paths if path.exists()]
+
+ @staticmethod
+ def get_compatibility_tool_paths() -> List[Path]:
+ """
+ Get all compatibility tool paths for GE-Proton and other custom Proton versions.
+
+ Returns:
+ List of Path objects for compatibility tool directories
+ """
+ compat_paths = [
+ Path.home() / ".steam/steam/compatibilitytools.d",
+ Path.home() / ".local/share/Steam/compatibilitytools.d"
+ ]
+
+ # Return only existing paths
+ return [path for path in compat_paths if path.exists()]
+
+ @staticmethod
+ def scan_ge_proton_versions() -> List[Dict[str, any]]:
+ """
+ Scan for available GE-Proton versions in compatibilitytools.d directories.
+
+ Returns:
+ List of dicts with version info, sorted by priority (newest first)
+ """
+ logger.info("Scanning for available GE-Proton versions...")
+
+ found_versions = []
+ compat_paths = WineUtils.get_compatibility_tool_paths()
+
+ if not compat_paths:
+ logger.warning("No compatibility tool paths found")
+ return []
+
+ for compat_path in compat_paths:
+ logger.debug(f"Scanning compatibility tools: {compat_path}")
+
+ try:
+ # Look for GE-Proton directories
+ for proton_dir in compat_path.iterdir():
+ if not proton_dir.is_dir():
+ continue
+
+ dir_name = proton_dir.name
+ if not dir_name.startswith("GE-Proton"):
+ continue
+
+ # Check for wine binary
+ wine_bin = proton_dir / "files" / "bin" / "wine"
+ if not wine_bin.exists() or not wine_bin.is_file():
+ logger.debug(f"Skipping {dir_name} - no wine binary found")
+ continue
+
+ # Parse version from directory name (e.g., "GE-Proton10-16")
+ version_match = re.match(r'GE-Proton(\d+)-(\d+)', dir_name)
+ if version_match:
+ major_ver = int(version_match.group(1))
+ minor_ver = int(version_match.group(2))
+
+ # Calculate priority: GE-Proton gets highest priority
+ # Priority format: 200 (base) + major*10 + minor (e.g., 200 + 100 + 16 = 316)
+ priority = 200 + (major_ver * 10) + minor_ver
+
+ found_versions.append({
+ 'name': dir_name,
+ 'path': proton_dir,
+ 'wine_bin': wine_bin,
+ 'priority': priority,
+ 'major_version': major_ver,
+ 'minor_version': minor_ver,
+ 'type': 'GE-Proton'
+ })
+ logger.debug(f"Found {dir_name} at {proton_dir} (priority: {priority})")
+ else:
+ logger.debug(f"Skipping {dir_name} - unknown GE-Proton version format")
+
+ except Exception as e:
+ logger.warning(f"Error scanning {compat_path}: {e}")
+
+ # Sort by priority (highest first, so newest GE-Proton versions come first)
+ found_versions.sort(key=lambda x: x['priority'], reverse=True)
+
+ logger.info(f"Found {len(found_versions)} GE-Proton version(s)")
+ return found_versions
+
+ @staticmethod
+ def scan_valve_proton_versions() -> List[Dict[str, any]]:
+ """
+ Scan for available Valve Proton versions with fallback priority.
+
+ Returns:
+ List of dicts with version info, sorted by priority (best first)
+ """
+ logger.info("Scanning for available Valve Proton versions...")
+
+ found_versions = []
+ steam_libs = WineUtils.get_steam_library_paths()
+
+ if not steam_libs:
+ logger.warning("No Steam library paths found")
+ return []
+
+ # Priority order for Valve Proton versions
+ # Note: GE-Proton uses 200+ range, so Valve Proton gets 100+ range
+ preferred_versions = [
+ ("Proton - Experimental", 150), # Higher priority than regular Valve Proton
+ ("Proton 10.0", 140),
+ ("Proton 9.0", 130),
+ ("Proton 9.0 (Beta)", 125)
+ ]
+
+ for steam_path in steam_libs:
+ logger.debug(f"Scanning Steam library: {steam_path}")
+
+ for version_name, priority in preferred_versions:
+ proton_path = steam_path / version_name
+ wine_bin = proton_path / "files" / "bin" / "wine"
+
+ if wine_bin.exists() and wine_bin.is_file():
+ found_versions.append({
+ 'name': version_name,
+ 'path': proton_path,
+ 'wine_bin': wine_bin,
+ 'priority': priority,
+ 'type': 'Valve-Proton'
+ })
+ logger.debug(f"Found {version_name} at {proton_path}")
+
+ # Sort by priority (highest first)
+ found_versions.sort(key=lambda x: x['priority'], reverse=True)
+
+ # Remove duplicates while preserving order
+ unique_versions = []
+ seen_names = set()
+ for version in found_versions:
+ if version['name'] not in seen_names:
+ unique_versions.append(version)
+ seen_names.add(version['name'])
+
+ logger.info(f"Found {len(unique_versions)} unique Valve Proton version(s)")
+ return unique_versions
+
+ @staticmethod
+ def scan_all_proton_versions() -> List[Dict[str, any]]:
+ """
+ Scan for all available Proton versions (GE-Proton + Valve Proton) with unified priority.
+
+ Priority Chain (highest to lowest):
+ 1. GE-Proton10-16+ (priority 316+)
+ 2. GE-Proton10-* (priority 200+)
+ 3. Proton - Experimental (priority 150)
+ 4. Proton 10.0 (priority 140)
+ 5. Proton 9.0 (priority 130)
+ 6. Proton 9.0 (Beta) (priority 125)
+
+ Returns:
+ List of dicts with version info, sorted by priority (best first)
+ """
+ logger.info("Scanning for all available Proton versions...")
+
+ all_versions = []
+
+ # Scan GE-Proton versions (highest priority)
+ ge_versions = WineUtils.scan_ge_proton_versions()
+ all_versions.extend(ge_versions)
+
+ # Scan Valve Proton versions
+ valve_versions = WineUtils.scan_valve_proton_versions()
+ all_versions.extend(valve_versions)
+
+ # Sort by priority (highest first)
+ all_versions.sort(key=lambda x: x['priority'], reverse=True)
+
+ # Remove duplicates while preserving order
+ unique_versions = []
+ seen_names = set()
+ for version in all_versions:
+ if version['name'] not in seen_names:
+ unique_versions.append(version)
+ seen_names.add(version['name'])
+
+ if unique_versions:
+ logger.info(f"Found {len(unique_versions)} total Proton version(s)")
+ logger.info(f"Best available: {unique_versions[0]['name']} ({unique_versions[0]['type']})")
+ else:
+ logger.warning("No Proton versions found")
+
+ return unique_versions
+
+ @staticmethod
+ def select_best_proton() -> Optional[Dict[str, any]]:
+ """
+ Select the best available Proton version (GE-Proton or Valve Proton) using unified precedence.
+
+ Returns:
+ Dict with version info for the best Proton, or None if none found
+ """
+ available_versions = WineUtils.scan_all_proton_versions()
+
+ if not available_versions:
+ logger.warning("No compatible Proton versions found")
+ return None
+
+ # Return the highest priority version (first in sorted list)
+ best_version = available_versions[0]
+ logger.info(f"Selected best Proton version: {best_version['name']} ({best_version['type']})")
+ return best_version
+
+ @staticmethod
+ def select_best_valve_proton() -> Optional[Dict[str, any]]:
+ """
+ Select the best available Valve Proton version using fallback precedence.
+ Note: This method is kept for backward compatibility. Consider using select_best_proton() instead.
+
+ Returns:
+ Dict with version info for the best Proton, or None if none found
+ """
+ available_versions = WineUtils.scan_valve_proton_versions()
+
+ if not available_versions:
+ logger.warning("No compatible Valve Proton versions found")
+ return None
+
+ # Return the highest priority version (first in sorted list)
+ best_version = available_versions[0]
+ logger.info(f"Selected Valve Proton version: {best_version['name']}")
+ return best_version
+
+ @staticmethod
+ def check_proton_requirements() -> Tuple[bool, str, Optional[Dict[str, any]]]:
+ """
+ Check if compatible Proton version is available for workflows.
+
+ Returns:
+ tuple: (requirements_met, status_message, proton_info)
+ - requirements_met: True if compatible Proton found
+ - status_message: Human-readable status for display to user
+ - proton_info: Dict with Proton details if found, None otherwise
+ """
+ logger.info("Checking Proton requirements for workflow...")
+
+ # Scan for available Proton versions (includes GE-Proton + Valve Proton)
+ best_proton = WineUtils.select_best_proton()
+
+ if best_proton:
+ # Compatible Proton found
+ proton_type = best_proton.get('type', 'Unknown')
+ status_msg = f"✓ Using {best_proton['name']} ({proton_type}) for this workflow"
+ logger.info(f"Proton requirements satisfied: {best_proton['name']} ({proton_type})")
+ return True, status_msg, best_proton
+ else:
+ # No compatible Proton found
+ status_msg = "✗ No compatible Proton version found (GE-Proton 10+, Proton 9+, 10, or Experimental required)"
+ logger.warning("Proton requirements not met - no compatible version found")
+ return False, status_msg, None
\ No newline at end of file
diff --git a/jackify/backend/services/automated_prefix_service.py b/jackify/backend/services/automated_prefix_service.py
index 6c0f3f0..490dff4 100644
--- a/jackify/backend/services/automated_prefix_service.py
+++ b/jackify/backend/services/automated_prefix_service.py
@@ -38,6 +38,44 @@ class AutomatedPrefixService:
"""Get consistent progress timestamp"""
from jackify.shared.timing import get_timestamp
return get_timestamp()
+
+ def _get_user_proton_version(self):
+ """Get user's preferred Proton version from config, with fallback to auto-detection"""
+ try:
+ from jackify.backend.handlers.config_handler import ConfigHandler
+ from jackify.backend.handlers.wine_utils import WineUtils
+
+ config_handler = ConfigHandler()
+ user_proton_path = config_handler.get('proton_path', 'auto')
+
+ if user_proton_path == 'auto':
+ # Use enhanced fallback logic with GE-Proton preference
+ logger.info("User selected auto-detect, using GE-Proton → Experimental → Proton precedence")
+ return WineUtils.select_best_proton()
+ else:
+ # User has selected a specific Proton version
+ # Use the exact directory name for Steam config.vdf
+ try:
+ proton_version = os.path.basename(user_proton_path)
+ # GE-Proton uses exact directory name, Valve Proton needs lowercase conversion
+ if proton_version.startswith('GE-Proton'):
+ # Keep GE-Proton name exactly as-is
+ steam_proton_name = proton_version
+ else:
+ # Convert Valve Proton names to Steam's format
+ steam_proton_name = proton_version.lower().replace(' - ', '_').replace(' ', '_').replace('-', '_')
+ if not steam_proton_name.startswith('proton'):
+ steam_proton_name = f"proton_{steam_proton_name}"
+
+ logger.info(f"Using user-selected Proton: {steam_proton_name}")
+ return steam_proton_name
+ except Exception as e:
+ logger.warning(f"Invalid user Proton path '{user_proton_path}', falling back to auto: {e}")
+ return WineUtils.select_best_proton()
+
+ except Exception as e:
+ logger.error(f"Failed to get user Proton preference, using default: {e}")
+ return "proton_experimental"
def create_shortcut_with_native_service(self, shortcut_name: str, exe_path: str,
@@ -87,6 +125,9 @@ class AutomatedPrefixService:
logger.warning(f"Could not generate STEAM_COMPAT_MOUNTS, using default: {e}")
launch_options = "%command%"
+ # Get user's preferred Proton version
+ proton_version = self._get_user_proton_version()
+
# Create shortcut with Proton using native service
success, app_id = steam_service.create_shortcut_with_proton(
app_name=shortcut_name,
@@ -94,7 +135,7 @@ class AutomatedPrefixService:
start_dir=modlist_install_dir,
launch_options=launch_options,
tags=["Jackify"],
- proton_version="proton_experimental"
+ proton_version=proton_version
)
if success and app_id:
@@ -292,13 +333,13 @@ class AutomatedPrefixService:
logger.error(f"Steam userdata directory not found: {userdata_dir}")
return None
- # Find the first user directory (most systems have only one user)
- user_dirs = [d for d in userdata_dir.iterdir() if d.is_dir() and d.name.isdigit()]
+ # Find user directories (excluding user 0 which is a system account)
+ user_dirs = [d for d in userdata_dir.iterdir() if d.is_dir() and d.name.isdigit() and d.name != "0"]
if not user_dirs:
- logger.error("No Steam user directories found in userdata")
+ logger.error("No valid Steam user directories found in userdata (user 0 is not valid)")
return None
- # Use the first user directory found
+ # Use the first valid user directory found
user_dir = user_dirs[0]
shortcuts_path = user_dir / "config" / "shortcuts.vdf"
@@ -2499,8 +2540,8 @@ echo Prefix creation complete.
# Try the standard Steam userdata path
steam_userdata_path = Path.home() / ".steam" / "steam" / "userdata"
if steam_userdata_path.exists():
- # Find the first user directory (usually only one on Steam Deck)
- user_dirs = [d for d in steam_userdata_path.iterdir() if d.is_dir() and d.name.isdigit()]
+ # Find user directories (excluding user 0 which is a system account)
+ user_dirs = [d for d in steam_userdata_path.iterdir() if d.is_dir() and d.name.isdigit() and d.name != "0"]
if user_dirs:
localconfig_path = user_dirs[0] / "config" / "localconfig.vdf"
if localconfig_path.exists():
@@ -2601,8 +2642,11 @@ echo Prefix creation complete.
env = os.environ.copy()
env['STEAM_COMPAT_CLIENT_INSTALL_PATH'] = str(steam_root)
env['STEAM_COMPAT_DATA_PATH'] = str(compatdata_dir / str(abs(appid)))
- # Suppress GUI windows by unsetting DISPLAY
+ # Suppress GUI windows using jackify-engine's proven approach
env['DISPLAY'] = ''
+ env['WAYLAND_DISPLAY'] = ''
+ env['WINEDEBUG'] = '-all'
+ env['WINEDLLOVERRIDES'] = 'msdia80.dll=n;conhost.exe=d;cmd.exe=d'
# Create the compatdata directory
compat_dir = compatdata_dir / str(abs(appid))
@@ -2616,7 +2660,9 @@ echo Prefix creation complete.
cmd = [str(proton_path), 'run', 'wineboot', '-u']
logger.info(f"Running: {' '.join(cmd)}")
- result = subprocess.run(cmd, env=env, capture_output=True, text=True, timeout=60)
+ # Use jackify-engine's approach: UseShellExecute=false, CreateNoWindow=true equivalent
+ result = subprocess.run(cmd, env=env, capture_output=True, text=True, timeout=60,
+ shell=False, creationflags=getattr(subprocess, 'CREATE_NO_WINDOW', 0))
logger.info(f"Proton exit code: {result.returncode}")
if result.stdout:
@@ -2718,26 +2764,39 @@ echo Prefix creation complete.
def verify_compatibility_tool_persists(self, appid: int) -> bool:
"""
- Verify that the compatibility tool setting persists.
-
+ Verify that the compatibility tool setting persists with correct Proton version.
+
Args:
appid: The AppID to check
-
+
Returns:
- True if compatibility tool is set, False otherwise
+ True if compatibility tool is correctly set, False otherwise
"""
try:
config_path = Path.home() / ".steam/steam/config/config.vdf"
- with open(config_path, 'r') as f:
+ if not config_path.exists():
+ logger.warning("Steam config.vdf not found")
+ return False
+
+ with open(config_path, 'r', encoding='utf-8') as f:
content = f.read()
-
+
+ # Check if AppID exists and has a Proton version set
if f'"{appid}"' in content:
- logger.info(" Compatibility tool persists")
- return True
+ # Get the expected Proton version
+ expected_proton = self._get_user_proton_version()
+
+ # Look for the Proton version in the compatibility tool mapping
+ if expected_proton in content:
+ logger.info(f" Compatibility tool persists: {expected_proton}")
+ return True
+ else:
+ logger.warning(f"AppID {appid} found but Proton version '{expected_proton}' not set")
+ return False
else:
logger.warning("Compatibility tool not found")
return False
-
+
except Exception as e:
logger.error(f"Error verifying compatibility tool: {e}")
return False
diff --git a/jackify/backend/services/native_steam_service.py b/jackify/backend/services/native_steam_service.py
index 3b4e101..eddb44d 100644
--- a/jackify/backend/services/native_steam_service.py
+++ b/jackify/backend/services/native_steam_service.py
@@ -40,13 +40,13 @@ class NativeSteamService:
logger.error("Steam userdata directory not found")
return False
- # Find the first user directory (usually there's only one)
- user_dirs = [d for d in self.userdata_path.iterdir() if d.is_dir() and d.name.isdigit()]
+ # Find user directories (excluding user 0 which is a system account)
+ user_dirs = [d for d in self.userdata_path.iterdir() if d.is_dir() and d.name.isdigit() and d.name != "0"]
if not user_dirs:
- logger.error("No Steam user directories found")
+ logger.error("No valid Steam user directories found (user 0 is not valid for shortcuts)")
return False
- # Use the first user directory
+ # Use the first valid user directory
user_dir = user_dirs[0]
self.user_id = user_dir.name
self.user_config_path = user_dir / "config"
@@ -327,17 +327,27 @@ class NativeSteamService:
logger.error(f"Error setting Proton version: {e}")
return False
- def create_shortcut_with_proton(self, app_name: str, exe_path: str, start_dir: str = None,
+ def create_shortcut_with_proton(self, app_name: str, exe_path: str, start_dir: str = None,
launch_options: str = "%command%", tags: List[str] = None,
- proton_version: str = "proton_experimental") -> Tuple[bool, Optional[int]]:
+ proton_version: str = None) -> Tuple[bool, Optional[int]]:
"""
Complete workflow: Create shortcut and set Proton version.
-
+
This is the main method that replaces STL entirely.
-
+
Returns:
(success, app_id) - Success status and the AppID
"""
+ # Auto-detect best Proton version if none provided
+ if proton_version is None:
+ try:
+ from jackify.backend.core.modlist_operations import _get_user_proton_version
+ proton_version = _get_user_proton_version()
+ logger.info(f"Auto-detected Proton version: {proton_version}")
+ except Exception as e:
+ logger.warning(f"Failed to auto-detect Proton, falling back to experimental: {e}")
+ proton_version = "proton_experimental"
+
logger.info(f"Creating shortcut with Proton: '{app_name}' -> '{proton_version}'")
# Step 1: Create the shortcut
diff --git a/jackify/engine/Wabbajack.CLI.Builder.dll b/jackify/engine/Wabbajack.CLI.Builder.dll
index e40be4c..069ed6e 100644
Binary files a/jackify/engine/Wabbajack.CLI.Builder.dll and b/jackify/engine/Wabbajack.CLI.Builder.dll differ
diff --git a/jackify/engine/Wabbajack.Common.dll b/jackify/engine/Wabbajack.Common.dll
index cc54e48..351e404 100644
Binary files a/jackify/engine/Wabbajack.Common.dll and b/jackify/engine/Wabbajack.Common.dll differ
diff --git a/jackify/engine/Wabbajack.Compiler.dll b/jackify/engine/Wabbajack.Compiler.dll
index 9a74999..8c45d85 100644
Binary files a/jackify/engine/Wabbajack.Compiler.dll and b/jackify/engine/Wabbajack.Compiler.dll differ
diff --git a/jackify/engine/Wabbajack.Compression.BSA.dll b/jackify/engine/Wabbajack.Compression.BSA.dll
index 6c9be8a..98205eb 100644
Binary files a/jackify/engine/Wabbajack.Compression.BSA.dll and b/jackify/engine/Wabbajack.Compression.BSA.dll differ
diff --git a/jackify/engine/Wabbajack.Compression.Zip.dll b/jackify/engine/Wabbajack.Compression.Zip.dll
index a2d75a2..c8a6b0c 100644
Binary files a/jackify/engine/Wabbajack.Compression.Zip.dll and b/jackify/engine/Wabbajack.Compression.Zip.dll differ
diff --git a/jackify/engine/Wabbajack.Configuration.dll b/jackify/engine/Wabbajack.Configuration.dll
index 0855672..2031231 100644
Binary files a/jackify/engine/Wabbajack.Configuration.dll and b/jackify/engine/Wabbajack.Configuration.dll differ
diff --git a/jackify/engine/Wabbajack.DTOs.dll b/jackify/engine/Wabbajack.DTOs.dll
index 63a953a..e5d7367 100644
Binary files a/jackify/engine/Wabbajack.DTOs.dll and b/jackify/engine/Wabbajack.DTOs.dll differ
diff --git a/jackify/engine/Wabbajack.Downloaders.Bethesda.dll b/jackify/engine/Wabbajack.Downloaders.Bethesda.dll
index 80b0eb3..28df717 100644
Binary files a/jackify/engine/Wabbajack.Downloaders.Bethesda.dll and b/jackify/engine/Wabbajack.Downloaders.Bethesda.dll differ
diff --git a/jackify/engine/Wabbajack.Downloaders.Dispatcher.dll b/jackify/engine/Wabbajack.Downloaders.Dispatcher.dll
index 915bcb7..3416aed 100644
Binary files a/jackify/engine/Wabbajack.Downloaders.Dispatcher.dll and b/jackify/engine/Wabbajack.Downloaders.Dispatcher.dll differ
diff --git a/jackify/engine/Wabbajack.Downloaders.GameFile.dll b/jackify/engine/Wabbajack.Downloaders.GameFile.dll
index 84faeee..2db7ded 100644
Binary files a/jackify/engine/Wabbajack.Downloaders.GameFile.dll and b/jackify/engine/Wabbajack.Downloaders.GameFile.dll differ
diff --git a/jackify/engine/Wabbajack.Downloaders.GoogleDrive.dll b/jackify/engine/Wabbajack.Downloaders.GoogleDrive.dll
index 1f09c62..766afb9 100644
Binary files a/jackify/engine/Wabbajack.Downloaders.GoogleDrive.dll and b/jackify/engine/Wabbajack.Downloaders.GoogleDrive.dll differ
diff --git a/jackify/engine/Wabbajack.Downloaders.Http.dll b/jackify/engine/Wabbajack.Downloaders.Http.dll
index b9f70ac..ddbdb45 100644
Binary files a/jackify/engine/Wabbajack.Downloaders.Http.dll and b/jackify/engine/Wabbajack.Downloaders.Http.dll differ
diff --git a/jackify/engine/Wabbajack.Downloaders.IPS4OAuth2Downloader.dll b/jackify/engine/Wabbajack.Downloaders.IPS4OAuth2Downloader.dll
index f6853a7..17f8b9a 100644
Binary files a/jackify/engine/Wabbajack.Downloaders.IPS4OAuth2Downloader.dll and b/jackify/engine/Wabbajack.Downloaders.IPS4OAuth2Downloader.dll differ
diff --git a/jackify/engine/Wabbajack.Downloaders.Interfaces.dll b/jackify/engine/Wabbajack.Downloaders.Interfaces.dll
index 41cedad..5a5680c 100644
Binary files a/jackify/engine/Wabbajack.Downloaders.Interfaces.dll and b/jackify/engine/Wabbajack.Downloaders.Interfaces.dll differ
diff --git a/jackify/engine/Wabbajack.Downloaders.Manual.dll b/jackify/engine/Wabbajack.Downloaders.Manual.dll
index dc2c504..1816990 100644
Binary files a/jackify/engine/Wabbajack.Downloaders.Manual.dll and b/jackify/engine/Wabbajack.Downloaders.Manual.dll differ
diff --git a/jackify/engine/Wabbajack.Downloaders.MediaFire.dll b/jackify/engine/Wabbajack.Downloaders.MediaFire.dll
index 963c733..4394662 100644
Binary files a/jackify/engine/Wabbajack.Downloaders.MediaFire.dll and b/jackify/engine/Wabbajack.Downloaders.MediaFire.dll differ
diff --git a/jackify/engine/Wabbajack.Downloaders.Mega.dll b/jackify/engine/Wabbajack.Downloaders.Mega.dll
index b6c3388..9a079c2 100644
Binary files a/jackify/engine/Wabbajack.Downloaders.Mega.dll and b/jackify/engine/Wabbajack.Downloaders.Mega.dll differ
diff --git a/jackify/engine/Wabbajack.Downloaders.ModDB.dll b/jackify/engine/Wabbajack.Downloaders.ModDB.dll
index f1d4c93..8c42eb1 100644
Binary files a/jackify/engine/Wabbajack.Downloaders.ModDB.dll and b/jackify/engine/Wabbajack.Downloaders.ModDB.dll differ
diff --git a/jackify/engine/Wabbajack.Downloaders.Nexus.dll b/jackify/engine/Wabbajack.Downloaders.Nexus.dll
index 0f1aa3c..6bf0cfd 100644
Binary files a/jackify/engine/Wabbajack.Downloaders.Nexus.dll and b/jackify/engine/Wabbajack.Downloaders.Nexus.dll differ
diff --git a/jackify/engine/Wabbajack.Downloaders.VerificationCache.dll b/jackify/engine/Wabbajack.Downloaders.VerificationCache.dll
index 0d32d9a..0df14ef 100644
Binary files a/jackify/engine/Wabbajack.Downloaders.VerificationCache.dll and b/jackify/engine/Wabbajack.Downloaders.VerificationCache.dll differ
diff --git a/jackify/engine/Wabbajack.Downloaders.WabbajackCDN.dll b/jackify/engine/Wabbajack.Downloaders.WabbajackCDN.dll
index 7203a01..758d973 100644
Binary files a/jackify/engine/Wabbajack.Downloaders.WabbajackCDN.dll and b/jackify/engine/Wabbajack.Downloaders.WabbajackCDN.dll differ
diff --git a/jackify/engine/Wabbajack.FileExtractor.dll b/jackify/engine/Wabbajack.FileExtractor.dll
index eab09a9..6797aa1 100644
Binary files a/jackify/engine/Wabbajack.FileExtractor.dll and b/jackify/engine/Wabbajack.FileExtractor.dll differ
diff --git a/jackify/engine/Wabbajack.Hashing.PHash.dll b/jackify/engine/Wabbajack.Hashing.PHash.dll
index 6746836..cc358fa 100644
Binary files a/jackify/engine/Wabbajack.Hashing.PHash.dll and b/jackify/engine/Wabbajack.Hashing.PHash.dll differ
diff --git a/jackify/engine/Wabbajack.Hashing.xxHash64.dll b/jackify/engine/Wabbajack.Hashing.xxHash64.dll
index 2c1459f..5407d92 100644
Binary files a/jackify/engine/Wabbajack.Hashing.xxHash64.dll and b/jackify/engine/Wabbajack.Hashing.xxHash64.dll differ
diff --git a/jackify/engine/Wabbajack.IO.Async.dll b/jackify/engine/Wabbajack.IO.Async.dll
index 94d05b6..3b619be 100644
Binary files a/jackify/engine/Wabbajack.IO.Async.dll and b/jackify/engine/Wabbajack.IO.Async.dll differ
diff --git a/jackify/engine/Wabbajack.Installer.dll b/jackify/engine/Wabbajack.Installer.dll
index 1dd7ba0..7593881 100644
Binary files a/jackify/engine/Wabbajack.Installer.dll and b/jackify/engine/Wabbajack.Installer.dll differ
diff --git a/jackify/engine/Wabbajack.Networking.BethesdaNet.dll b/jackify/engine/Wabbajack.Networking.BethesdaNet.dll
index 282661a..a4d69c9 100644
Binary files a/jackify/engine/Wabbajack.Networking.BethesdaNet.dll and b/jackify/engine/Wabbajack.Networking.BethesdaNet.dll differ
diff --git a/jackify/engine/Wabbajack.Networking.Discord.dll b/jackify/engine/Wabbajack.Networking.Discord.dll
index eb6ca84..ebbd796 100644
Binary files a/jackify/engine/Wabbajack.Networking.Discord.dll and b/jackify/engine/Wabbajack.Networking.Discord.dll differ
diff --git a/jackify/engine/Wabbajack.Networking.GitHub.dll b/jackify/engine/Wabbajack.Networking.GitHub.dll
index ff9c9c6..6124a60 100644
Binary files a/jackify/engine/Wabbajack.Networking.GitHub.dll and b/jackify/engine/Wabbajack.Networking.GitHub.dll differ
diff --git a/jackify/engine/Wabbajack.Networking.Http.Interfaces.dll b/jackify/engine/Wabbajack.Networking.Http.Interfaces.dll
index 76708eb..e06e883 100644
Binary files a/jackify/engine/Wabbajack.Networking.Http.Interfaces.dll and b/jackify/engine/Wabbajack.Networking.Http.Interfaces.dll differ
diff --git a/jackify/engine/Wabbajack.Networking.Http.dll b/jackify/engine/Wabbajack.Networking.Http.dll
index e9b54aa..c523dd2 100644
Binary files a/jackify/engine/Wabbajack.Networking.Http.dll and b/jackify/engine/Wabbajack.Networking.Http.dll differ
diff --git a/jackify/engine/Wabbajack.Networking.NexusApi.dll b/jackify/engine/Wabbajack.Networking.NexusApi.dll
index 863cf3b..cf6260e 100644
Binary files a/jackify/engine/Wabbajack.Networking.NexusApi.dll and b/jackify/engine/Wabbajack.Networking.NexusApi.dll differ
diff --git a/jackify/engine/Wabbajack.Networking.WabbajackClientApi.dll b/jackify/engine/Wabbajack.Networking.WabbajackClientApi.dll
index 14a5ec6..899578d 100644
Binary files a/jackify/engine/Wabbajack.Networking.WabbajackClientApi.dll and b/jackify/engine/Wabbajack.Networking.WabbajackClientApi.dll differ
diff --git a/jackify/engine/Wabbajack.Paths.IO.dll b/jackify/engine/Wabbajack.Paths.IO.dll
index f25da12..8091dc7 100644
Binary files a/jackify/engine/Wabbajack.Paths.IO.dll and b/jackify/engine/Wabbajack.Paths.IO.dll differ
diff --git a/jackify/engine/Wabbajack.Paths.dll b/jackify/engine/Wabbajack.Paths.dll
index debdb38..28c8eb2 100644
Binary files a/jackify/engine/Wabbajack.Paths.dll and b/jackify/engine/Wabbajack.Paths.dll differ
diff --git a/jackify/engine/Wabbajack.RateLimiter.dll b/jackify/engine/Wabbajack.RateLimiter.dll
index f77cd11..901749f 100644
Binary files a/jackify/engine/Wabbajack.RateLimiter.dll and b/jackify/engine/Wabbajack.RateLimiter.dll differ
diff --git a/jackify/engine/Wabbajack.Server.Lib.dll b/jackify/engine/Wabbajack.Server.Lib.dll
index 3eab3e0..9f974de 100644
Binary files a/jackify/engine/Wabbajack.Server.Lib.dll and b/jackify/engine/Wabbajack.Server.Lib.dll differ
diff --git a/jackify/engine/Wabbajack.Services.OSIntegrated.dll b/jackify/engine/Wabbajack.Services.OSIntegrated.dll
index 86f5a2b..b57cce1 100644
Binary files a/jackify/engine/Wabbajack.Services.OSIntegrated.dll and b/jackify/engine/Wabbajack.Services.OSIntegrated.dll differ
diff --git a/jackify/engine/Wabbajack.VFS.Interfaces.dll b/jackify/engine/Wabbajack.VFS.Interfaces.dll
index 9eb2c70..627f5f4 100644
Binary files a/jackify/engine/Wabbajack.VFS.Interfaces.dll and b/jackify/engine/Wabbajack.VFS.Interfaces.dll differ
diff --git a/jackify/engine/Wabbajack.VFS.dll b/jackify/engine/Wabbajack.VFS.dll
index 44c1077..42abcf4 100644
Binary files a/jackify/engine/Wabbajack.VFS.dll and b/jackify/engine/Wabbajack.VFS.dll differ
diff --git a/jackify/engine/jackify-engine.deps.json b/jackify/engine/jackify-engine.deps.json
index 0e07f79..3a447af 100644
--- a/jackify/engine/jackify-engine.deps.json
+++ b/jackify/engine/jackify-engine.deps.json
@@ -7,7 +7,7 @@
"targets": {
".NETCoreApp,Version=v8.0": {},
".NETCoreApp,Version=v8.0/linux-x64": {
- "jackify-engine/0.3.14": {
+ "jackify-engine/0.3.15": {
"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.14",
- "Wabbajack.Downloaders.Bethesda": "0.3.14",
- "Wabbajack.Downloaders.Dispatcher": "0.3.14",
- "Wabbajack.Hashing.xxHash64": "0.3.14",
- "Wabbajack.Networking.Discord": "0.3.14",
- "Wabbajack.Networking.GitHub": "0.3.14",
- "Wabbajack.Paths.IO": "0.3.14",
- "Wabbajack.Server.Lib": "0.3.14",
- "Wabbajack.Services.OSIntegrated": "0.3.14",
- "Wabbajack.VFS": "0.3.14",
+ "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",
"MegaApiClient": "1.0.0.0",
"runtimepack.Microsoft.NETCore.App.Runtime.linux-x64": "8.0.19"
},
@@ -1781,7 +1781,7 @@
}
}
},
- "Wabbajack.CLI.Builder/0.3.14": {
+ "Wabbajack.CLI.Builder/0.3.15": {
"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.14"
+ "Wabbajack.Paths": "0.3.15"
},
"runtime": {
"Wabbajack.CLI.Builder.dll": {}
}
},
- "Wabbajack.Common/0.3.14": {
+ "Wabbajack.Common/0.3.15": {
"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.14",
- "Wabbajack.Networking.Http": "0.3.14",
- "Wabbajack.Paths.IO": "0.3.14"
+ "Wabbajack.DTOs": "0.3.15",
+ "Wabbajack.Networking.Http": "0.3.15",
+ "Wabbajack.Paths.IO": "0.3.15"
},
"runtime": {
"Wabbajack.Common.dll": {}
}
},
- "Wabbajack.Compiler/0.3.14": {
+ "Wabbajack.Compiler/0.3.15": {
"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.14",
- "Wabbajack.Installer": "0.3.14",
- "Wabbajack.VFS": "0.3.14",
+ "Wabbajack.Downloaders.Dispatcher": "0.3.15",
+ "Wabbajack.Installer": "0.3.15",
+ "Wabbajack.VFS": "0.3.15",
"ini-parser-netstandard": "2.5.2"
},
"runtime": {
"Wabbajack.Compiler.dll": {}
}
},
- "Wabbajack.Compression.BSA/0.3.14": {
+ "Wabbajack.Compression.BSA/0.3.15": {
"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.14",
- "Wabbajack.DTOs": "0.3.14"
+ "Wabbajack.Common": "0.3.15",
+ "Wabbajack.DTOs": "0.3.15"
},
"runtime": {
"Wabbajack.Compression.BSA.dll": {}
}
},
- "Wabbajack.Compression.Zip/0.3.14": {
+ "Wabbajack.Compression.Zip/0.3.15": {
"dependencies": {
- "Wabbajack.IO.Async": "0.3.14"
+ "Wabbajack.IO.Async": "0.3.15"
},
"runtime": {
"Wabbajack.Compression.Zip.dll": {}
}
},
- "Wabbajack.Configuration/0.3.14": {
+ "Wabbajack.Configuration/0.3.15": {
"runtime": {
"Wabbajack.Configuration.dll": {}
}
},
- "Wabbajack.Downloaders.Bethesda/0.3.14": {
+ "Wabbajack.Downloaders.Bethesda/0.3.15": {
"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.14",
- "Wabbajack.Downloaders.Interfaces": "0.3.14",
- "Wabbajack.Networking.BethesdaNet": "0.3.14"
+ "Wabbajack.Common": "0.3.15",
+ "Wabbajack.Downloaders.Interfaces": "0.3.15",
+ "Wabbajack.Networking.BethesdaNet": "0.3.15"
},
"runtime": {
"Wabbajack.Downloaders.Bethesda.dll": {}
}
},
- "Wabbajack.Downloaders.Dispatcher/0.3.14": {
+ "Wabbajack.Downloaders.Dispatcher/0.3.15": {
"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.14",
- "Wabbajack.Downloaders.GameFile": "0.3.14",
- "Wabbajack.Downloaders.GoogleDrive": "0.3.14",
- "Wabbajack.Downloaders.Http": "0.3.14",
- "Wabbajack.Downloaders.IPS4OAuth2Downloader": "0.3.14",
- "Wabbajack.Downloaders.Interfaces": "0.3.14",
- "Wabbajack.Downloaders.Manual": "0.3.14",
- "Wabbajack.Downloaders.MediaFire": "0.3.14",
- "Wabbajack.Downloaders.Mega": "0.3.14",
- "Wabbajack.Downloaders.ModDB": "0.3.14",
- "Wabbajack.Downloaders.Nexus": "0.3.14",
- "Wabbajack.Downloaders.VerificationCache": "0.3.14",
- "Wabbajack.Downloaders.WabbajackCDN": "0.3.14",
- "Wabbajack.Networking.WabbajackClientApi": "0.3.14"
+ "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"
},
"runtime": {
"Wabbajack.Downloaders.Dispatcher.dll": {}
}
},
- "Wabbajack.Downloaders.GameFile/0.3.14": {
+ "Wabbajack.Downloaders.GameFile/0.3.15": {
"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.14",
- "Wabbajack.VFS": "0.3.14"
+ "Wabbajack.Downloaders.Interfaces": "0.3.15",
+ "Wabbajack.VFS": "0.3.15"
},
"runtime": {
"Wabbajack.Downloaders.GameFile.dll": {}
}
},
- "Wabbajack.Downloaders.GoogleDrive/0.3.14": {
+ "Wabbajack.Downloaders.GoogleDrive/0.3.15": {
"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.14",
- "Wabbajack.DTOs": "0.3.14",
- "Wabbajack.Downloaders.Interfaces": "0.3.14",
- "Wabbajack.Networking.Http": "0.3.14",
- "Wabbajack.Networking.Http.Interfaces": "0.3.14"
+ "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"
},
"runtime": {
"Wabbajack.Downloaders.GoogleDrive.dll": {}
}
},
- "Wabbajack.Downloaders.Http/0.3.14": {
+ "Wabbajack.Downloaders.Http/0.3.15": {
"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.14",
- "Wabbajack.DTOs": "0.3.14",
- "Wabbajack.Downloaders.Interfaces": "0.3.14",
- "Wabbajack.Networking.BethesdaNet": "0.3.14",
- "Wabbajack.Networking.Http.Interfaces": "0.3.14",
- "Wabbajack.Paths.IO": "0.3.14"
+ "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"
},
"runtime": {
"Wabbajack.Downloaders.Http.dll": {}
}
},
- "Wabbajack.Downloaders.Interfaces/0.3.14": {
+ "Wabbajack.Downloaders.Interfaces/0.3.15": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
- "Wabbajack.Compression.Zip": "0.3.14",
- "Wabbajack.DTOs": "0.3.14",
- "Wabbajack.Paths.IO": "0.3.14"
+ "Wabbajack.Compression.Zip": "0.3.15",
+ "Wabbajack.DTOs": "0.3.15",
+ "Wabbajack.Paths.IO": "0.3.15"
},
"runtime": {
"Wabbajack.Downloaders.Interfaces.dll": {}
}
},
- "Wabbajack.Downloaders.IPS4OAuth2Downloader/0.3.14": {
+ "Wabbajack.Downloaders.IPS4OAuth2Downloader/0.3.15": {
"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.14",
- "Wabbajack.Downloaders.Interfaces": "0.3.14",
- "Wabbajack.Networking.Http": "0.3.14",
- "Wabbajack.Networking.Http.Interfaces": "0.3.14"
+ "Wabbajack.Common": "0.3.15",
+ "Wabbajack.Downloaders.Interfaces": "0.3.15",
+ "Wabbajack.Networking.Http": "0.3.15",
+ "Wabbajack.Networking.Http.Interfaces": "0.3.15"
},
"runtime": {
"Wabbajack.Downloaders.IPS4OAuth2Downloader.dll": {}
}
},
- "Wabbajack.Downloaders.Manual/0.3.14": {
+ "Wabbajack.Downloaders.Manual/0.3.15": {
"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.14",
- "Wabbajack.Downloaders.Interfaces": "0.3.14"
+ "Wabbajack.Common": "0.3.15",
+ "Wabbajack.Downloaders.Interfaces": "0.3.15"
},
"runtime": {
"Wabbajack.Downloaders.Manual.dll": {}
}
},
- "Wabbajack.Downloaders.MediaFire/0.3.14": {
+ "Wabbajack.Downloaders.MediaFire/0.3.15": {
"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.14",
- "Wabbajack.Downloaders.Interfaces": "0.3.14",
- "Wabbajack.Networking.Http.Interfaces": "0.3.14"
+ "Wabbajack.Common": "0.3.15",
+ "Wabbajack.Downloaders.Interfaces": "0.3.15",
+ "Wabbajack.Networking.Http.Interfaces": "0.3.15"
},
"runtime": {
"Wabbajack.Downloaders.MediaFire.dll": {}
}
},
- "Wabbajack.Downloaders.Mega/0.3.14": {
+ "Wabbajack.Downloaders.Mega/0.3.15": {
"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.14",
- "Wabbajack.Downloaders.Interfaces": "0.3.14",
- "Wabbajack.Paths.IO": "0.3.14"
+ "Wabbajack.Common": "0.3.15",
+ "Wabbajack.Downloaders.Interfaces": "0.3.15",
+ "Wabbajack.Paths.IO": "0.3.15"
},
"runtime": {
"Wabbajack.Downloaders.Mega.dll": {}
}
},
- "Wabbajack.Downloaders.ModDB/0.3.14": {
+ "Wabbajack.Downloaders.ModDB/0.3.15": {
"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.14",
- "Wabbajack.Downloaders.Interfaces": "0.3.14",
- "Wabbajack.Networking.Http": "0.3.14",
- "Wabbajack.Networking.Http.Interfaces": "0.3.14"
+ "Wabbajack.Common": "0.3.15",
+ "Wabbajack.Downloaders.Interfaces": "0.3.15",
+ "Wabbajack.Networking.Http": "0.3.15",
+ "Wabbajack.Networking.Http.Interfaces": "0.3.15"
},
"runtime": {
"Wabbajack.Downloaders.ModDB.dll": {}
}
},
- "Wabbajack.Downloaders.Nexus/0.3.14": {
+ "Wabbajack.Downloaders.Nexus/0.3.15": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
- "Wabbajack.DTOs": "0.3.14",
- "Wabbajack.Downloaders.Interfaces": "0.3.14",
- "Wabbajack.Hashing.xxHash64": "0.3.14",
- "Wabbajack.Networking.Http": "0.3.14",
- "Wabbajack.Networking.Http.Interfaces": "0.3.14",
- "Wabbajack.Networking.NexusApi": "0.3.14",
- "Wabbajack.Paths": "0.3.14"
+ "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"
},
"runtime": {
"Wabbajack.Downloaders.Nexus.dll": {}
}
},
- "Wabbajack.Downloaders.VerificationCache/0.3.14": {
+ "Wabbajack.Downloaders.VerificationCache/0.3.15": {
"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.14",
- "Wabbajack.Paths.IO": "0.3.14"
+ "Wabbajack.DTOs": "0.3.15",
+ "Wabbajack.Paths.IO": "0.3.15"
},
"runtime": {
"Wabbajack.Downloaders.VerificationCache.dll": {}
}
},
- "Wabbajack.Downloaders.WabbajackCDN/0.3.14": {
+ "Wabbajack.Downloaders.WabbajackCDN/0.3.15": {
"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.14",
- "Wabbajack.Downloaders.Interfaces": "0.3.14",
- "Wabbajack.Networking.Http": "0.3.14",
- "Wabbajack.RateLimiter": "0.3.14"
+ "Wabbajack.Common": "0.3.15",
+ "Wabbajack.Downloaders.Interfaces": "0.3.15",
+ "Wabbajack.Networking.Http": "0.3.15",
+ "Wabbajack.RateLimiter": "0.3.15"
},
"runtime": {
"Wabbajack.Downloaders.WabbajackCDN.dll": {}
}
},
- "Wabbajack.DTOs/0.3.14": {
+ "Wabbajack.DTOs/0.3.15": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
- "Wabbajack.Hashing.xxHash64": "0.3.14",
- "Wabbajack.Paths": "0.3.14"
+ "Wabbajack.Hashing.xxHash64": "0.3.15",
+ "Wabbajack.Paths": "0.3.15"
},
"runtime": {
"Wabbajack.DTOs.dll": {}
}
},
- "Wabbajack.FileExtractor/0.3.14": {
+ "Wabbajack.FileExtractor/0.3.15": {
"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.14",
- "Wabbajack.Compression.BSA": "0.3.14",
- "Wabbajack.Hashing.PHash": "0.3.14",
- "Wabbajack.Paths": "0.3.14"
+ "Wabbajack.Common": "0.3.15",
+ "Wabbajack.Compression.BSA": "0.3.15",
+ "Wabbajack.Hashing.PHash": "0.3.15",
+ "Wabbajack.Paths": "0.3.15"
},
"runtime": {
"Wabbajack.FileExtractor.dll": {}
}
},
- "Wabbajack.Hashing.PHash/0.3.14": {
+ "Wabbajack.Hashing.PHash/0.3.15": {
"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.14",
- "Wabbajack.DTOs": "0.3.14",
- "Wabbajack.Paths": "0.3.14",
- "Wabbajack.Paths.IO": "0.3.14"
+ "Wabbajack.Common": "0.3.15",
+ "Wabbajack.DTOs": "0.3.15",
+ "Wabbajack.Paths": "0.3.15",
+ "Wabbajack.Paths.IO": "0.3.15"
},
"runtime": {
"Wabbajack.Hashing.PHash.dll": {}
}
},
- "Wabbajack.Hashing.xxHash64/0.3.14": {
+ "Wabbajack.Hashing.xxHash64/0.3.15": {
"dependencies": {
- "Wabbajack.Paths": "0.3.14",
- "Wabbajack.RateLimiter": "0.3.14"
+ "Wabbajack.Paths": "0.3.15",
+ "Wabbajack.RateLimiter": "0.3.15"
},
"runtime": {
"Wabbajack.Hashing.xxHash64.dll": {}
}
},
- "Wabbajack.Installer/0.3.14": {
+ "Wabbajack.Installer/0.3.15": {
"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.14",
- "Wabbajack.Downloaders.Dispatcher": "0.3.14",
- "Wabbajack.Downloaders.GameFile": "0.3.14",
- "Wabbajack.FileExtractor": "0.3.14",
- "Wabbajack.Networking.WabbajackClientApi": "0.3.14",
- "Wabbajack.Paths": "0.3.14",
- "Wabbajack.Paths.IO": "0.3.14",
- "Wabbajack.VFS": "0.3.14",
+ "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",
"ini-parser-netstandard": "2.5.2"
},
"runtime": {
"Wabbajack.Installer.dll": {}
}
},
- "Wabbajack.IO.Async/0.3.14": {
+ "Wabbajack.IO.Async/0.3.15": {
"runtime": {
"Wabbajack.IO.Async.dll": {}
}
},
- "Wabbajack.Networking.BethesdaNet/0.3.14": {
+ "Wabbajack.Networking.BethesdaNet/0.3.15": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
- "Wabbajack.DTOs": "0.3.14",
- "Wabbajack.Networking.Http": "0.3.14",
- "Wabbajack.Networking.Http.Interfaces": "0.3.14"
+ "Wabbajack.DTOs": "0.3.15",
+ "Wabbajack.Networking.Http": "0.3.15",
+ "Wabbajack.Networking.Http.Interfaces": "0.3.15"
},
"runtime": {
"Wabbajack.Networking.BethesdaNet.dll": {}
}
},
- "Wabbajack.Networking.Discord/0.3.14": {
+ "Wabbajack.Networking.Discord/0.3.15": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
- "Wabbajack.Networking.Http.Interfaces": "0.3.14"
+ "Wabbajack.Networking.Http.Interfaces": "0.3.15"
},
"runtime": {
"Wabbajack.Networking.Discord.dll": {}
}
},
- "Wabbajack.Networking.GitHub/0.3.14": {
+ "Wabbajack.Networking.GitHub/0.3.15": {
"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.14",
- "Wabbajack.Networking.Http.Interfaces": "0.3.14"
+ "Wabbajack.DTOs": "0.3.15",
+ "Wabbajack.Networking.Http.Interfaces": "0.3.15"
},
"runtime": {
"Wabbajack.Networking.GitHub.dll": {}
}
},
- "Wabbajack.Networking.Http/0.3.14": {
+ "Wabbajack.Networking.Http/0.3.15": {
"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.14",
- "Wabbajack.Downloaders.Interfaces": "0.3.14",
- "Wabbajack.Hashing.xxHash64": "0.3.14",
- "Wabbajack.Networking.Http.Interfaces": "0.3.14",
- "Wabbajack.Paths": "0.3.14",
- "Wabbajack.Paths.IO": "0.3.14"
+ "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"
},
"runtime": {
"Wabbajack.Networking.Http.dll": {}
}
},
- "Wabbajack.Networking.Http.Interfaces/0.3.14": {
+ "Wabbajack.Networking.Http.Interfaces/0.3.15": {
"dependencies": {
- "Wabbajack.Hashing.xxHash64": "0.3.14"
+ "Wabbajack.Hashing.xxHash64": "0.3.15"
},
"runtime": {
"Wabbajack.Networking.Http.Interfaces.dll": {}
}
},
- "Wabbajack.Networking.NexusApi/0.3.14": {
+ "Wabbajack.Networking.NexusApi/0.3.15": {
"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.14",
- "Wabbajack.Networking.Http": "0.3.14",
- "Wabbajack.Networking.Http.Interfaces": "0.3.14",
- "Wabbajack.Networking.WabbajackClientApi": "0.3.14"
+ "Wabbajack.DTOs": "0.3.15",
+ "Wabbajack.Networking.Http": "0.3.15",
+ "Wabbajack.Networking.Http.Interfaces": "0.3.15",
+ "Wabbajack.Networking.WabbajackClientApi": "0.3.15"
},
"runtime": {
"Wabbajack.Networking.NexusApi.dll": {}
}
},
- "Wabbajack.Networking.WabbajackClientApi/0.3.14": {
+ "Wabbajack.Networking.WabbajackClientApi/0.3.15": {
"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.14",
- "Wabbajack.DTOs": "0.3.14",
- "Wabbajack.Paths.IO": "0.3.14",
- "Wabbajack.VFS.Interfaces": "0.3.14",
+ "Wabbajack.Common": "0.3.15",
+ "Wabbajack.DTOs": "0.3.15",
+ "Wabbajack.Paths.IO": "0.3.15",
+ "Wabbajack.VFS.Interfaces": "0.3.15",
"YamlDotNet": "16.3.0"
},
"runtime": {
"Wabbajack.Networking.WabbajackClientApi.dll": {}
}
},
- "Wabbajack.Paths/0.3.14": {
+ "Wabbajack.Paths/0.3.15": {
"runtime": {
"Wabbajack.Paths.dll": {}
}
},
- "Wabbajack.Paths.IO/0.3.14": {
+ "Wabbajack.Paths.IO/0.3.15": {
"dependencies": {
- "Wabbajack.Paths": "0.3.14",
+ "Wabbajack.Paths": "0.3.15",
"shortid": "4.0.0"
},
"runtime": {
"Wabbajack.Paths.IO.dll": {}
}
},
- "Wabbajack.RateLimiter/0.3.14": {
+ "Wabbajack.RateLimiter/0.3.15": {
"runtime": {
"Wabbajack.RateLimiter.dll": {}
}
},
- "Wabbajack.Server.Lib/0.3.14": {
+ "Wabbajack.Server.Lib/0.3.15": {
"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.14",
- "Wabbajack.Networking.Http.Interfaces": "0.3.14",
- "Wabbajack.Services.OSIntegrated": "0.3.14"
+ "Wabbajack.Common": "0.3.15",
+ "Wabbajack.Networking.Http.Interfaces": "0.3.15",
+ "Wabbajack.Services.OSIntegrated": "0.3.15"
},
"runtime": {
"Wabbajack.Server.Lib.dll": {}
}
},
- "Wabbajack.Services.OSIntegrated/0.3.14": {
+ "Wabbajack.Services.OSIntegrated/0.3.15": {
"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.14",
- "Wabbajack.Downloaders.Dispatcher": "0.3.14",
- "Wabbajack.Installer": "0.3.14",
- "Wabbajack.Networking.BethesdaNet": "0.3.14",
- "Wabbajack.Networking.Discord": "0.3.14",
- "Wabbajack.VFS": "0.3.14"
+ "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"
},
"runtime": {
"Wabbajack.Services.OSIntegrated.dll": {}
}
},
- "Wabbajack.VFS/0.3.14": {
+ "Wabbajack.VFS/0.3.15": {
"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.14",
- "Wabbajack.FileExtractor": "0.3.14",
- "Wabbajack.Hashing.PHash": "0.3.14",
- "Wabbajack.Hashing.xxHash64": "0.3.14",
- "Wabbajack.Paths": "0.3.14",
- "Wabbajack.Paths.IO": "0.3.14",
- "Wabbajack.VFS.Interfaces": "0.3.14"
+ "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"
},
"runtime": {
"Wabbajack.VFS.dll": {}
}
},
- "Wabbajack.VFS.Interfaces/0.3.14": {
+ "Wabbajack.VFS.Interfaces/0.3.15": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
- "Wabbajack.DTOs": "0.3.14",
- "Wabbajack.Hashing.xxHash64": "0.3.14",
- "Wabbajack.Paths": "0.3.14"
+ "Wabbajack.DTOs": "0.3.15",
+ "Wabbajack.Hashing.xxHash64": "0.3.15",
+ "Wabbajack.Paths": "0.3.15"
},
"runtime": {
"Wabbajack.VFS.Interfaces.dll": {}
@@ -2332,7 +2332,7 @@
}
},
"libraries": {
- "jackify-engine/0.3.14": {
+ "jackify-engine/0.3.15": {
"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.14": {
+ "Wabbajack.CLI.Builder/0.3.15": {
"type": "project",
"serviceable": false,
"sha512": ""
},
- "Wabbajack.Common/0.3.14": {
+ "Wabbajack.Common/0.3.15": {
"type": "project",
"serviceable": false,
"sha512": ""
},
- "Wabbajack.Compiler/0.3.14": {
+ "Wabbajack.Compiler/0.3.15": {
"type": "project",
"serviceable": false,
"sha512": ""
},
- "Wabbajack.Compression.BSA/0.3.14": {
+ "Wabbajack.Compression.BSA/0.3.15": {
"type": "project",
"serviceable": false,
"sha512": ""
},
- "Wabbajack.Compression.Zip/0.3.14": {
+ "Wabbajack.Compression.Zip/0.3.15": {
"type": "project",
"serviceable": false,
"sha512": ""
},
- "Wabbajack.Configuration/0.3.14": {
+ "Wabbajack.Configuration/0.3.15": {
"type": "project",
"serviceable": false,
"sha512": ""
},
- "Wabbajack.Downloaders.Bethesda/0.3.14": {
+ "Wabbajack.Downloaders.Bethesda/0.3.15": {
"type": "project",
"serviceable": false,
"sha512": ""
},
- "Wabbajack.Downloaders.Dispatcher/0.3.14": {
+ "Wabbajack.Downloaders.Dispatcher/0.3.15": {
"type": "project",
"serviceable": false,
"sha512": ""
},
- "Wabbajack.Downloaders.GameFile/0.3.14": {
+ "Wabbajack.Downloaders.GameFile/0.3.15": {
"type": "project",
"serviceable": false,
"sha512": ""
},
- "Wabbajack.Downloaders.GoogleDrive/0.3.14": {
+ "Wabbajack.Downloaders.GoogleDrive/0.3.15": {
"type": "project",
"serviceable": false,
"sha512": ""
},
- "Wabbajack.Downloaders.Http/0.3.14": {
+ "Wabbajack.Downloaders.Http/0.3.15": {
"type": "project",
"serviceable": false,
"sha512": ""
},
- "Wabbajack.Downloaders.Interfaces/0.3.14": {
+ "Wabbajack.Downloaders.Interfaces/0.3.15": {
"type": "project",
"serviceable": false,
"sha512": ""
},
- "Wabbajack.Downloaders.IPS4OAuth2Downloader/0.3.14": {
+ "Wabbajack.Downloaders.IPS4OAuth2Downloader/0.3.15": {
"type": "project",
"serviceable": false,
"sha512": ""
},
- "Wabbajack.Downloaders.Manual/0.3.14": {
+ "Wabbajack.Downloaders.Manual/0.3.15": {
"type": "project",
"serviceable": false,
"sha512": ""
},
- "Wabbajack.Downloaders.MediaFire/0.3.14": {
+ "Wabbajack.Downloaders.MediaFire/0.3.15": {
"type": "project",
"serviceable": false,
"sha512": ""
},
- "Wabbajack.Downloaders.Mega/0.3.14": {
+ "Wabbajack.Downloaders.Mega/0.3.15": {
"type": "project",
"serviceable": false,
"sha512": ""
},
- "Wabbajack.Downloaders.ModDB/0.3.14": {
+ "Wabbajack.Downloaders.ModDB/0.3.15": {
"type": "project",
"serviceable": false,
"sha512": ""
},
- "Wabbajack.Downloaders.Nexus/0.3.14": {
+ "Wabbajack.Downloaders.Nexus/0.3.15": {
"type": "project",
"serviceable": false,
"sha512": ""
},
- "Wabbajack.Downloaders.VerificationCache/0.3.14": {
+ "Wabbajack.Downloaders.VerificationCache/0.3.15": {
"type": "project",
"serviceable": false,
"sha512": ""
},
- "Wabbajack.Downloaders.WabbajackCDN/0.3.14": {
+ "Wabbajack.Downloaders.WabbajackCDN/0.3.15": {
"type": "project",
"serviceable": false,
"sha512": ""
},
- "Wabbajack.DTOs/0.3.14": {
+ "Wabbajack.DTOs/0.3.15": {
"type": "project",
"serviceable": false,
"sha512": ""
},
- "Wabbajack.FileExtractor/0.3.14": {
+ "Wabbajack.FileExtractor/0.3.15": {
"type": "project",
"serviceable": false,
"sha512": ""
},
- "Wabbajack.Hashing.PHash/0.3.14": {
+ "Wabbajack.Hashing.PHash/0.3.15": {
"type": "project",
"serviceable": false,
"sha512": ""
},
- "Wabbajack.Hashing.xxHash64/0.3.14": {
+ "Wabbajack.Hashing.xxHash64/0.3.15": {
"type": "project",
"serviceable": false,
"sha512": ""
},
- "Wabbajack.Installer/0.3.14": {
+ "Wabbajack.Installer/0.3.15": {
"type": "project",
"serviceable": false,
"sha512": ""
},
- "Wabbajack.IO.Async/0.3.14": {
+ "Wabbajack.IO.Async/0.3.15": {
"type": "project",
"serviceable": false,
"sha512": ""
},
- "Wabbajack.Networking.BethesdaNet/0.3.14": {
+ "Wabbajack.Networking.BethesdaNet/0.3.15": {
"type": "project",
"serviceable": false,
"sha512": ""
},
- "Wabbajack.Networking.Discord/0.3.14": {
+ "Wabbajack.Networking.Discord/0.3.15": {
"type": "project",
"serviceable": false,
"sha512": ""
},
- "Wabbajack.Networking.GitHub/0.3.14": {
+ "Wabbajack.Networking.GitHub/0.3.15": {
"type": "project",
"serviceable": false,
"sha512": ""
},
- "Wabbajack.Networking.Http/0.3.14": {
+ "Wabbajack.Networking.Http/0.3.15": {
"type": "project",
"serviceable": false,
"sha512": ""
},
- "Wabbajack.Networking.Http.Interfaces/0.3.14": {
+ "Wabbajack.Networking.Http.Interfaces/0.3.15": {
"type": "project",
"serviceable": false,
"sha512": ""
},
- "Wabbajack.Networking.NexusApi/0.3.14": {
+ "Wabbajack.Networking.NexusApi/0.3.15": {
"type": "project",
"serviceable": false,
"sha512": ""
},
- "Wabbajack.Networking.WabbajackClientApi/0.3.14": {
+ "Wabbajack.Networking.WabbajackClientApi/0.3.15": {
"type": "project",
"serviceable": false,
"sha512": ""
},
- "Wabbajack.Paths/0.3.14": {
+ "Wabbajack.Paths/0.3.15": {
"type": "project",
"serviceable": false,
"sha512": ""
},
- "Wabbajack.Paths.IO/0.3.14": {
+ "Wabbajack.Paths.IO/0.3.15": {
"type": "project",
"serviceable": false,
"sha512": ""
},
- "Wabbajack.RateLimiter/0.3.14": {
+ "Wabbajack.RateLimiter/0.3.15": {
"type": "project",
"serviceable": false,
"sha512": ""
},
- "Wabbajack.Server.Lib/0.3.14": {
+ "Wabbajack.Server.Lib/0.3.15": {
"type": "project",
"serviceable": false,
"sha512": ""
},
- "Wabbajack.Services.OSIntegrated/0.3.14": {
+ "Wabbajack.Services.OSIntegrated/0.3.15": {
"type": "project",
"serviceable": false,
"sha512": ""
},
- "Wabbajack.VFS/0.3.14": {
+ "Wabbajack.VFS/0.3.15": {
"type": "project",
"serviceable": false,
"sha512": ""
},
- "Wabbajack.VFS.Interfaces/0.3.14": {
+ "Wabbajack.VFS.Interfaces/0.3.15": {
"type": "project",
"serviceable": false,
"sha512": ""
diff --git a/jackify/engine/jackify-engine.dll b/jackify/engine/jackify-engine.dll
index 14aa184..fee0c2c 100644
Binary files a/jackify/engine/jackify-engine.dll and b/jackify/engine/jackify-engine.dll differ
diff --git a/jackify/frontends/gui/main.py b/jackify/frontends/gui/main.py
index cc6c3a5..9432cd9 100644
--- a/jackify/frontends/gui/main.py
+++ b/jackify/frontends/gui/main.py
@@ -7,6 +7,7 @@ This replaces the legacy jackify_gui implementation with a refactored architectu
import sys
import os
+import logging
from pathlib import Path
# Suppress xkbcommon locale errors (harmless but annoying)
@@ -81,6 +82,9 @@ if '--env-diagnostic' in sys.argv:
from jackify import __version__ as jackify_version
+# Initialize logger
+logger = logging.getLogger(__name__)
+
if '--help' in sys.argv or '-h' in sys.argv:
print("""Jackify - Native Linux Modlist Manager\n\nUsage:\n jackify [--cli] [--debug] [--version] [--help]\n\nOptions:\n --cli Launch CLI frontend\n --debug Enable debug logging\n --version Show version and exit\n --help, -h Show this help message and exit\n\nIf no options are given, the GUI will launch by default.\n""")
sys.exit(0)
@@ -98,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
+ QStackedWidget, QHBoxLayout, QDialog, QFormLayout, QLineEdit, QCheckBox, QSpinBox, QMessageBox, QGroupBox, QGridLayout, QFileDialog, QToolButton, QStyle, QComboBox
)
from PySide6.QtCore import Qt, QEvent
from PySide6.QtGui import QIcon
@@ -298,6 +302,33 @@ class SettingsDialog(QDialog):
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; }")
@@ -447,6 +478,85 @@ class SettingsDialog(QDialog):
api_key = text.strip()
self.config_handler.save_api_key(api_key)
+ def _get_proton_10_path(self):
+ """Get Proton 10 path if available, fallback to auto"""
+ try:
+ from jackify.backend.handlers.wine_utils import WineUtils
+ available_protons = WineUtils.scan_valve_proton_versions()
+
+ # Look for Proton 10.x
+ for proton in available_protons:
+ if proton['version'].startswith('10.'):
+ return proton['path']
+
+ # Fallback to auto if no Proton 10 found
+ return 'auto'
+ except:
+ return 'auto'
+
+ def _populate_proton_dropdown(self):
+ """Populate Proton version dropdown with detected versions (includes GE-Proton and Valve Proton)"""
+ try:
+ from jackify.backend.handlers.wine_utils import WineUtils
+
+ # Get all available Proton versions (GE-Proton + Valve Proton)
+ available_protons = WineUtils.scan_all_proton_versions()
+
+ # Add "Auto" option first
+ self.proton_dropdown.addItem("Auto", "auto")
+
+ # Add detected Proton versions with type indicators
+ for proton in available_protons:
+ proton_name = proton.get('name', 'Unknown Proton')
+ proton_type = proton.get('type', 'Unknown')
+
+ # Format display name to show type for clarity
+ if proton_type == 'GE-Proton':
+ display_name = f"{proton_name} (GE)"
+ elif proton_type == 'Valve-Proton':
+ display_name = f"{proton_name}"
+ else:
+ display_name = proton_name
+
+ self.proton_dropdown.addItem(display_name, str(proton['path']))
+
+ # Load saved preference and determine UI selection
+ saved_proton = self.config_handler.get('proton_path', self._get_proton_10_path())
+
+ # Check if saved path matches any specific Proton in dropdown
+ found_match = False
+ for i in range(self.proton_dropdown.count()):
+ if self.proton_dropdown.itemData(i) == saved_proton:
+ self.proton_dropdown.setCurrentIndex(i)
+ found_match = True
+ break
+
+ # If no exact match found, check if it's a resolved auto-selection
+ if not found_match and saved_proton != "auto":
+ # This means config has a resolved path from previous "Auto" selection
+ # Show "Auto" in UI since user chose auto-detection
+ for i in range(self.proton_dropdown.count()):
+ if self.proton_dropdown.itemData(i) == "auto":
+ self.proton_dropdown.setCurrentIndex(i)
+ break
+
+ except Exception as e:
+ logger.error(f"Failed to populate Proton dropdown: {e}")
+ # Fallback: just show auto
+ self.proton_dropdown.addItem("Auto", "auto")
+
+ def _refresh_proton_dropdown(self):
+ """Refresh Proton dropdown with latest detected versions"""
+ current_selection = self.proton_dropdown.currentData()
+ self.proton_dropdown.clear()
+ self._populate_proton_dropdown()
+
+ # Restore selection if still available
+ for i in range(self.proton_dropdown.count()):
+ if self.proton_dropdown.itemData(i) == current_selection:
+ self.proton_dropdown.setCurrentIndex(i)
+ break
+
def _save(self):
# Validate values
for k, (multithreading_checkbox, max_tasks_spin) in self.resource_edits.items():
@@ -490,6 +600,33 @@ class SettingsDialog(QDialog):
# Save jackify data directory (always store actual path, never None)
jackify_data_dir = self.jackify_data_dir_edit.text().strip()
self.config_handler.set("jackify_data_dir", jackify_data_dir)
+
+ # Save Proton selection - resolve "auto" to actual path
+ selected_proton_path = self.proton_dropdown.currentData()
+ if selected_proton_path == "auto":
+ # Resolve "auto" to actual best Proton path using unified detection
+ try:
+ from jackify.backend.handlers.wine_utils import WineUtils
+ best_proton = WineUtils.select_best_proton()
+
+ if best_proton:
+ resolved_path = str(best_proton['path'])
+ resolved_version = best_proton['name']
+ else:
+ resolved_path = "auto"
+ resolved_version = "auto"
+ except:
+ resolved_path = "auto"
+ resolved_version = "auto"
+ else:
+ # User selected specific Proton version
+ resolved_path = selected_proton_path
+ # Extract version from dropdown text
+ resolved_version = self.proton_dropdown.currentText()
+
+ self.config_handler.set("proton_path", resolved_path)
+ self.config_handler.set("proton_version", resolved_version)
+
self.config_handler.save_config()
# Refresh cached paths in GUI screens if Jackify directory changed
diff --git a/jackify/frontends/gui/screens/configure_new_modlist.py b/jackify/frontends/gui/screens/configure_new_modlist.py
index 43e2395..b20860e 100644
--- a/jackify/frontends/gui/screens/configure_new_modlist.py
+++ b/jackify/frontends/gui/screens/configure_new_modlist.py
@@ -22,6 +22,7 @@ from jackify.backend.handlers.config_handler import ConfigHandler
from ..dialogs import SuccessDialog
from PySide6.QtWidgets import QApplication
from jackify.frontends.gui.services.message_service import MessageService
+from jackify.shared.resolution_utils import get_resolution_fallback
def debug_print(message):
"""Print debug message only if debug mode is enabled"""
@@ -1033,7 +1034,7 @@ class ConfigureNewModlistScreen(QWidget):
try:
# Get resolution from UI
resolution = self.resolution_combo.currentText()
- resolution_value = resolution.split()[0] if resolution != "Leave unchanged" else '2560x1600'
+ resolution_value = resolution.split()[0] if resolution != "Leave unchanged" else None
# Update the context with the new AppID (same format as manual steps)
mo2_exe_path = self.install_dir_edit.text().strip()
@@ -1082,7 +1083,7 @@ class ConfigureNewModlistScreen(QWidget):
nexus_api_key='', # Not needed for configuration
modlist_value=self.context.get('modlist_value'),
modlist_source=self.context.get('modlist_source', 'identifier'),
- resolution=self.context.get('resolution', '2560x1600'),
+ resolution=self.context.get('resolution') or get_resolution_fallback(None),
skip_confirmation=True
)
diff --git a/jackify/frontends/gui/screens/install_modlist.py b/jackify/frontends/gui/screens/install_modlist.py
index c140ed5..97f12d5 100644
--- a/jackify/frontends/gui/screens/install_modlist.py
+++ b/jackify/frontends/gui/screens/install_modlist.py
@@ -367,6 +367,10 @@ class InstallModlistScreen(QWidget):
self.resolution_service = ResolutionService()
self.config_handler = ConfigHandler()
self.protontricks_service = ProtontricksDetectionService()
+
+ # Somnium guidance tracking
+ self._show_somnium_guidance = False
+ self._somnium_install_dir = None
# Scroll tracking for professional auto-scroll behavior
self._user_manually_scrolled = False
@@ -1356,7 +1360,8 @@ class InstallModlistScreen(QWidget):
'oblivion': 'oblivion',
'starfield': 'starfield',
'oblivion_remastered': 'oblivion_remastered',
- 'enderal': 'enderal'
+ 'enderal': 'enderal',
+ 'enderal special edition': 'enderal'
}
game_type = game_mapping.get(game_name.lower())
debug_print(f"DEBUG: Mapped game_name '{game_name}' to game_type: '{game_type}'")
@@ -1373,6 +1378,7 @@ class InstallModlistScreen(QWidget):
# Check if game is supported
debug_print(f"DEBUG: Checking if game_type '{game_type}' is supported")
+ debug_print(f"DEBUG: game_type='{game_type}', game_name='{game_name}'")
is_supported = self.wabbajack_parser.is_supported_game(game_type) if game_type else False
debug_print(f"DEBUG: is_supported_game('{game_type}') returned: {is_supported}")
@@ -1760,10 +1766,26 @@ class InstallModlistScreen(QWidget):
final_exe_path = os.path.join(install_dir, "ModOrganizer.exe")
if not os.path.exists(final_exe_path):
- self._safe_append_text(f"ERROR: ModOrganizer.exe not found at {final_exe_path}")
- MessageService.critical(self, "ModOrganizer.exe Not Found",
- f"ModOrganizer.exe not found at:\n{final_exe_path}\n\nCannot proceed with automated setup.")
- return
+ # Check if this is Somnium specifically (uses files/ subdirectory)
+ modlist_name_lower = modlist_name.lower()
+ if "somnium" in modlist_name_lower:
+ somnium_exe_path = os.path.join(install_dir, "files", "ModOrganizer.exe")
+ if os.path.exists(somnium_exe_path):
+ final_exe_path = somnium_exe_path
+ self._safe_append_text(f"Detected Somnium modlist - will proceed with automated setup")
+ # Show Somnium guidance popup after automated workflow completes
+ self._show_somnium_guidance = True
+ self._somnium_install_dir = install_dir
+ else:
+ self._safe_append_text(f"ERROR: Somnium ModOrganizer.exe not found at {somnium_exe_path}")
+ MessageService.critical(self, "Somnium ModOrganizer.exe Not Found",
+ f"Expected Somnium ModOrganizer.exe not found at:\n{somnium_exe_path}\n\nCannot proceed with automated setup.")
+ return
+ else:
+ self._safe_append_text(f"ERROR: ModOrganizer.exe not found at {final_exe_path}")
+ MessageService.critical(self, "ModOrganizer.exe Not Found",
+ f"ModOrganizer.exe not found at:\n{final_exe_path}\n\nCannot proceed with automated setup.")
+ return
# Run automated prefix creation in separate thread
from PySide6.QtCore import QThread, Signal
@@ -1940,6 +1962,10 @@ class InstallModlistScreen(QWidget):
self._enable_controls_after_operation()
if success:
+ # Check if we need to show Somnium guidance
+ if self._show_somnium_guidance:
+ self._show_somnium_post_install_guidance()
+
# Show celebration SuccessDialog after the entire workflow
from ..dialogs import SuccessDialog
import time
@@ -2041,11 +2067,20 @@ class InstallModlistScreen(QWidget):
self.cancel_btn.setVisible(True)
self.cancel_install_btn.setVisible(False)
+ def _get_mo2_path(self, install_dir, modlist_name):
+ """Get ModOrganizer.exe path, handling Somnium's non-standard structure"""
+ mo2_exe_path = os.path.join(install_dir, "ModOrganizer.exe")
+ if not os.path.exists(mo2_exe_path) and "somnium" in modlist_name.lower():
+ somnium_path = os.path.join(install_dir, "files", "ModOrganizer.exe")
+ if os.path.exists(somnium_path):
+ mo2_exe_path = somnium_path
+ return mo2_exe_path
+
def validate_manual_steps_completion(self):
"""Validate that manual steps were actually completed and handle retry logic"""
modlist_name = self.modlist_name_edit.text().strip()
install_dir = self.install_dir_edit.text().strip()
- mo2_exe_path = os.path.join(install_dir, "ModOrganizer.exe")
+ mo2_exe_path = self._get_mo2_path(install_dir, modlist_name)
# Add delay to allow Steam filesystem updates to complete
self._safe_append_text("Waiting for Steam filesystem updates to complete...")
@@ -2283,7 +2318,7 @@ class InstallModlistScreen(QWidget):
updated_context = {
'name': modlist_name,
'path': install_dir,
- 'mo2_exe_path': os.path.join(install_dir, "ModOrganizer.exe"),
+ 'mo2_exe_path': self._get_mo2_path(install_dir, modlist_name),
'modlist_value': None,
'modlist_source': None,
'resolution': getattr(self, '_current_resolution', '2560x1600'),
@@ -2381,7 +2416,7 @@ class InstallModlistScreen(QWidget):
updated_context = {
'name': modlist_name,
'path': install_dir,
- 'mo2_exe_path': os.path.join(install_dir, "ModOrganizer.exe"),
+ 'mo2_exe_path': self._get_mo2_path(install_dir, modlist_name),
'modlist_value': None,
'modlist_source': None,
'resolution': getattr(self, '_current_resolution', '2560x1600'),
@@ -2616,6 +2651,26 @@ class InstallModlistScreen(QWidget):
self._safe_append_text("Installation cancelled by user.")
+ def _show_somnium_post_install_guidance(self):
+ """Show guidance popup for Somnium post-installation steps"""
+ from ..widgets.message_service import MessageService
+
+ guidance_text = f"""Somnium Post-Installation Required
+Due to Somnium's non-standard folder structure, you need to manually update the binary paths in ModOrganizer:
+1. Launch the Steam shortcut created for Somnium
+2. In ModOrganizer, go to Settings → Executables
+3. For each executable entry (SKSE64, etc.), update the binary path to point to:
+{self._somnium_install_dir}/files/root/Enderal Special Edition/skse64_loader.exe
+Note: Full Somnium support will be added in a future Jackify update.
+You can also refer to the Somnium installation guide at:
+https://wiki.scenicroute.games/Somnium/1_Installation.html"""
+
+ MessageService.information(self, "Somnium Setup Required", guidance_text)
+
+ # Reset the guidance flag
+ self._show_somnium_guidance = False
+ self._somnium_install_dir = None
+
def cancel_and_cleanup(self):
"""Handle Cancel button - clean up processes and go back"""
self.cleanup_processes()
diff --git a/jackify/frontends/gui/widgets/unsupported_game_dialog.py b/jackify/frontends/gui/widgets/unsupported_game_dialog.py
index 48b8570..1df6686 100644
--- a/jackify/frontends/gui/widgets/unsupported_game_dialog.py
+++ b/jackify/frontends/gui/widgets/unsupported_game_dialog.py
@@ -94,6 +94,7 @@ class UnsupportedGameDialog(QDialog):
For unsupported games, you will need to manually configure Steam shortcuts and other post-install steps.
@@ -113,6 +114,7 @@ class UnsupportedGameDialog(QDialog):For unsupported games, you will need to manually configure Steam shortcuts and other post-install steps.
diff --git a/jackify/shared/resolution_utils.py b/jackify/shared/resolution_utils.py new file mode 100644 index 0000000..a7aea84 --- /dev/null +++ b/jackify/shared/resolution_utils.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Resolution Utilities Module +Provides utility functions for handling resolution across GUI and CLI frontends +""" + +import logging +import os +from typing import Optional + +logger = logging.getLogger(__name__) + + +def get_default_resolution() -> str: + """ + Get the appropriate default resolution based on system detection and user preferences. + + Returns: + str: Resolution string (e.g., '1920x1080', '1280x800') + """ + try: + # First try to get saved resolution from config + from ..backend.services.resolution_service import ResolutionService + resolution_service = ResolutionService() + + saved_resolution = resolution_service.get_saved_resolution() + if saved_resolution and saved_resolution != 'Leave unchanged': + logger.debug(f"Using saved resolution: {saved_resolution}") + return saved_resolution + + except Exception as e: + logger.warning(f"Could not load ResolutionService: {e}") + + try: + # Check for Steam Deck + if _is_steam_deck(): + logger.debug("Steam Deck detected, using 1280x800") + return "1280x800" + + except Exception as e: + logger.warning(f"Error detecting Steam Deck: {e}") + + # Fallback to common 1080p instead of arbitrary resolution + logger.debug("Using fallback resolution: 1920x1080") + return "1920x1080" + + +def _is_steam_deck() -> bool: + """ + Detect if running on Steam Deck + + Returns: + bool: True if Steam Deck detected + """ + try: + if os.path.exists("/etc/os-release"): + with open("/etc/os-release", "r") as f: + content = f.read().lower() + return "steamdeck" in content or "steamos" in content + except Exception as e: + logger.debug(f"Error reading /etc/os-release: {e}") + + return False + + +def get_resolution_fallback(current_resolution: Optional[str]) -> str: + """ + Get appropriate resolution fallback when current resolution is invalid or None + + Args: + current_resolution: Current resolution value that might be None/invalid + + Returns: + str: Valid resolution string + """ + if current_resolution and current_resolution != 'Leave unchanged': + # Validate format + if _validate_resolution_format(current_resolution): + return current_resolution + + # Use proper default resolution logic + return get_default_resolution() + + +def _validate_resolution_format(resolution: str) -> bool: + """ + Validate resolution format + + Args: + resolution: Resolution string to validate + + Returns: + bool: True if valid WxH format + """ + import re + + if not resolution: + return False + + # Handle Steam Deck format + clean_resolution = resolution.replace(' (Steam Deck)', '') + + # Check WxH format + if re.match(r'^[0-9]+x[0-9]+$', clean_resolution): + try: + width, height = clean_resolution.split('x') + width_int, height_int = int(width), int(height) + return 0 < width_int <= 10000 and 0 < height_int <= 10000 + except ValueError: + return False + + return False \ No newline at end of file