mirror of
https://github.com/Omni-guides/Jackify.git
synced 2026-01-17 11:37:01 +01:00
Sync from development - prepare for v0.1.4
This commit is contained in:
45
CHANGELOG.md
45
CHANGELOG.md
@@ -1,5 +1,50 @@
|
|||||||
# Jackify Changelog
|
# 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
|
## v0.1.2 - About Dialog and System Information
|
||||||
**Release Date:** September 16, 2025
|
**Release Date:** September 16, 2025
|
||||||
|
|
||||||
|
|||||||
@@ -5,4 +5,4 @@ This package provides both CLI and GUI interfaces for managing
|
|||||||
Wabbajack modlists natively on Linux systems.
|
Wabbajack modlists natively on Linux systems.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__version__ = "0.1.2"
|
__version__ = "0.1.4"
|
||||||
|
|||||||
@@ -23,6 +23,44 @@ from jackify.backend.handlers.config_handler import ConfigHandler
|
|||||||
|
|
||||||
# UI Colors already imported above
|
# 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
|
# Attempt to import readline for tab completion
|
||||||
READLINE_AVAILABLE = False
|
READLINE_AVAILABLE = False
|
||||||
try:
|
try:
|
||||||
@@ -1248,13 +1286,16 @@ class ModlistInstallCLI:
|
|||||||
from jackify.backend.services.native_steam_service import NativeSteamService
|
from jackify.backend.services.native_steam_service import NativeSteamService
|
||||||
steam_service = 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(
|
success, app_id = steam_service.create_shortcut_with_proton(
|
||||||
app_name=config_context['name'],
|
app_name=config_context['name'],
|
||||||
exe_path=config_context['mo2_exe_path'],
|
exe_path=config_context['mo2_exe_path'],
|
||||||
start_dir=os.path.dirname(config_context['mo2_exe_path']),
|
start_dir=os.path.dirname(config_context['mo2_exe_path']),
|
||||||
launch_options="%command%",
|
launch_options="%command%",
|
||||||
tags=["Jackify"],
|
tags=["Jackify"],
|
||||||
proton_version="proton_experimental"
|
proton_version=proton_version
|
||||||
)
|
)
|
||||||
|
|
||||||
if not success or not app_id:
|
if not success or not app_id:
|
||||||
|
|||||||
@@ -47,8 +47,10 @@ class ConfigHandler:
|
|||||||
# If steam_path is not set, detect it
|
# If steam_path is not set, detect it
|
||||||
if not self.settings["steam_path"]:
|
if not self.settings["steam_path"]:
|
||||||
self.settings["steam_path"] = self._detect_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 jackify_data_dir is not set, initialize it to default
|
||||||
if not self.settings.get("jackify_data_dir"):
|
if not self.settings.get("jackify_data_dir"):
|
||||||
@@ -494,4 +496,28 @@ class ConfigHandler:
|
|||||||
logger.error(f"Error saving modlist downloads base directory: {e}")
|
logger.error(f"Error saving modlist downloads base directory: {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def _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"
|
||||||
|
|
||||||
|
|
||||||
@@ -1163,7 +1163,7 @@ class ModlistHandler:
|
|||||||
# Determine game type
|
# Determine game type
|
||||||
game = (game_var_full or modlist_name or "").lower().replace(" ", "")
|
game = (game_var_full or modlist_name or "").lower().replace(" ", "")
|
||||||
# Add game-specific extras
|
# 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"]
|
extras += ["d3dcompiler_47", "d3dx11_43", "d3dcompiler_43", "dotnet6", "dotnet7"]
|
||||||
elif "falloutnewvegas" in game or "fnv" in game or "oblivion" in game:
|
elif "falloutnewvegas" in game or "fnv" in game or "oblivion" in game:
|
||||||
extras += ["d3dx9_43", "d3dx9"]
|
extras += ["d3dx9_43", "d3dx9"]
|
||||||
@@ -1238,6 +1238,12 @@ class ModlistHandler:
|
|||||||
# Check ModOrganizer.ini for indicators (nvse/enderal) as an early, robust signal
|
# Check ModOrganizer.ini for indicators (nvse/enderal) as an early, robust signal
|
||||||
try:
|
try:
|
||||||
mo2_ini = modlist_path / "ModOrganizer.ini"
|
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():
|
if mo2_ini.exists():
|
||||||
try:
|
try:
|
||||||
content = mo2_ini.read_text(errors='ignore').lower()
|
content = mo2_ini.read_text(errors='ignore').lower()
|
||||||
|
|||||||
@@ -988,8 +988,8 @@ class ShortcutHandler:
|
|||||||
shortcuts_data = VDFHandler.load(shortcuts_vdf_path, binary=True)
|
shortcuts_data = VDFHandler.load(shortcuts_vdf_path, binary=True)
|
||||||
if shortcuts_data and 'shortcuts' in shortcuts_data:
|
if shortcuts_data and 'shortcuts' in shortcuts_data:
|
||||||
for idx, shortcut in shortcuts_data['shortcuts'].items():
|
for idx, shortcut in shortcuts_data['shortcuts'].items():
|
||||||
app_name = shortcut.get('AppName', '').strip()
|
app_name = shortcut.get('AppName', shortcut.get('appname', '')).strip()
|
||||||
exe = shortcut.get('Exe', '').strip('"').strip()
|
exe = shortcut.get('Exe', shortcut.get('exe', '')).strip('"').strip()
|
||||||
vdf_shortcuts.append((app_name, exe, idx))
|
vdf_shortcuts.append((app_name, exe, idx))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f"Error parsing shortcuts.vdf for exe path matching: {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}")
|
self.logger.warning(f"Skipping invalid shortcut entry (not a dict) at index {shortcut_id} in {shortcuts_file}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
app_name = shortcut.get('AppName')
|
app_name = shortcut.get('AppName', shortcut.get('appname'))
|
||||||
exe_path = shortcut.get('Exe', '').strip('"')
|
exe_path = shortcut.get('Exe', shortcut.get('exe', '')).strip('"')
|
||||||
start_dir = shortcut.get('StartDir', '').strip('"')
|
start_dir = shortcut.get('StartDir', shortcut.get('startdir', '')).strip('"')
|
||||||
|
|
||||||
# Check if the base name of the exe_path matches the target
|
# 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:
|
if app_name and start_dir and os.path.basename(exe_path) == executable_name:
|
||||||
|
|||||||
@@ -132,7 +132,8 @@ class WabbajackParser:
|
|||||||
'falloutnv': 'Fallout New Vegas',
|
'falloutnv': 'Fallout New Vegas',
|
||||||
'oblivion': 'Oblivion',
|
'oblivion': 'Oblivion',
|
||||||
'starfield': 'Starfield',
|
'starfield': 'Starfield',
|
||||||
'oblivion_remastered': 'Oblivion Remastered'
|
'oblivion_remastered': 'Oblivion Remastered',
|
||||||
|
'enderal': 'Enderal'
|
||||||
}
|
}
|
||||||
return [display_names.get(game, game) for game in self.supported_games]
|
return [display_names.get(game, game) for game in self.supported_games]
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import shutil
|
|||||||
import time
|
import time
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import glob
|
import glob
|
||||||
from typing import Optional, Tuple
|
from typing import Optional, Tuple, List, Dict
|
||||||
from .subprocess_utils import get_clean_subprocess_env
|
from .subprocess_utils import get_clean_subprocess_env
|
||||||
|
|
||||||
# Initialize logger
|
# Initialize logger
|
||||||
@@ -643,7 +643,20 @@ class WineUtils:
|
|||||||
wine_bin = subdir / "files/bin/wine"
|
wine_bin = subdir / "files/bin/wine"
|
||||||
if wine_bin.is_file():
|
if wine_bin.is_file():
|
||||||
return str(wine_bin)
|
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:
|
for base_path in steam_common_paths:
|
||||||
wine_bin = base_path / "Proton - Experimental" / "files/bin/wine"
|
wine_bin = base_path / "Proton - Experimental" / "files/bin/wine"
|
||||||
if wine_bin.is_file():
|
if wine_bin.is_file():
|
||||||
@@ -698,4 +711,276 @@ class WineUtils:
|
|||||||
proton_path = str(Path(wine_bin).parent.parent)
|
proton_path = str(Path(wine_bin).parent.parent)
|
||||||
logger.debug(f"Found Proton path: {proton_path}")
|
logger.debug(f"Found Proton path: {proton_path}")
|
||||||
|
|
||||||
return compatdata_path, proton_path, wine_bin
|
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
|
||||||
@@ -38,6 +38,44 @@ class AutomatedPrefixService:
|
|||||||
"""Get consistent progress timestamp"""
|
"""Get consistent progress timestamp"""
|
||||||
from jackify.shared.timing import get_timestamp
|
from jackify.shared.timing import get_timestamp
|
||||||
return 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,
|
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}")
|
logger.warning(f"Could not generate STEAM_COMPAT_MOUNTS, using default: {e}")
|
||||||
launch_options = "%command%"
|
launch_options = "%command%"
|
||||||
|
|
||||||
|
# Get user's preferred Proton version
|
||||||
|
proton_version = self._get_user_proton_version()
|
||||||
|
|
||||||
# Create shortcut with Proton using native service
|
# Create shortcut with Proton using native service
|
||||||
success, app_id = steam_service.create_shortcut_with_proton(
|
success, app_id = steam_service.create_shortcut_with_proton(
|
||||||
app_name=shortcut_name,
|
app_name=shortcut_name,
|
||||||
@@ -94,7 +135,7 @@ class AutomatedPrefixService:
|
|||||||
start_dir=modlist_install_dir,
|
start_dir=modlist_install_dir,
|
||||||
launch_options=launch_options,
|
launch_options=launch_options,
|
||||||
tags=["Jackify"],
|
tags=["Jackify"],
|
||||||
proton_version="proton_experimental"
|
proton_version=proton_version
|
||||||
)
|
)
|
||||||
|
|
||||||
if success and app_id:
|
if success and app_id:
|
||||||
@@ -292,13 +333,13 @@ class AutomatedPrefixService:
|
|||||||
logger.error(f"Steam userdata directory not found: {userdata_dir}")
|
logger.error(f"Steam userdata directory not found: {userdata_dir}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Find the first user directory (most systems have only one user)
|
# 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()]
|
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:
|
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
|
return None
|
||||||
|
|
||||||
# Use the first user directory found
|
# Use the first valid user directory found
|
||||||
user_dir = user_dirs[0]
|
user_dir = user_dirs[0]
|
||||||
shortcuts_path = user_dir / "config" / "shortcuts.vdf"
|
shortcuts_path = user_dir / "config" / "shortcuts.vdf"
|
||||||
|
|
||||||
@@ -2499,8 +2540,8 @@ echo Prefix creation complete.
|
|||||||
# Try the standard Steam userdata path
|
# Try the standard Steam userdata path
|
||||||
steam_userdata_path = Path.home() / ".steam" / "steam" / "userdata"
|
steam_userdata_path = Path.home() / ".steam" / "steam" / "userdata"
|
||||||
if steam_userdata_path.exists():
|
if steam_userdata_path.exists():
|
||||||
# Find the first user directory (usually only one on Steam Deck)
|
# 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()]
|
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:
|
if user_dirs:
|
||||||
localconfig_path = user_dirs[0] / "config" / "localconfig.vdf"
|
localconfig_path = user_dirs[0] / "config" / "localconfig.vdf"
|
||||||
if localconfig_path.exists():
|
if localconfig_path.exists():
|
||||||
@@ -2601,8 +2642,11 @@ echo Prefix creation complete.
|
|||||||
env = os.environ.copy()
|
env = os.environ.copy()
|
||||||
env['STEAM_COMPAT_CLIENT_INSTALL_PATH'] = str(steam_root)
|
env['STEAM_COMPAT_CLIENT_INSTALL_PATH'] = str(steam_root)
|
||||||
env['STEAM_COMPAT_DATA_PATH'] = str(compatdata_dir / str(abs(appid)))
|
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['DISPLAY'] = ''
|
||||||
|
env['WAYLAND_DISPLAY'] = ''
|
||||||
|
env['WINEDEBUG'] = '-all'
|
||||||
|
env['WINEDLLOVERRIDES'] = 'msdia80.dll=n;conhost.exe=d;cmd.exe=d'
|
||||||
|
|
||||||
# Create the compatdata directory
|
# Create the compatdata directory
|
||||||
compat_dir = compatdata_dir / str(abs(appid))
|
compat_dir = compatdata_dir / str(abs(appid))
|
||||||
@@ -2616,7 +2660,9 @@ echo Prefix creation complete.
|
|||||||
cmd = [str(proton_path), 'run', 'wineboot', '-u']
|
cmd = [str(proton_path), 'run', 'wineboot', '-u']
|
||||||
logger.info(f"Running: {' '.join(cmd)}")
|
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}")
|
logger.info(f"Proton exit code: {result.returncode}")
|
||||||
|
|
||||||
if result.stdout:
|
if result.stdout:
|
||||||
@@ -2718,26 +2764,39 @@ echo Prefix creation complete.
|
|||||||
|
|
||||||
def verify_compatibility_tool_persists(self, appid: int) -> bool:
|
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:
|
Args:
|
||||||
appid: The AppID to check
|
appid: The AppID to check
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
True if compatibility tool is set, False otherwise
|
True if compatibility tool is correctly set, False otherwise
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
config_path = Path.home() / ".steam/steam/config/config.vdf"
|
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()
|
content = f.read()
|
||||||
|
|
||||||
|
# Check if AppID exists and has a Proton version set
|
||||||
if f'"{appid}"' in content:
|
if f'"{appid}"' in content:
|
||||||
logger.info(" Compatibility tool persists")
|
# Get the expected Proton version
|
||||||
return True
|
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:
|
else:
|
||||||
logger.warning("Compatibility tool not found")
|
logger.warning("Compatibility tool not found")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error verifying compatibility tool: {e}")
|
logger.error(f"Error verifying compatibility tool: {e}")
|
||||||
return False
|
return False
|
||||||
|
|||||||
@@ -40,13 +40,13 @@ class NativeSteamService:
|
|||||||
logger.error("Steam userdata directory not found")
|
logger.error("Steam userdata directory not found")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Find the first user directory (usually there's only one)
|
# 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()]
|
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:
|
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
|
return False
|
||||||
|
|
||||||
# Use the first user directory
|
# Use the first valid user directory
|
||||||
user_dir = user_dirs[0]
|
user_dir = user_dirs[0]
|
||||||
self.user_id = user_dir.name
|
self.user_id = user_dir.name
|
||||||
self.user_config_path = user_dir / "config"
|
self.user_config_path = user_dir / "config"
|
||||||
@@ -327,17 +327,27 @@ class NativeSteamService:
|
|||||||
logger.error(f"Error setting Proton version: {e}")
|
logger.error(f"Error setting Proton version: {e}")
|
||||||
return False
|
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,
|
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.
|
Complete workflow: Create shortcut and set Proton version.
|
||||||
|
|
||||||
This is the main method that replaces STL entirely.
|
This is the main method that replaces STL entirely.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
(success, app_id) - Success status and the AppID
|
(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}'")
|
logger.info(f"Creating shortcut with Proton: '{app_name}' -> '{proton_version}'")
|
||||||
|
|
||||||
# Step 1: Create the shortcut
|
# Step 1: Create the shortcut
|
||||||
|
|||||||
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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -7,7 +7,7 @@
|
|||||||
"targets": {
|
"targets": {
|
||||||
".NETCoreApp,Version=v8.0": {},
|
".NETCoreApp,Version=v8.0": {},
|
||||||
".NETCoreApp,Version=v8.0/linux-x64": {
|
".NETCoreApp,Version=v8.0/linux-x64": {
|
||||||
"jackify-engine/0.3.14": {
|
"jackify-engine/0.3.15": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Markdig": "0.40.0",
|
"Markdig": "0.40.0",
|
||||||
"Microsoft.Extensions.Configuration.Json": "9.0.1",
|
"Microsoft.Extensions.Configuration.Json": "9.0.1",
|
||||||
@@ -22,16 +22,16 @@
|
|||||||
"SixLabors.ImageSharp": "3.1.6",
|
"SixLabors.ImageSharp": "3.1.6",
|
||||||
"System.CommandLine": "2.0.0-beta4.22272.1",
|
"System.CommandLine": "2.0.0-beta4.22272.1",
|
||||||
"System.CommandLine.NamingConventionBinder": "2.0.0-beta4.22272.1",
|
"System.CommandLine.NamingConventionBinder": "2.0.0-beta4.22272.1",
|
||||||
"Wabbajack.CLI.Builder": "0.3.14",
|
"Wabbajack.CLI.Builder": "0.3.15",
|
||||||
"Wabbajack.Downloaders.Bethesda": "0.3.14",
|
"Wabbajack.Downloaders.Bethesda": "0.3.15",
|
||||||
"Wabbajack.Downloaders.Dispatcher": "0.3.14",
|
"Wabbajack.Downloaders.Dispatcher": "0.3.15",
|
||||||
"Wabbajack.Hashing.xxHash64": "0.3.14",
|
"Wabbajack.Hashing.xxHash64": "0.3.15",
|
||||||
"Wabbajack.Networking.Discord": "0.3.14",
|
"Wabbajack.Networking.Discord": "0.3.15",
|
||||||
"Wabbajack.Networking.GitHub": "0.3.14",
|
"Wabbajack.Networking.GitHub": "0.3.15",
|
||||||
"Wabbajack.Paths.IO": "0.3.14",
|
"Wabbajack.Paths.IO": "0.3.15",
|
||||||
"Wabbajack.Server.Lib": "0.3.14",
|
"Wabbajack.Server.Lib": "0.3.15",
|
||||||
"Wabbajack.Services.OSIntegrated": "0.3.14",
|
"Wabbajack.Services.OSIntegrated": "0.3.15",
|
||||||
"Wabbajack.VFS": "0.3.14",
|
"Wabbajack.VFS": "0.3.15",
|
||||||
"MegaApiClient": "1.0.0.0",
|
"MegaApiClient": "1.0.0.0",
|
||||||
"runtimepack.Microsoft.NETCore.App.Runtime.linux-x64": "8.0.19"
|
"runtimepack.Microsoft.NETCore.App.Runtime.linux-x64": "8.0.19"
|
||||||
},
|
},
|
||||||
@@ -1781,7 +1781,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.CLI.Builder/0.3.14": {
|
"Wabbajack.CLI.Builder/0.3.15": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.Configuration.Json": "9.0.1",
|
"Microsoft.Extensions.Configuration.Json": "9.0.1",
|
||||||
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
||||||
@@ -1791,109 +1791,109 @@
|
|||||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
||||||
"System.CommandLine": "2.0.0-beta4.22272.1",
|
"System.CommandLine": "2.0.0-beta4.22272.1",
|
||||||
"System.CommandLine.NamingConventionBinder": "2.0.0-beta4.22272.1",
|
"System.CommandLine.NamingConventionBinder": "2.0.0-beta4.22272.1",
|
||||||
"Wabbajack.Paths": "0.3.14"
|
"Wabbajack.Paths": "0.3.15"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.CLI.Builder.dll": {}
|
"Wabbajack.CLI.Builder.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.Common/0.3.14": {
|
"Wabbajack.Common/0.3.15": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
||||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
||||||
"System.Reactive": "6.0.1",
|
"System.Reactive": "6.0.1",
|
||||||
"Wabbajack.DTOs": "0.3.14",
|
"Wabbajack.DTOs": "0.3.15",
|
||||||
"Wabbajack.Networking.Http": "0.3.14",
|
"Wabbajack.Networking.Http": "0.3.15",
|
||||||
"Wabbajack.Paths.IO": "0.3.14"
|
"Wabbajack.Paths.IO": "0.3.15"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.Common.dll": {}
|
"Wabbajack.Common.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.Compiler/0.3.14": {
|
"Wabbajack.Compiler/0.3.15": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"F23.StringSimilarity": "6.0.0",
|
"F23.StringSimilarity": "6.0.0",
|
||||||
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
||||||
"Newtonsoft.Json": "13.0.3",
|
"Newtonsoft.Json": "13.0.3",
|
||||||
"SixLabors.ImageSharp": "3.1.6",
|
"SixLabors.ImageSharp": "3.1.6",
|
||||||
"Wabbajack.Downloaders.Dispatcher": "0.3.14",
|
"Wabbajack.Downloaders.Dispatcher": "0.3.15",
|
||||||
"Wabbajack.Installer": "0.3.14",
|
"Wabbajack.Installer": "0.3.15",
|
||||||
"Wabbajack.VFS": "0.3.14",
|
"Wabbajack.VFS": "0.3.15",
|
||||||
"ini-parser-netstandard": "2.5.2"
|
"ini-parser-netstandard": "2.5.2"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.Compiler.dll": {}
|
"Wabbajack.Compiler.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.Compression.BSA/0.3.14": {
|
"Wabbajack.Compression.BSA/0.3.15": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"K4os.Compression.LZ4.Streams": "1.3.8",
|
"K4os.Compression.LZ4.Streams": "1.3.8",
|
||||||
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
||||||
"SharpZipLib": "1.4.2",
|
"SharpZipLib": "1.4.2",
|
||||||
"Wabbajack.Common": "0.3.14",
|
"Wabbajack.Common": "0.3.15",
|
||||||
"Wabbajack.DTOs": "0.3.14"
|
"Wabbajack.DTOs": "0.3.15"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.Compression.BSA.dll": {}
|
"Wabbajack.Compression.BSA.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.Compression.Zip/0.3.14": {
|
"Wabbajack.Compression.Zip/0.3.15": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Wabbajack.IO.Async": "0.3.14"
|
"Wabbajack.IO.Async": "0.3.15"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.Compression.Zip.dll": {}
|
"Wabbajack.Compression.Zip.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.Configuration/0.3.14": {
|
"Wabbajack.Configuration/0.3.15": {
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.Configuration.dll": {}
|
"Wabbajack.Configuration.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.Downloaders.Bethesda/0.3.14": {
|
"Wabbajack.Downloaders.Bethesda/0.3.15": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"LibAES-CTR": "1.1.0",
|
"LibAES-CTR": "1.1.0",
|
||||||
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
||||||
"SharpZipLib": "1.4.2",
|
"SharpZipLib": "1.4.2",
|
||||||
"Wabbajack.Common": "0.3.14",
|
"Wabbajack.Common": "0.3.15",
|
||||||
"Wabbajack.Downloaders.Interfaces": "0.3.14",
|
"Wabbajack.Downloaders.Interfaces": "0.3.15",
|
||||||
"Wabbajack.Networking.BethesdaNet": "0.3.14"
|
"Wabbajack.Networking.BethesdaNet": "0.3.15"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.Downloaders.Bethesda.dll": {}
|
"Wabbajack.Downloaders.Bethesda.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.Downloaders.Dispatcher/0.3.14": {
|
"Wabbajack.Downloaders.Dispatcher/0.3.15": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
||||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
||||||
"Newtonsoft.Json": "13.0.3",
|
"Newtonsoft.Json": "13.0.3",
|
||||||
"SixLabors.ImageSharp": "3.1.6",
|
"SixLabors.ImageSharp": "3.1.6",
|
||||||
"Wabbajack.Downloaders.Bethesda": "0.3.14",
|
"Wabbajack.Downloaders.Bethesda": "0.3.15",
|
||||||
"Wabbajack.Downloaders.GameFile": "0.3.14",
|
"Wabbajack.Downloaders.GameFile": "0.3.15",
|
||||||
"Wabbajack.Downloaders.GoogleDrive": "0.3.14",
|
"Wabbajack.Downloaders.GoogleDrive": "0.3.15",
|
||||||
"Wabbajack.Downloaders.Http": "0.3.14",
|
"Wabbajack.Downloaders.Http": "0.3.15",
|
||||||
"Wabbajack.Downloaders.IPS4OAuth2Downloader": "0.3.14",
|
"Wabbajack.Downloaders.IPS4OAuth2Downloader": "0.3.15",
|
||||||
"Wabbajack.Downloaders.Interfaces": "0.3.14",
|
"Wabbajack.Downloaders.Interfaces": "0.3.15",
|
||||||
"Wabbajack.Downloaders.Manual": "0.3.14",
|
"Wabbajack.Downloaders.Manual": "0.3.15",
|
||||||
"Wabbajack.Downloaders.MediaFire": "0.3.14",
|
"Wabbajack.Downloaders.MediaFire": "0.3.15",
|
||||||
"Wabbajack.Downloaders.Mega": "0.3.14",
|
"Wabbajack.Downloaders.Mega": "0.3.15",
|
||||||
"Wabbajack.Downloaders.ModDB": "0.3.14",
|
"Wabbajack.Downloaders.ModDB": "0.3.15",
|
||||||
"Wabbajack.Downloaders.Nexus": "0.3.14",
|
"Wabbajack.Downloaders.Nexus": "0.3.15",
|
||||||
"Wabbajack.Downloaders.VerificationCache": "0.3.14",
|
"Wabbajack.Downloaders.VerificationCache": "0.3.15",
|
||||||
"Wabbajack.Downloaders.WabbajackCDN": "0.3.14",
|
"Wabbajack.Downloaders.WabbajackCDN": "0.3.15",
|
||||||
"Wabbajack.Networking.WabbajackClientApi": "0.3.14"
|
"Wabbajack.Networking.WabbajackClientApi": "0.3.15"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.Downloaders.Dispatcher.dll": {}
|
"Wabbajack.Downloaders.Dispatcher.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.Downloaders.GameFile/0.3.14": {
|
"Wabbajack.Downloaders.GameFile/0.3.15": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"GameFinder.StoreHandlers.EADesktop": "4.5.0",
|
"GameFinder.StoreHandlers.EADesktop": "4.5.0",
|
||||||
"GameFinder.StoreHandlers.EGS": "4.5.0",
|
"GameFinder.StoreHandlers.EGS": "4.5.0",
|
||||||
@@ -1903,360 +1903,360 @@
|
|||||||
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
||||||
"SixLabors.ImageSharp": "3.1.6",
|
"SixLabors.ImageSharp": "3.1.6",
|
||||||
"Wabbajack.Downloaders.Interfaces": "0.3.14",
|
"Wabbajack.Downloaders.Interfaces": "0.3.15",
|
||||||
"Wabbajack.VFS": "0.3.14"
|
"Wabbajack.VFS": "0.3.15"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.Downloaders.GameFile.dll": {}
|
"Wabbajack.Downloaders.GameFile.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.Downloaders.GoogleDrive/0.3.14": {
|
"Wabbajack.Downloaders.GoogleDrive/0.3.15": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"HtmlAgilityPack": "1.11.72",
|
"HtmlAgilityPack": "1.11.72",
|
||||||
"Microsoft.AspNetCore.Http.Extensions": "2.3.0",
|
"Microsoft.AspNetCore.Http.Extensions": "2.3.0",
|
||||||
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
||||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
||||||
"Wabbajack.Common": "0.3.14",
|
"Wabbajack.Common": "0.3.15",
|
||||||
"Wabbajack.DTOs": "0.3.14",
|
"Wabbajack.DTOs": "0.3.15",
|
||||||
"Wabbajack.Downloaders.Interfaces": "0.3.14",
|
"Wabbajack.Downloaders.Interfaces": "0.3.15",
|
||||||
"Wabbajack.Networking.Http": "0.3.14",
|
"Wabbajack.Networking.Http": "0.3.15",
|
||||||
"Wabbajack.Networking.Http.Interfaces": "0.3.14"
|
"Wabbajack.Networking.Http.Interfaces": "0.3.15"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.Downloaders.GoogleDrive.dll": {}
|
"Wabbajack.Downloaders.GoogleDrive.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.Downloaders.Http/0.3.14": {
|
"Wabbajack.Downloaders.Http/0.3.15": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
||||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
||||||
"Wabbajack.Common": "0.3.14",
|
"Wabbajack.Common": "0.3.15",
|
||||||
"Wabbajack.DTOs": "0.3.14",
|
"Wabbajack.DTOs": "0.3.15",
|
||||||
"Wabbajack.Downloaders.Interfaces": "0.3.14",
|
"Wabbajack.Downloaders.Interfaces": "0.3.15",
|
||||||
"Wabbajack.Networking.BethesdaNet": "0.3.14",
|
"Wabbajack.Networking.BethesdaNet": "0.3.15",
|
||||||
"Wabbajack.Networking.Http.Interfaces": "0.3.14",
|
"Wabbajack.Networking.Http.Interfaces": "0.3.15",
|
||||||
"Wabbajack.Paths.IO": "0.3.14"
|
"Wabbajack.Paths.IO": "0.3.15"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.Downloaders.Http.dll": {}
|
"Wabbajack.Downloaders.Http.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.Downloaders.Interfaces/0.3.14": {
|
"Wabbajack.Downloaders.Interfaces/0.3.15": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
||||||
"Wabbajack.Compression.Zip": "0.3.14",
|
"Wabbajack.Compression.Zip": "0.3.15",
|
||||||
"Wabbajack.DTOs": "0.3.14",
|
"Wabbajack.DTOs": "0.3.15",
|
||||||
"Wabbajack.Paths.IO": "0.3.14"
|
"Wabbajack.Paths.IO": "0.3.15"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.Downloaders.Interfaces.dll": {}
|
"Wabbajack.Downloaders.Interfaces.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.Downloaders.IPS4OAuth2Downloader/0.3.14": {
|
"Wabbajack.Downloaders.IPS4OAuth2Downloader/0.3.15": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"F23.StringSimilarity": "6.0.0",
|
"F23.StringSimilarity": "6.0.0",
|
||||||
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
||||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
||||||
"Wabbajack.Common": "0.3.14",
|
"Wabbajack.Common": "0.3.15",
|
||||||
"Wabbajack.Downloaders.Interfaces": "0.3.14",
|
"Wabbajack.Downloaders.Interfaces": "0.3.15",
|
||||||
"Wabbajack.Networking.Http": "0.3.14",
|
"Wabbajack.Networking.Http": "0.3.15",
|
||||||
"Wabbajack.Networking.Http.Interfaces": "0.3.14"
|
"Wabbajack.Networking.Http.Interfaces": "0.3.15"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.Downloaders.IPS4OAuth2Downloader.dll": {}
|
"Wabbajack.Downloaders.IPS4OAuth2Downloader.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.Downloaders.Manual/0.3.14": {
|
"Wabbajack.Downloaders.Manual/0.3.15": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
||||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
||||||
"Wabbajack.Common": "0.3.14",
|
"Wabbajack.Common": "0.3.15",
|
||||||
"Wabbajack.Downloaders.Interfaces": "0.3.14"
|
"Wabbajack.Downloaders.Interfaces": "0.3.15"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.Downloaders.Manual.dll": {}
|
"Wabbajack.Downloaders.Manual.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.Downloaders.MediaFire/0.3.14": {
|
"Wabbajack.Downloaders.MediaFire/0.3.15": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"HtmlAgilityPack": "1.11.72",
|
"HtmlAgilityPack": "1.11.72",
|
||||||
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
||||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
||||||
"Wabbajack.Common": "0.3.14",
|
"Wabbajack.Common": "0.3.15",
|
||||||
"Wabbajack.Downloaders.Interfaces": "0.3.14",
|
"Wabbajack.Downloaders.Interfaces": "0.3.15",
|
||||||
"Wabbajack.Networking.Http.Interfaces": "0.3.14"
|
"Wabbajack.Networking.Http.Interfaces": "0.3.15"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.Downloaders.MediaFire.dll": {}
|
"Wabbajack.Downloaders.MediaFire.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.Downloaders.Mega/0.3.14": {
|
"Wabbajack.Downloaders.Mega/0.3.15": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
||||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
||||||
"Newtonsoft.Json": "13.0.3",
|
"Newtonsoft.Json": "13.0.3",
|
||||||
"Wabbajack.Common": "0.3.14",
|
"Wabbajack.Common": "0.3.15",
|
||||||
"Wabbajack.Downloaders.Interfaces": "0.3.14",
|
"Wabbajack.Downloaders.Interfaces": "0.3.15",
|
||||||
"Wabbajack.Paths.IO": "0.3.14"
|
"Wabbajack.Paths.IO": "0.3.15"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.Downloaders.Mega.dll": {}
|
"Wabbajack.Downloaders.Mega.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.Downloaders.ModDB/0.3.14": {
|
"Wabbajack.Downloaders.ModDB/0.3.15": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"HtmlAgilityPack": "1.11.72",
|
"HtmlAgilityPack": "1.11.72",
|
||||||
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
||||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
||||||
"Newtonsoft.Json": "13.0.3",
|
"Newtonsoft.Json": "13.0.3",
|
||||||
"Wabbajack.Common": "0.3.14",
|
"Wabbajack.Common": "0.3.15",
|
||||||
"Wabbajack.Downloaders.Interfaces": "0.3.14",
|
"Wabbajack.Downloaders.Interfaces": "0.3.15",
|
||||||
"Wabbajack.Networking.Http": "0.3.14",
|
"Wabbajack.Networking.Http": "0.3.15",
|
||||||
"Wabbajack.Networking.Http.Interfaces": "0.3.14"
|
"Wabbajack.Networking.Http.Interfaces": "0.3.15"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.Downloaders.ModDB.dll": {}
|
"Wabbajack.Downloaders.ModDB.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.Downloaders.Nexus/0.3.14": {
|
"Wabbajack.Downloaders.Nexus/0.3.15": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
||||||
"Wabbajack.DTOs": "0.3.14",
|
"Wabbajack.DTOs": "0.3.15",
|
||||||
"Wabbajack.Downloaders.Interfaces": "0.3.14",
|
"Wabbajack.Downloaders.Interfaces": "0.3.15",
|
||||||
"Wabbajack.Hashing.xxHash64": "0.3.14",
|
"Wabbajack.Hashing.xxHash64": "0.3.15",
|
||||||
"Wabbajack.Networking.Http": "0.3.14",
|
"Wabbajack.Networking.Http": "0.3.15",
|
||||||
"Wabbajack.Networking.Http.Interfaces": "0.3.14",
|
"Wabbajack.Networking.Http.Interfaces": "0.3.15",
|
||||||
"Wabbajack.Networking.NexusApi": "0.3.14",
|
"Wabbajack.Networking.NexusApi": "0.3.15",
|
||||||
"Wabbajack.Paths": "0.3.14"
|
"Wabbajack.Paths": "0.3.15"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.Downloaders.Nexus.dll": {}
|
"Wabbajack.Downloaders.Nexus.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.Downloaders.VerificationCache/0.3.14": {
|
"Wabbajack.Downloaders.VerificationCache/0.3.15": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
||||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
||||||
"Stub.System.Data.SQLite.Core.NetStandard": "1.0.119",
|
"Stub.System.Data.SQLite.Core.NetStandard": "1.0.119",
|
||||||
"Wabbajack.DTOs": "0.3.14",
|
"Wabbajack.DTOs": "0.3.15",
|
||||||
"Wabbajack.Paths.IO": "0.3.14"
|
"Wabbajack.Paths.IO": "0.3.15"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.Downloaders.VerificationCache.dll": {}
|
"Wabbajack.Downloaders.VerificationCache.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.Downloaders.WabbajackCDN/0.3.14": {
|
"Wabbajack.Downloaders.WabbajackCDN/0.3.15": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
||||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
||||||
"Microsoft.Toolkit.HighPerformance": "7.1.2",
|
"Microsoft.Toolkit.HighPerformance": "7.1.2",
|
||||||
"Wabbajack.Common": "0.3.14",
|
"Wabbajack.Common": "0.3.15",
|
||||||
"Wabbajack.Downloaders.Interfaces": "0.3.14",
|
"Wabbajack.Downloaders.Interfaces": "0.3.15",
|
||||||
"Wabbajack.Networking.Http": "0.3.14",
|
"Wabbajack.Networking.Http": "0.3.15",
|
||||||
"Wabbajack.RateLimiter": "0.3.14"
|
"Wabbajack.RateLimiter": "0.3.15"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.Downloaders.WabbajackCDN.dll": {}
|
"Wabbajack.Downloaders.WabbajackCDN.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.DTOs/0.3.14": {
|
"Wabbajack.DTOs/0.3.15": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
||||||
"Wabbajack.Hashing.xxHash64": "0.3.14",
|
"Wabbajack.Hashing.xxHash64": "0.3.15",
|
||||||
"Wabbajack.Paths": "0.3.14"
|
"Wabbajack.Paths": "0.3.15"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.DTOs.dll": {}
|
"Wabbajack.DTOs.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.FileExtractor/0.3.14": {
|
"Wabbajack.FileExtractor/0.3.15": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
||||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
||||||
"OMODFramework": "3.0.1",
|
"OMODFramework": "3.0.1",
|
||||||
"Wabbajack.Common": "0.3.14",
|
"Wabbajack.Common": "0.3.15",
|
||||||
"Wabbajack.Compression.BSA": "0.3.14",
|
"Wabbajack.Compression.BSA": "0.3.15",
|
||||||
"Wabbajack.Hashing.PHash": "0.3.14",
|
"Wabbajack.Hashing.PHash": "0.3.15",
|
||||||
"Wabbajack.Paths": "0.3.14"
|
"Wabbajack.Paths": "0.3.15"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.FileExtractor.dll": {}
|
"Wabbajack.FileExtractor.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.Hashing.PHash/0.3.14": {
|
"Wabbajack.Hashing.PHash/0.3.15": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"BCnEncoder.Net.ImageSharp": "1.1.1",
|
"BCnEncoder.Net.ImageSharp": "1.1.1",
|
||||||
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
||||||
"Shipwreck.Phash": "0.5.0",
|
"Shipwreck.Phash": "0.5.0",
|
||||||
"SixLabors.ImageSharp": "3.1.6",
|
"SixLabors.ImageSharp": "3.1.6",
|
||||||
"Wabbajack.Common": "0.3.14",
|
"Wabbajack.Common": "0.3.15",
|
||||||
"Wabbajack.DTOs": "0.3.14",
|
"Wabbajack.DTOs": "0.3.15",
|
||||||
"Wabbajack.Paths": "0.3.14",
|
"Wabbajack.Paths": "0.3.15",
|
||||||
"Wabbajack.Paths.IO": "0.3.14"
|
"Wabbajack.Paths.IO": "0.3.15"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.Hashing.PHash.dll": {}
|
"Wabbajack.Hashing.PHash.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.Hashing.xxHash64/0.3.14": {
|
"Wabbajack.Hashing.xxHash64/0.3.15": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Wabbajack.Paths": "0.3.14",
|
"Wabbajack.Paths": "0.3.15",
|
||||||
"Wabbajack.RateLimiter": "0.3.14"
|
"Wabbajack.RateLimiter": "0.3.15"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.Hashing.xxHash64.dll": {}
|
"Wabbajack.Hashing.xxHash64.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.Installer/0.3.14": {
|
"Wabbajack.Installer/0.3.15": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
||||||
"Newtonsoft.Json": "13.0.3",
|
"Newtonsoft.Json": "13.0.3",
|
||||||
"Octopus.Octodiff": "2.0.548",
|
"Octopus.Octodiff": "2.0.548",
|
||||||
"SixLabors.ImageSharp": "3.1.6",
|
"SixLabors.ImageSharp": "3.1.6",
|
||||||
"Wabbajack.DTOs": "0.3.14",
|
"Wabbajack.DTOs": "0.3.15",
|
||||||
"Wabbajack.Downloaders.Dispatcher": "0.3.14",
|
"Wabbajack.Downloaders.Dispatcher": "0.3.15",
|
||||||
"Wabbajack.Downloaders.GameFile": "0.3.14",
|
"Wabbajack.Downloaders.GameFile": "0.3.15",
|
||||||
"Wabbajack.FileExtractor": "0.3.14",
|
"Wabbajack.FileExtractor": "0.3.15",
|
||||||
"Wabbajack.Networking.WabbajackClientApi": "0.3.14",
|
"Wabbajack.Networking.WabbajackClientApi": "0.3.15",
|
||||||
"Wabbajack.Paths": "0.3.14",
|
"Wabbajack.Paths": "0.3.15",
|
||||||
"Wabbajack.Paths.IO": "0.3.14",
|
"Wabbajack.Paths.IO": "0.3.15",
|
||||||
"Wabbajack.VFS": "0.3.14",
|
"Wabbajack.VFS": "0.3.15",
|
||||||
"ini-parser-netstandard": "2.5.2"
|
"ini-parser-netstandard": "2.5.2"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.Installer.dll": {}
|
"Wabbajack.Installer.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.IO.Async/0.3.14": {
|
"Wabbajack.IO.Async/0.3.15": {
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.IO.Async.dll": {}
|
"Wabbajack.IO.Async.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.Networking.BethesdaNet/0.3.14": {
|
"Wabbajack.Networking.BethesdaNet/0.3.15": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
||||||
"Wabbajack.DTOs": "0.3.14",
|
"Wabbajack.DTOs": "0.3.15",
|
||||||
"Wabbajack.Networking.Http": "0.3.14",
|
"Wabbajack.Networking.Http": "0.3.15",
|
||||||
"Wabbajack.Networking.Http.Interfaces": "0.3.14"
|
"Wabbajack.Networking.Http.Interfaces": "0.3.15"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.Networking.BethesdaNet.dll": {}
|
"Wabbajack.Networking.BethesdaNet.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.Networking.Discord/0.3.14": {
|
"Wabbajack.Networking.Discord/0.3.15": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
||||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
||||||
"Wabbajack.Networking.Http.Interfaces": "0.3.14"
|
"Wabbajack.Networking.Http.Interfaces": "0.3.15"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.Networking.Discord.dll": {}
|
"Wabbajack.Networking.Discord.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.Networking.GitHub/0.3.14": {
|
"Wabbajack.Networking.GitHub/0.3.15": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
||||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
||||||
"Octokit": "14.0.0",
|
"Octokit": "14.0.0",
|
||||||
"Wabbajack.DTOs": "0.3.14",
|
"Wabbajack.DTOs": "0.3.15",
|
||||||
"Wabbajack.Networking.Http.Interfaces": "0.3.14"
|
"Wabbajack.Networking.Http.Interfaces": "0.3.15"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.Networking.GitHub.dll": {}
|
"Wabbajack.Networking.GitHub.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.Networking.Http/0.3.14": {
|
"Wabbajack.Networking.Http/0.3.15": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
||||||
"Microsoft.Extensions.Http": "9.0.1",
|
"Microsoft.Extensions.Http": "9.0.1",
|
||||||
"Microsoft.Extensions.Logging": "9.0.1",
|
"Microsoft.Extensions.Logging": "9.0.1",
|
||||||
"Wabbajack.Configuration": "0.3.14",
|
"Wabbajack.Configuration": "0.3.15",
|
||||||
"Wabbajack.Downloaders.Interfaces": "0.3.14",
|
"Wabbajack.Downloaders.Interfaces": "0.3.15",
|
||||||
"Wabbajack.Hashing.xxHash64": "0.3.14",
|
"Wabbajack.Hashing.xxHash64": "0.3.15",
|
||||||
"Wabbajack.Networking.Http.Interfaces": "0.3.14",
|
"Wabbajack.Networking.Http.Interfaces": "0.3.15",
|
||||||
"Wabbajack.Paths": "0.3.14",
|
"Wabbajack.Paths": "0.3.15",
|
||||||
"Wabbajack.Paths.IO": "0.3.14"
|
"Wabbajack.Paths.IO": "0.3.15"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.Networking.Http.dll": {}
|
"Wabbajack.Networking.Http.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.Networking.Http.Interfaces/0.3.14": {
|
"Wabbajack.Networking.Http.Interfaces/0.3.15": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Wabbajack.Hashing.xxHash64": "0.3.14"
|
"Wabbajack.Hashing.xxHash64": "0.3.15"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.Networking.Http.Interfaces.dll": {}
|
"Wabbajack.Networking.Http.Interfaces.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.Networking.NexusApi/0.3.14": {
|
"Wabbajack.Networking.NexusApi/0.3.15": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
||||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
||||||
"Wabbajack.DTOs": "0.3.14",
|
"Wabbajack.DTOs": "0.3.15",
|
||||||
"Wabbajack.Networking.Http": "0.3.14",
|
"Wabbajack.Networking.Http": "0.3.15",
|
||||||
"Wabbajack.Networking.Http.Interfaces": "0.3.14",
|
"Wabbajack.Networking.Http.Interfaces": "0.3.15",
|
||||||
"Wabbajack.Networking.WabbajackClientApi": "0.3.14"
|
"Wabbajack.Networking.WabbajackClientApi": "0.3.15"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.Networking.NexusApi.dll": {}
|
"Wabbajack.Networking.NexusApi.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.Networking.WabbajackClientApi/0.3.14": {
|
"Wabbajack.Networking.WabbajackClientApi/0.3.15": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
||||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
||||||
"Octokit": "14.0.0",
|
"Octokit": "14.0.0",
|
||||||
"Wabbajack.Common": "0.3.14",
|
"Wabbajack.Common": "0.3.15",
|
||||||
"Wabbajack.DTOs": "0.3.14",
|
"Wabbajack.DTOs": "0.3.15",
|
||||||
"Wabbajack.Paths.IO": "0.3.14",
|
"Wabbajack.Paths.IO": "0.3.15",
|
||||||
"Wabbajack.VFS.Interfaces": "0.3.14",
|
"Wabbajack.VFS.Interfaces": "0.3.15",
|
||||||
"YamlDotNet": "16.3.0"
|
"YamlDotNet": "16.3.0"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.Networking.WabbajackClientApi.dll": {}
|
"Wabbajack.Networking.WabbajackClientApi.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.Paths/0.3.14": {
|
"Wabbajack.Paths/0.3.15": {
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.Paths.dll": {}
|
"Wabbajack.Paths.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.Paths.IO/0.3.14": {
|
"Wabbajack.Paths.IO/0.3.15": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Wabbajack.Paths": "0.3.14",
|
"Wabbajack.Paths": "0.3.15",
|
||||||
"shortid": "4.0.0"
|
"shortid": "4.0.0"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.Paths.IO.dll": {}
|
"Wabbajack.Paths.IO.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.RateLimiter/0.3.14": {
|
"Wabbajack.RateLimiter/0.3.15": {
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.RateLimiter.dll": {}
|
"Wabbajack.RateLimiter.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.Server.Lib/0.3.14": {
|
"Wabbajack.Server.Lib/0.3.15": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"FluentFTP": "52.0.0",
|
"FluentFTP": "52.0.0",
|
||||||
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
||||||
@@ -2264,58 +2264,58 @@
|
|||||||
"Nettle": "3.0.0",
|
"Nettle": "3.0.0",
|
||||||
"Newtonsoft.Json": "13.0.3",
|
"Newtonsoft.Json": "13.0.3",
|
||||||
"SixLabors.ImageSharp": "3.1.6",
|
"SixLabors.ImageSharp": "3.1.6",
|
||||||
"Wabbajack.Common": "0.3.14",
|
"Wabbajack.Common": "0.3.15",
|
||||||
"Wabbajack.Networking.Http.Interfaces": "0.3.14",
|
"Wabbajack.Networking.Http.Interfaces": "0.3.15",
|
||||||
"Wabbajack.Services.OSIntegrated": "0.3.14"
|
"Wabbajack.Services.OSIntegrated": "0.3.15"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.Server.Lib.dll": {}
|
"Wabbajack.Server.Lib.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.Services.OSIntegrated/0.3.14": {
|
"Wabbajack.Services.OSIntegrated/0.3.15": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"DeviceId": "6.8.0",
|
"DeviceId": "6.8.0",
|
||||||
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
||||||
"Newtonsoft.Json": "13.0.3",
|
"Newtonsoft.Json": "13.0.3",
|
||||||
"SixLabors.ImageSharp": "3.1.6",
|
"SixLabors.ImageSharp": "3.1.6",
|
||||||
"Wabbajack.Compiler": "0.3.14",
|
"Wabbajack.Compiler": "0.3.15",
|
||||||
"Wabbajack.Downloaders.Dispatcher": "0.3.14",
|
"Wabbajack.Downloaders.Dispatcher": "0.3.15",
|
||||||
"Wabbajack.Installer": "0.3.14",
|
"Wabbajack.Installer": "0.3.15",
|
||||||
"Wabbajack.Networking.BethesdaNet": "0.3.14",
|
"Wabbajack.Networking.BethesdaNet": "0.3.15",
|
||||||
"Wabbajack.Networking.Discord": "0.3.14",
|
"Wabbajack.Networking.Discord": "0.3.15",
|
||||||
"Wabbajack.VFS": "0.3.14"
|
"Wabbajack.VFS": "0.3.15"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.Services.OSIntegrated.dll": {}
|
"Wabbajack.Services.OSIntegrated.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.VFS/0.3.14": {
|
"Wabbajack.VFS/0.3.15": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
||||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
||||||
"SixLabors.ImageSharp": "3.1.6",
|
"SixLabors.ImageSharp": "3.1.6",
|
||||||
"System.Data.SQLite.Core": "1.0.119",
|
"System.Data.SQLite.Core": "1.0.119",
|
||||||
"Wabbajack.Common": "0.3.14",
|
"Wabbajack.Common": "0.3.15",
|
||||||
"Wabbajack.FileExtractor": "0.3.14",
|
"Wabbajack.FileExtractor": "0.3.15",
|
||||||
"Wabbajack.Hashing.PHash": "0.3.14",
|
"Wabbajack.Hashing.PHash": "0.3.15",
|
||||||
"Wabbajack.Hashing.xxHash64": "0.3.14",
|
"Wabbajack.Hashing.xxHash64": "0.3.15",
|
||||||
"Wabbajack.Paths": "0.3.14",
|
"Wabbajack.Paths": "0.3.15",
|
||||||
"Wabbajack.Paths.IO": "0.3.14",
|
"Wabbajack.Paths.IO": "0.3.15",
|
||||||
"Wabbajack.VFS.Interfaces": "0.3.14"
|
"Wabbajack.VFS.Interfaces": "0.3.15"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.VFS.dll": {}
|
"Wabbajack.VFS.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.VFS.Interfaces/0.3.14": {
|
"Wabbajack.VFS.Interfaces/0.3.15": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
||||||
"Wabbajack.DTOs": "0.3.14",
|
"Wabbajack.DTOs": "0.3.15",
|
||||||
"Wabbajack.Hashing.xxHash64": "0.3.14",
|
"Wabbajack.Hashing.xxHash64": "0.3.15",
|
||||||
"Wabbajack.Paths": "0.3.14"
|
"Wabbajack.Paths": "0.3.15"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.VFS.Interfaces.dll": {}
|
"Wabbajack.VFS.Interfaces.dll": {}
|
||||||
@@ -2332,7 +2332,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"libraries": {
|
"libraries": {
|
||||||
"jackify-engine/0.3.14": {
|
"jackify-engine/0.3.15": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
@@ -3021,202 +3021,202 @@
|
|||||||
"path": "yamldotnet/16.3.0",
|
"path": "yamldotnet/16.3.0",
|
||||||
"hashPath": "yamldotnet.16.3.0.nupkg.sha512"
|
"hashPath": "yamldotnet.16.3.0.nupkg.sha512"
|
||||||
},
|
},
|
||||||
"Wabbajack.CLI.Builder/0.3.14": {
|
"Wabbajack.CLI.Builder/0.3.15": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.Common/0.3.14": {
|
"Wabbajack.Common/0.3.15": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.Compiler/0.3.14": {
|
"Wabbajack.Compiler/0.3.15": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.Compression.BSA/0.3.14": {
|
"Wabbajack.Compression.BSA/0.3.15": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.Compression.Zip/0.3.14": {
|
"Wabbajack.Compression.Zip/0.3.15": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.Configuration/0.3.14": {
|
"Wabbajack.Configuration/0.3.15": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.Downloaders.Bethesda/0.3.14": {
|
"Wabbajack.Downloaders.Bethesda/0.3.15": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.Downloaders.Dispatcher/0.3.14": {
|
"Wabbajack.Downloaders.Dispatcher/0.3.15": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.Downloaders.GameFile/0.3.14": {
|
"Wabbajack.Downloaders.GameFile/0.3.15": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.Downloaders.GoogleDrive/0.3.14": {
|
"Wabbajack.Downloaders.GoogleDrive/0.3.15": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.Downloaders.Http/0.3.14": {
|
"Wabbajack.Downloaders.Http/0.3.15": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.Downloaders.Interfaces/0.3.14": {
|
"Wabbajack.Downloaders.Interfaces/0.3.15": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.Downloaders.IPS4OAuth2Downloader/0.3.14": {
|
"Wabbajack.Downloaders.IPS4OAuth2Downloader/0.3.15": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.Downloaders.Manual/0.3.14": {
|
"Wabbajack.Downloaders.Manual/0.3.15": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.Downloaders.MediaFire/0.3.14": {
|
"Wabbajack.Downloaders.MediaFire/0.3.15": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.Downloaders.Mega/0.3.14": {
|
"Wabbajack.Downloaders.Mega/0.3.15": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.Downloaders.ModDB/0.3.14": {
|
"Wabbajack.Downloaders.ModDB/0.3.15": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.Downloaders.Nexus/0.3.14": {
|
"Wabbajack.Downloaders.Nexus/0.3.15": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.Downloaders.VerificationCache/0.3.14": {
|
"Wabbajack.Downloaders.VerificationCache/0.3.15": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.Downloaders.WabbajackCDN/0.3.14": {
|
"Wabbajack.Downloaders.WabbajackCDN/0.3.15": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.DTOs/0.3.14": {
|
"Wabbajack.DTOs/0.3.15": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.FileExtractor/0.3.14": {
|
"Wabbajack.FileExtractor/0.3.15": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.Hashing.PHash/0.3.14": {
|
"Wabbajack.Hashing.PHash/0.3.15": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.Hashing.xxHash64/0.3.14": {
|
"Wabbajack.Hashing.xxHash64/0.3.15": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.Installer/0.3.14": {
|
"Wabbajack.Installer/0.3.15": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.IO.Async/0.3.14": {
|
"Wabbajack.IO.Async/0.3.15": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.Networking.BethesdaNet/0.3.14": {
|
"Wabbajack.Networking.BethesdaNet/0.3.15": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.Networking.Discord/0.3.14": {
|
"Wabbajack.Networking.Discord/0.3.15": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.Networking.GitHub/0.3.14": {
|
"Wabbajack.Networking.GitHub/0.3.15": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.Networking.Http/0.3.14": {
|
"Wabbajack.Networking.Http/0.3.15": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.Networking.Http.Interfaces/0.3.14": {
|
"Wabbajack.Networking.Http.Interfaces/0.3.15": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.Networking.NexusApi/0.3.14": {
|
"Wabbajack.Networking.NexusApi/0.3.15": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.Networking.WabbajackClientApi/0.3.14": {
|
"Wabbajack.Networking.WabbajackClientApi/0.3.15": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.Paths/0.3.14": {
|
"Wabbajack.Paths/0.3.15": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.Paths.IO/0.3.14": {
|
"Wabbajack.Paths.IO/0.3.15": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.RateLimiter/0.3.14": {
|
"Wabbajack.RateLimiter/0.3.15": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.Server.Lib/0.3.14": {
|
"Wabbajack.Server.Lib/0.3.15": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.Services.OSIntegrated/0.3.14": {
|
"Wabbajack.Services.OSIntegrated/0.3.15": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.VFS/0.3.14": {
|
"Wabbajack.VFS/0.3.15": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.VFS.Interfaces/0.3.14": {
|
"Wabbajack.VFS.Interfaces/0.3.15": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
|
|||||||
Binary file not shown.
@@ -7,6 +7,7 @@ This replaces the legacy jackify_gui implementation with a refactored architectu
|
|||||||
|
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
# Suppress xkbcommon locale errors (harmless but annoying)
|
# Suppress xkbcommon locale errors (harmless but annoying)
|
||||||
@@ -81,6 +82,9 @@ if '--env-diagnostic' in sys.argv:
|
|||||||
|
|
||||||
from jackify import __version__ as jackify_version
|
from jackify import __version__ as jackify_version
|
||||||
|
|
||||||
|
# Initialize logger
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
if '--help' in sys.argv or '-h' in sys.argv:
|
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""")
|
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)
|
sys.exit(0)
|
||||||
@@ -98,7 +102,7 @@ sys.path.insert(0, str(src_dir))
|
|||||||
|
|
||||||
from PySide6.QtWidgets import (
|
from PySide6.QtWidgets import (
|
||||||
QApplication, QMainWindow, QWidget, QLabel, QVBoxLayout, QPushButton,
|
QApplication, QMainWindow, QWidget, QLabel, QVBoxLayout, QPushButton,
|
||||||
QStackedWidget, QHBoxLayout, QDialog, QFormLayout, QLineEdit, QCheckBox, QSpinBox, QMessageBox, QGroupBox, QGridLayout, QFileDialog, QToolButton, QStyle
|
QStackedWidget, QHBoxLayout, QDialog, QFormLayout, QLineEdit, QCheckBox, QSpinBox, QMessageBox, QGroupBox, QGridLayout, QFileDialog, QToolButton, QStyle, QComboBox
|
||||||
)
|
)
|
||||||
from PySide6.QtCore import Qt, QEvent
|
from PySide6.QtCore import Qt, QEvent
|
||||||
from PySide6.QtGui import QIcon
|
from PySide6.QtGui import QIcon
|
||||||
@@ -298,6 +302,33 @@ class SettingsDialog(QDialog):
|
|||||||
main_layout.addWidget(api_group)
|
main_layout.addWidget(api_group)
|
||||||
main_layout.addSpacing(12)
|
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 ---
|
# --- Directories & Paths Section ---
|
||||||
dir_group = QGroupBox("Directories & Paths")
|
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_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()
|
api_key = text.strip()
|
||||||
self.config_handler.save_api_key(api_key)
|
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):
|
def _save(self):
|
||||||
# Validate values
|
# Validate values
|
||||||
for k, (multithreading_checkbox, max_tasks_spin) in self.resource_edits.items():
|
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)
|
# Save jackify data directory (always store actual path, never None)
|
||||||
jackify_data_dir = self.jackify_data_dir_edit.text().strip()
|
jackify_data_dir = self.jackify_data_dir_edit.text().strip()
|
||||||
self.config_handler.set("jackify_data_dir", jackify_data_dir)
|
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()
|
self.config_handler.save_config()
|
||||||
|
|
||||||
# Refresh cached paths in GUI screens if Jackify directory changed
|
# Refresh cached paths in GUI screens if Jackify directory changed
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ from jackify.backend.handlers.config_handler import ConfigHandler
|
|||||||
from ..dialogs import SuccessDialog
|
from ..dialogs import SuccessDialog
|
||||||
from PySide6.QtWidgets import QApplication
|
from PySide6.QtWidgets import QApplication
|
||||||
from jackify.frontends.gui.services.message_service import MessageService
|
from jackify.frontends.gui.services.message_service import MessageService
|
||||||
|
from jackify.shared.resolution_utils import get_resolution_fallback
|
||||||
|
|
||||||
def debug_print(message):
|
def debug_print(message):
|
||||||
"""Print debug message only if debug mode is enabled"""
|
"""Print debug message only if debug mode is enabled"""
|
||||||
@@ -1033,7 +1034,7 @@ class ConfigureNewModlistScreen(QWidget):
|
|||||||
try:
|
try:
|
||||||
# Get resolution from UI
|
# Get resolution from UI
|
||||||
resolution = self.resolution_combo.currentText()
|
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)
|
# Update the context with the new AppID (same format as manual steps)
|
||||||
mo2_exe_path = self.install_dir_edit.text().strip()
|
mo2_exe_path = self.install_dir_edit.text().strip()
|
||||||
@@ -1082,7 +1083,7 @@ class ConfigureNewModlistScreen(QWidget):
|
|||||||
nexus_api_key='', # Not needed for configuration
|
nexus_api_key='', # Not needed for configuration
|
||||||
modlist_value=self.context.get('modlist_value'),
|
modlist_value=self.context.get('modlist_value'),
|
||||||
modlist_source=self.context.get('modlist_source', 'identifier'),
|
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
|
skip_confirmation=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -367,6 +367,10 @@ class InstallModlistScreen(QWidget):
|
|||||||
self.resolution_service = ResolutionService()
|
self.resolution_service = ResolutionService()
|
||||||
self.config_handler = ConfigHandler()
|
self.config_handler = ConfigHandler()
|
||||||
self.protontricks_service = ProtontricksDetectionService()
|
self.protontricks_service = ProtontricksDetectionService()
|
||||||
|
|
||||||
|
# Somnium guidance tracking
|
||||||
|
self._show_somnium_guidance = False
|
||||||
|
self._somnium_install_dir = None
|
||||||
|
|
||||||
# Scroll tracking for professional auto-scroll behavior
|
# Scroll tracking for professional auto-scroll behavior
|
||||||
self._user_manually_scrolled = False
|
self._user_manually_scrolled = False
|
||||||
@@ -1356,7 +1360,8 @@ class InstallModlistScreen(QWidget):
|
|||||||
'oblivion': 'oblivion',
|
'oblivion': 'oblivion',
|
||||||
'starfield': 'starfield',
|
'starfield': 'starfield',
|
||||||
'oblivion_remastered': 'oblivion_remastered',
|
'oblivion_remastered': 'oblivion_remastered',
|
||||||
'enderal': 'enderal'
|
'enderal': 'enderal',
|
||||||
|
'enderal special edition': 'enderal'
|
||||||
}
|
}
|
||||||
game_type = game_mapping.get(game_name.lower())
|
game_type = game_mapping.get(game_name.lower())
|
||||||
debug_print(f"DEBUG: Mapped game_name '{game_name}' to game_type: '{game_type}'")
|
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
|
# Check if game is supported
|
||||||
debug_print(f"DEBUG: Checking if game_type '{game_type}' 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
|
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}")
|
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")
|
final_exe_path = os.path.join(install_dir, "ModOrganizer.exe")
|
||||||
|
|
||||||
if not os.path.exists(final_exe_path):
|
if not os.path.exists(final_exe_path):
|
||||||
self._safe_append_text(f"ERROR: ModOrganizer.exe not found at {final_exe_path}")
|
# Check if this is Somnium specifically (uses files/ subdirectory)
|
||||||
MessageService.critical(self, "ModOrganizer.exe Not Found",
|
modlist_name_lower = modlist_name.lower()
|
||||||
f"ModOrganizer.exe not found at:\n{final_exe_path}\n\nCannot proceed with automated setup.")
|
if "somnium" in modlist_name_lower:
|
||||||
return
|
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
|
# Run automated prefix creation in separate thread
|
||||||
from PySide6.QtCore import QThread, Signal
|
from PySide6.QtCore import QThread, Signal
|
||||||
@@ -1940,6 +1962,10 @@ class InstallModlistScreen(QWidget):
|
|||||||
self._enable_controls_after_operation()
|
self._enable_controls_after_operation()
|
||||||
|
|
||||||
if success:
|
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
|
# Show celebration SuccessDialog after the entire workflow
|
||||||
from ..dialogs import SuccessDialog
|
from ..dialogs import SuccessDialog
|
||||||
import time
|
import time
|
||||||
@@ -2041,11 +2067,20 @@ class InstallModlistScreen(QWidget):
|
|||||||
self.cancel_btn.setVisible(True)
|
self.cancel_btn.setVisible(True)
|
||||||
self.cancel_install_btn.setVisible(False)
|
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):
|
def validate_manual_steps_completion(self):
|
||||||
"""Validate that manual steps were actually completed and handle retry logic"""
|
"""Validate that manual steps were actually completed and handle retry logic"""
|
||||||
modlist_name = self.modlist_name_edit.text().strip()
|
modlist_name = self.modlist_name_edit.text().strip()
|
||||||
install_dir = self.install_dir_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
|
# Add delay to allow Steam filesystem updates to complete
|
||||||
self._safe_append_text("Waiting for 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 = {
|
updated_context = {
|
||||||
'name': modlist_name,
|
'name': modlist_name,
|
||||||
'path': install_dir,
|
'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_value': None,
|
||||||
'modlist_source': None,
|
'modlist_source': None,
|
||||||
'resolution': getattr(self, '_current_resolution', '2560x1600'),
|
'resolution': getattr(self, '_current_resolution', '2560x1600'),
|
||||||
@@ -2381,7 +2416,7 @@ class InstallModlistScreen(QWidget):
|
|||||||
updated_context = {
|
updated_context = {
|
||||||
'name': modlist_name,
|
'name': modlist_name,
|
||||||
'path': install_dir,
|
'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_value': None,
|
||||||
'modlist_source': None,
|
'modlist_source': None,
|
||||||
'resolution': getattr(self, '_current_resolution', '2560x1600'),
|
'resolution': getattr(self, '_current_resolution', '2560x1600'),
|
||||||
@@ -2616,6 +2651,26 @@ class InstallModlistScreen(QWidget):
|
|||||||
|
|
||||||
self._safe_append_text("Installation cancelled by user.")
|
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"""<b>Somnium Post-Installation Required</b><br><br>
|
||||||
|
Due to Somnium's non-standard folder structure, you need to manually update the binary paths in ModOrganizer:<br><br>
|
||||||
|
<b>1.</b> Launch the Steam shortcut created for Somnium<br>
|
||||||
|
<b>2.</b> In ModOrganizer, go to Settings → Executables<br>
|
||||||
|
<b>3.</b> For each executable entry (SKSE64, etc.), update the binary path to point to:<br>
|
||||||
|
<code>{self._somnium_install_dir}/files/root/Enderal Special Edition/skse64_loader.exe</code><br><br>
|
||||||
|
<b>Note:</b> Full Somnium support will be added in a future Jackify update.<br><br>
|
||||||
|
<i>You can also refer to the Somnium installation guide at:<br>
|
||||||
|
https://wiki.scenicroute.games/Somnium/1_Installation.html</i>"""
|
||||||
|
|
||||||
|
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):
|
def cancel_and_cleanup(self):
|
||||||
"""Handle Cancel button - clean up processes and go back"""
|
"""Handle Cancel button - clean up processes and go back"""
|
||||||
self.cleanup_processes()
|
self.cleanup_processes()
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ class UnsupportedGameDialog(QDialog):
|
|||||||
<li><strong>Oblivion</strong></li>
|
<li><strong>Oblivion</strong></li>
|
||||||
<li><strong>Starfield</strong></li>
|
<li><strong>Starfield</strong></li>
|
||||||
<li><strong>Oblivion Remastered</strong></li>
|
<li><strong>Oblivion Remastered</strong></li>
|
||||||
|
<li><strong>Enderal</strong></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<p>For unsupported games, you will need to manually configure Steam shortcuts and other post-install steps.</p>
|
<p>For unsupported games, you will need to manually configure Steam shortcuts and other post-install steps.</p>
|
||||||
@@ -113,6 +114,7 @@ class UnsupportedGameDialog(QDialog):
|
|||||||
<li><strong>Oblivion</strong></li>
|
<li><strong>Oblivion</strong></li>
|
||||||
<li><strong>Starfield</strong></li>
|
<li><strong>Starfield</strong></li>
|
||||||
<li><strong>Oblivion Remastered</strong></li>
|
<li><strong>Oblivion Remastered</strong></li>
|
||||||
|
<li><strong>Enderal</strong></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<p>For unsupported games, you will need to manually configure Steam shortcuts and other post-install steps.</p>
|
<p>For unsupported games, you will need to manually configure Steam shortcuts and other post-install steps.</p>
|
||||||
|
|||||||
113
jackify/shared/resolution_utils.py
Normal file
113
jackify/shared/resolution_utils.py
Normal file
@@ -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
|
||||||
Reference in New Issue
Block a user