Sync from development - prepare for v0.2.0.10

This commit is contained in:
Omni
2026-01-04 22:43:32 +00:00
parent 0d84d2f2fe
commit 9b5310c2f9
59 changed files with 613 additions and 349 deletions

View File

@@ -1,5 +1,26 @@
# Jackify Changelog # Jackify Changelog
## v0.2.0.10 - Registry & Hashing Fixes
**Release Date:** 2025-01-04
### Engine Updates
- **jackify-engine 0.4.5**: Fixed archive extraction with backslashes (including pattern matching), data directory path configuration, and removed post-download .wabbajack hash validation. Engine now auto-refreshes OAuth tokens during long installations via `NEXUS_OAUTH_INFO` environment variable.
### Critical Bug Fixes
- **InstallationThread Crash**: Fixed crash during installation with error "'InstallationThread' object has no attribute 'auth_service'". Premium detection diagnostics code assumed auth_service existed but it was never passed to the thread. Affects all users when Premium detection (including false positives) is triggered.
- **Install Start Hang**: Fixed missing `oauth_info` parameter that prevented modlist installs from starting (hung at "Starting modlist installation...")
- **OAuth Token Auto-Refresh**: Fixed OAuth tokens expiring during long modlist installations. Jackify now refreshes tokens with 15-minute buffer before passing to engine. Engine receives full OAuth state via `NEXUS_OAUTH_INFO` environment variable, enabling automatic token refresh during multi-hour downloads. Fixes "Token has expired" errors that occurred 60 minutes into installations.
- **ShowDotFiles Registry Format**: Fixed Wine registry format bug causing hidden files to remain hidden in prefixes. Python string escaping issue wrote single backslash instead of double backslash in `[Software\\Wine]` section header. Added auto-detection and fix for broken format from curated registry files.
- **Dotnet4 Registry Fixes**: Confirmed universal dotnet4.x registry fixes (`*mscoree=native` and `OnlyUseLatestCLR=1`) are applied in all three workflows (Install, Configure New, Configure Existing) across both CLI and GUI interfaces
- **Proton Path Configuration**: Fixed `proton_path` writing invalid "auto" string to config.json - now uses `null` instead, preventing jackify-engine from receiving invalid paths
### Improvements
- **Wine Binary Detection**: Enhanced detection with recursive fallback search within Proton directory when expected paths don't exist (handles different Proton version structures)
- Added Jackify version logging at workflow start
- Fixed GUI log file rotation to only run in debug mode
---
## v0.2.0.9 - Critical Configuration Fixes ## v0.2.0.9 - Critical Configuration Fixes
**Release Date:** 2025-12-31 **Release Date:** 2025-12-31

View File

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

View File

@@ -34,7 +34,7 @@ def _get_user_proton_version():
# get_proton_path() returns the Install Proton path # get_proton_path() returns the Install Proton path
user_proton_path = config_handler.get_proton_path() user_proton_path = config_handler.get_proton_path()
if user_proton_path == 'auto': if not user_proton_path or user_proton_path == 'auto':
# Use enhanced fallback logic with GE-Proton preference # Use enhanced fallback logic with GE-Proton preference
logging.info("User selected auto-detect, using GE-Proton → Experimental → Proton precedence") logging.info("User selected auto-detect, using GE-Proton → Experimental → Proton precedence")
return WineUtils.select_best_proton() return WineUtils.select_best_proton()
@@ -502,10 +502,11 @@ class ModlistInstallCLI:
print("\n" + "-" * 28) print("\n" + "-" * 28)
print(f"{COLOR_INFO}Nexus Authentication: Using API Key (Legacy){COLOR_RESET}") print(f"{COLOR_INFO}Nexus Authentication: Using API Key (Legacy){COLOR_RESET}")
# Get valid token/key # Get valid token/key and OAuth state for engine auto-refresh
api_key = auth_service.ensure_valid_auth() api_key, oauth_info = auth_service.get_auth_for_engine()
if api_key: if api_key:
self.context['nexus_api_key'] = api_key self.context['nexus_api_key'] = api_key
self.context['nexus_oauth_info'] = oauth_info # For engine auto-refresh
else: else:
# Auth expired or invalid - prompt to set up # Auth expired or invalid - prompt to set up
print(f"\n{COLOR_WARNING}Your authentication has expired or is invalid.{COLOR_RESET}") print(f"\n{COLOR_WARNING}Your authentication has expired or is invalid.{COLOR_RESET}")
@@ -538,9 +539,10 @@ class ModlistInstallCLI:
if username: if username:
print(f"{COLOR_INFO}Authorized as: {username}{COLOR_RESET}") print(f"{COLOR_INFO}Authorized as: {username}{COLOR_RESET}")
api_key = auth_service.ensure_valid_auth() api_key, oauth_info = auth_service.get_auth_for_engine()
if api_key: if api_key:
self.context['nexus_api_key'] = api_key self.context['nexus_api_key'] = api_key
self.context['nexus_oauth_info'] = oauth_info # For engine auto-refresh
else: else:
print(f"{COLOR_ERROR}Failed to retrieve auth token after authorization.{COLOR_RESET}") print(f"{COLOR_ERROR}Failed to retrieve auth token after authorization.{COLOR_RESET}")
return None return None
@@ -738,6 +740,7 @@ class ModlistInstallCLI:
modlist_arg = self.context.get('modlist_value') or self.context.get('machineid') modlist_arg = self.context.get('modlist_value') or self.context.get('machineid')
machineid = self.context.get('machineid') machineid = self.context.get('machineid')
api_key = self.context.get('nexus_api_key') api_key = self.context.get('nexus_api_key')
oauth_info = self.context.get('nexus_oauth_info')
# Path to the engine binary # Path to the engine binary
engine_path = get_jackify_engine_path() engine_path = get_jackify_engine_path()
@@ -779,24 +782,37 @@ class ModlistInstallCLI:
# Store original environment values to restore later # Store original environment values to restore later
original_env_values = { original_env_values = {
'NEXUS_API_KEY': os.environ.get('NEXUS_API_KEY'), 'NEXUS_API_KEY': os.environ.get('NEXUS_API_KEY'),
'NEXUS_OAUTH_INFO': os.environ.get('NEXUS_OAUTH_INFO'),
'DOTNET_SYSTEM_GLOBALIZATION_INVARIANT': os.environ.get('DOTNET_SYSTEM_GLOBALIZATION_INVARIANT') 'DOTNET_SYSTEM_GLOBALIZATION_INVARIANT': os.environ.get('DOTNET_SYSTEM_GLOBALIZATION_INVARIANT')
} }
try: try:
# Temporarily modify current process's environment # Temporarily modify current process's environment
# Prefer NEXUS_OAUTH_INFO (supports auto-refresh) over NEXUS_API_KEY (legacy)
if oauth_info:
os.environ['NEXUS_OAUTH_INFO'] = oauth_info
self.logger.debug(f"Set NEXUS_OAUTH_INFO for engine (supports auto-refresh)")
# Also set NEXUS_API_KEY for backward compatibility
if api_key: if api_key:
os.environ['NEXUS_API_KEY'] = api_key os.environ['NEXUS_API_KEY'] = api_key
self.logger.debug(f"Temporarily set os.environ['NEXUS_API_KEY'] for engine call using session-provided key.") elif api_key:
elif 'NEXUS_API_KEY' in os.environ: # api_key is None/empty, but a system key might exist # No OAuth info, use API key only (no auto-refresh support)
self.logger.debug(f"Session API key not provided. Temporarily removing inherited NEXUS_API_KEY ('{'[REDACTED]' if os.environ.get('NEXUS_API_KEY') else 'None'}') from os.environ for engine call to ensure it is not used.") os.environ['NEXUS_API_KEY'] = api_key
self.logger.debug(f"Set NEXUS_API_KEY for engine (no auto-refresh)")
else:
# No auth available, clear any inherited values
if 'NEXUS_API_KEY' in os.environ:
del os.environ['NEXUS_API_KEY'] del os.environ['NEXUS_API_KEY']
# If api_key is None and NEXUS_API_KEY was not in os.environ, it remains unset, which is correct. if 'NEXUS_OAUTH_INFO' in os.environ:
del os.environ['NEXUS_OAUTH_INFO']
self.logger.debug(f"No Nexus auth available, cleared inherited env vars")
os.environ['DOTNET_SYSTEM_GLOBALIZATION_INVARIANT'] = "1" os.environ['DOTNET_SYSTEM_GLOBALIZATION_INVARIANT'] = "1"
self.logger.debug(f"Temporarily set os.environ['DOTNET_SYSTEM_GLOBALIZATION_INVARIANT'] = '1' for engine call.") self.logger.debug(f"Temporarily set os.environ['DOTNET_SYSTEM_GLOBALIZATION_INVARIANT'] = '1' for engine call.")
self.logger.info("Environment prepared for jackify-engine install process by modifying os.environ.") self.logger.info("Environment prepared for jackify-engine install process by modifying os.environ.")
self.logger.debug(f"NEXUS_API_KEY in os.environ (pre-call): {'[SET]' if os.environ.get('NEXUS_API_KEY') else '[NOT SET]'}") self.logger.debug(f"NEXUS_API_KEY in os.environ (pre-call): {'[SET]' if os.environ.get('NEXUS_API_KEY') else '[NOT SET]'}")
self.logger.debug(f"NEXUS_OAUTH_INFO in os.environ (pre-call): {'[SET]' if os.environ.get('NEXUS_OAUTH_INFO') else '[NOT SET]'}")
pretty_cmd = ' '.join([f'"{arg}"' if ' ' in arg else arg for arg in cmd]) pretty_cmd = ' '.join([f'"{arg}"' if ' ' in arg else arg for arg in cmd])
print(f"{COLOR_INFO}Launching Jackify Install Engine with command:{COLOR_RESET} {pretty_cmd}") print(f"{COLOR_INFO}Launching Jackify Install Engine with command:{COLOR_RESET} {pretty_cmd}")

View File

@@ -58,6 +58,8 @@ class ConfigHandler:
"use_winetricks_for_components": True, # DEPRECATED: Migrated to component_installation_method. Kept for backward compatibility. "use_winetricks_for_components": True, # DEPRECATED: Migrated to component_installation_method. Kept for backward compatibility.
"component_installation_method": "winetricks", # "winetricks" (default) or "system_protontricks" "component_installation_method": "winetricks", # "winetricks" (default) or "system_protontricks"
"game_proton_path": None, # Proton version for game shortcuts (can be any Proton 9+), separate from install proton "game_proton_path": None, # Proton version for game shortcuts (can be any Proton 9+), separate from install proton
"proton_path": None, # Install Proton path (for jackify-engine) - None means auto-detect
"proton_version": None, # Install Proton version name - None means auto-detect
"steam_restart_strategy": "jackify", # "jackify" (default) or "nak_simple" "steam_restart_strategy": "jackify", # "jackify" (default) or "nak_simple"
"window_width": None, # Saved window width (None = use dynamic sizing) "window_width": None, # Saved window width (None = use dynamic sizing)
"window_height": None # Saved window height (None = use dynamic sizing) "window_height": None # Saved window height (None = use dynamic sizing)
@@ -757,16 +759,20 @@ class ConfigHandler:
Always reads fresh from disk. Always reads fresh from disk.
Returns: Returns:
str: Saved Install Proton path or 'auto' if not saved str: Saved Install Proton path, or None if not set (indicates auto-detect mode)
""" """
try: try:
config = self._read_config_from_disk() config = self._read_config_from_disk()
proton_path = config.get("proton_path", "auto") proton_path = config.get("proton_path")
# Return None if missing/None/empty string - don't default to "auto"
if not proton_path:
logger.debug("proton_path not set in config - will use auto-detection")
return None
logger.debug(f"Retrieved fresh install proton_path from config: {proton_path}") logger.debug(f"Retrieved fresh install proton_path from config: {proton_path}")
return proton_path return proton_path
except Exception as e: except Exception as e:
logger.error(f"Error retrieving install proton_path: {e}") logger.error(f"Error retrieving install proton_path: {e}")
return "auto" return None
def get_game_proton_path(self): def get_game_proton_path(self):
""" """
@@ -775,7 +781,7 @@ class ConfigHandler:
Always reads fresh from disk. Always reads fresh from disk.
Returns: Returns:
str: Saved Game Proton path, Install Proton path, or 'auto' if not saved str: Saved Game Proton path, Install Proton path, or None if not saved (indicates auto-detect mode)
""" """
try: try:
config = self._read_config_from_disk() config = self._read_config_from_disk()
@@ -783,8 +789,12 @@ class ConfigHandler:
# If game proton not set or set to same_as_install, use install proton # If game proton not set or set to same_as_install, use install proton
if not game_proton_path or game_proton_path == "same_as_install": if not game_proton_path or game_proton_path == "same_as_install":
game_proton_path = config.get("proton_path", "auto") game_proton_path = config.get("proton_path") # Returns None if not set
# Return None if missing/None/empty string
if not game_proton_path:
logger.debug("game_proton_path not set in config - will use auto-detection")
return None
logger.debug(f"Retrieved fresh game proton_path from config: {game_proton_path}") logger.debug(f"Retrieved fresh game proton_path from config: {game_proton_path}")
return game_proton_path return game_proton_path
except Exception as e: except Exception as e:
@@ -821,15 +831,20 @@ class ConfigHandler:
logger.info(f"Auto-detected Proton: {best_proton['name']} ({proton_type})") logger.info(f"Auto-detected Proton: {best_proton['name']} ({proton_type})")
self.save_config() self.save_config()
else: else:
# Fallback to auto-detect mode # Set proton_path to None (will appear as null in JSON) so jackify-engine doesn't get invalid path
self.settings["proton_path"] = "auto" # Code will auto-detect on each run when proton_path is None
self.settings["proton_version"] = "auto" self.settings["proton_path"] = None
logger.info("No compatible Proton versions found, using auto-detect mode") self.settings["proton_version"] = None
logger.warning("No compatible Proton versions found - proton_path set to null in config.json")
logger.info("Jackify will auto-detect Proton on each run until a valid version is found")
self.save_config() self.save_config()
except Exception as e: except Exception as e:
logger.error(f"Failed to auto-detect Proton: {e}") logger.error(f"Failed to auto-detect Proton: {e}")
self.settings["proton_path"] = "auto" # Set proton_path to None (will appear as null in JSON)
self.settings["proton_version"] = "auto" self.settings["proton_path"] = None
self.settings["proton_version"] = None
logger.warning("proton_path set to null in config.json due to auto-detection failure")
self.save_config()

View File

@@ -105,9 +105,9 @@ class ModlistHandler:
verbose: Boolean indicating if verbose output is desired. verbose: Boolean indicating if verbose output is desired.
filesystem_handler: Optional FileSystemHandler instance to use instead of creating a new one. filesystem_handler: Optional FileSystemHandler instance to use instead of creating a new one.
""" """
# Use standard logging (no file handler) # Use standard logging (propagate to root logger so messages appear in logs)
self.logger = logging.getLogger(__name__) self.logger = logging.getLogger(__name__)
self.logger.propagate = False self.logger.propagate = True
self.steamdeck = steamdeck self.steamdeck = steamdeck
# DEBUG: Log ModlistHandler instantiation details for SD card path debugging # DEBUG: Log ModlistHandler instantiation details for SD card path debugging
@@ -746,15 +746,20 @@ class ModlistHandler:
try: try:
registry_success = self._apply_universal_dotnet_fixes() registry_success = self._apply_universal_dotnet_fixes()
except Exception as e: except Exception as e:
self.logger.error(f"CRITICAL: Registry fixes failed - modlist may have .NET compatibility issues: {e}") error_msg = f"CRITICAL: Registry fixes failed - modlist may have .NET compatibility issues: {e}"
self.logger.error(error_msg)
if status_callback:
status_callback(f"{self._get_progress_timestamp()} ERROR: {error_msg}")
registry_success = False registry_success = False
if not registry_success: if not registry_success:
failure_msg = "WARNING: Universal dotnet4.x registry fixes FAILED! This modlist may experience .NET Framework compatibility issues."
self.logger.error("=" * 80) self.logger.error("=" * 80)
self.logger.error("WARNING: Universal dotnet4.x registry fixes FAILED!") self.logger.error(failure_msg)
self.logger.error("This modlist may experience .NET Framework compatibility issues.")
self.logger.error("Consider manually setting mscoree=native in winecfg if problems occur.") self.logger.error("Consider manually setting mscoree=native in winecfg if problems occur.")
self.logger.error("=" * 80) self.logger.error("=" * 80)
if status_callback:
status_callback(f"{self._get_progress_timestamp()} {failure_msg}")
# Continue but user should be aware of potential issues # Continue but user should be aware of potential issues
# Step 4.6: Enable dotfiles visibility for Wine prefix # Step 4.6: Enable dotfiles visibility for Wine prefix
@@ -1596,20 +1601,21 @@ class ModlistHandler:
except Exception as e: except Exception as e:
self.logger.warning(f"Wineserver shutdown failed (non-critical): {e}") self.logger.warning(f"Wineserver shutdown failed (non-critical): {e}")
# Registry fix 1: Set mscoree=native DLL override # Registry fix 1: Set *mscoree=native DLL override (asterisk for full override)
# This tells Wine to use native .NET runtime instead of Wine's implementation # This tells Wine to use native .NET runtime instead of Wine's implementation
self.logger.debug("Setting mscoree=native DLL override...") self.logger.debug("Setting *mscoree=native DLL override...")
cmd1 = [ cmd1 = [
wine_binary, 'reg', 'add', wine_binary, 'reg', 'add',
'HKEY_CURRENT_USER\\Software\\Wine\\DllOverrides', 'HKEY_CURRENT_USER\\Software\\Wine\\DllOverrides',
'/v', 'mscoree', '/t', 'REG_SZ', '/d', 'native', '/f' '/v', '*mscoree', '/t', 'REG_SZ', '/d', 'native', '/f'
] ]
result1 = subprocess.run(cmd1, env=env, capture_output=True, text=True, errors='replace', timeout=30) result1 = subprocess.run(cmd1, env=env, capture_output=True, text=True, errors='replace', timeout=30)
self.logger.info(f"*mscoree registry command result: returncode={result1.returncode}, stdout={result1.stdout[:200]}, stderr={result1.stderr[:200]}")
if result1.returncode == 0: if result1.returncode == 0:
self.logger.info("Successfully applied mscoree=native DLL override") self.logger.info("Successfully applied *mscoree=native DLL override")
else: else:
self.logger.warning(f"Failed to set mscoree DLL override: {result1.stderr}") self.logger.error(f"Failed to set *mscoree DLL override: returncode={result1.returncode}, stderr={result1.stderr}")
# Registry fix 2: Set OnlyUseLatestCLR=1 # Registry fix 2: Set OnlyUseLatestCLR=1
# This prevents .NET version conflicts by using the latest CLR # This prevents .NET version conflicts by using the latest CLR
@@ -1621,10 +1627,11 @@ class ModlistHandler:
] ]
result2 = subprocess.run(cmd2, env=env, capture_output=True, text=True, errors='replace', timeout=30) result2 = subprocess.run(cmd2, env=env, capture_output=True, text=True, errors='replace', timeout=30)
self.logger.info(f"OnlyUseLatestCLR registry command result: returncode={result2.returncode}, stdout={result2.stdout[:200]}, stderr={result2.stderr[:200]}")
if result2.returncode == 0: if result2.returncode == 0:
self.logger.info("Successfully applied OnlyUseLatestCLR=1 registry entry") self.logger.info("Successfully applied OnlyUseLatestCLR=1 registry entry")
else: else:
self.logger.warning(f"Failed to set OnlyUseLatestCLR: {result2.stderr}") self.logger.error(f"Failed to set OnlyUseLatestCLR: returncode={result2.returncode}, stderr={result2.stderr}")
# Force wineserver to flush registry changes to disk # Force wineserver to flush registry changes to disk
if wineserver_binary: if wineserver_binary:
@@ -1639,17 +1646,17 @@ class ModlistHandler:
self.logger.info("Verifying registry entries were applied and persisted...") self.logger.info("Verifying registry entries were applied and persisted...")
verification_passed = True verification_passed = True
# Verify mscoree=native # Verify *mscoree=native
verify_cmd1 = [ verify_cmd1 = [
wine_binary, 'reg', 'query', wine_binary, 'reg', 'query',
'HKEY_CURRENT_USER\\Software\\Wine\\DllOverrides', 'HKEY_CURRENT_USER\\Software\\Wine\\DllOverrides',
'/v', 'mscoree' '/v', '*mscoree'
] ]
verify_result1 = subprocess.run(verify_cmd1, env=env, capture_output=True, text=True, errors='replace', timeout=30) verify_result1 = subprocess.run(verify_cmd1, env=env, capture_output=True, text=True, errors='replace', timeout=30)
if verify_result1.returncode == 0 and 'native' in verify_result1.stdout: if verify_result1.returncode == 0 and 'native' in verify_result1.stdout:
self.logger.info("VERIFIED: mscoree=native is set correctly") self.logger.info("VERIFIED: *mscoree=native is set correctly")
else: else:
self.logger.error(f"VERIFICATION FAILED: mscoree=native not found in registry. Query output: {verify_result1.stdout}") self.logger.error(f"VERIFICATION FAILED: *mscoree=native not found in registry. Query output: {verify_result1.stdout}")
verification_passed = False verification_passed = False
# Verify OnlyUseLatestCLR=1 # Verify OnlyUseLatestCLR=1
@@ -1696,10 +1703,17 @@ class ModlistHandler:
] ]
for wine_path in wine_candidates: for wine_path in wine_candidates:
if wine_path.exists(): if wine_path.exists() and wine_path.is_file():
self.logger.info(f"Using Wine binary from user's configured Proton: {wine_path}") self.logger.info(f"Using Wine binary from user's configured Proton: {wine_path}")
return str(wine_path) return str(wine_path)
# Wine binary not found at expected paths - search recursively in Proton directory
self.logger.debug(f"Wine binary not found at expected paths in {proton_path}, searching recursively...")
wine_binary = self._search_wine_in_proton_directory(proton_path)
if wine_binary:
self.logger.info(f"Found Wine binary via recursive search in Proton directory: {wine_binary}")
return wine_binary
self.logger.warning(f"User's configured Proton path has no wine binary: {user_proton_path}") self.logger.warning(f"User's configured Proton path has no wine binary: {user_proton_path}")
# Fallback: Try to use same Steam library detection as main Proton detection # Fallback: Try to use same Steam library detection as main Proton detection
@@ -1719,4 +1733,42 @@ class ModlistHandler:
self.logger.error(f"Error finding Wine binary: {e}") self.logger.error(f"Error finding Wine binary: {e}")
return None return None
def _search_wine_in_proton_directory(self, proton_path: Path) -> Optional[str]:
"""
Recursively search for wine binary within a Proton directory.
This handles cases where the directory structure might differ between Proton versions.
Args:
proton_path: Path to the Proton directory to search
Returns:
Path to wine binary if found, None otherwise
"""
try:
if not proton_path.exists() or not proton_path.is_dir():
return None
# Search for 'wine' executable (not 'wine64' or 'wine-preloader')
# Limit search depth to avoid scanning entire filesystem
max_depth = 5
for root, dirs, files in os.walk(proton_path, followlinks=False):
# Calculate depth relative to proton_path
depth = len(Path(root).relative_to(proton_path).parts)
if depth > max_depth:
dirs.clear() # Don't descend further
continue
# Check if 'wine' is in this directory
if 'wine' in files:
wine_path = Path(root) / 'wine'
# Verify it's actually an executable file
if wine_path.is_file() and os.access(wine_path, os.X_OK):
self.logger.debug(f"Found wine binary at: {wine_path}")
return str(wine_path)
return None
except Exception as e:
self.logger.debug(f"Error during recursive wine search in {proton_path}: {e}")
return None

View File

@@ -427,10 +427,11 @@ class ModlistInstallCLI:
print("\n" + "-" * 28) print("\n" + "-" * 28)
print(f"{COLOR_INFO}Nexus Authentication: Using API Key (Legacy){COLOR_RESET}") print(f"{COLOR_INFO}Nexus Authentication: Using API Key (Legacy){COLOR_RESET}")
# Get valid token/key # Get valid token/key and OAuth state for engine auto-refresh
api_key = auth_service.ensure_valid_auth() api_key, oauth_info = auth_service.get_auth_for_engine()
if api_key: if api_key:
self.context['nexus_api_key'] = api_key self.context['nexus_api_key'] = api_key
self.context['nexus_oauth_info'] = oauth_info # For engine auto-refresh
else: else:
# Auth expired or invalid - prompt to set up # Auth expired or invalid - prompt to set up
print(f"\n{COLOR_WARNING}Your authentication has expired or is invalid.{COLOR_RESET}") print(f"\n{COLOR_WARNING}Your authentication has expired or is invalid.{COLOR_RESET}")
@@ -463,9 +464,10 @@ class ModlistInstallCLI:
if username: if username:
print(f"{COLOR_INFO}Authorized as: {username}{COLOR_RESET}") print(f"{COLOR_INFO}Authorized as: {username}{COLOR_RESET}")
api_key = auth_service.ensure_valid_auth() api_key, oauth_info = auth_service.get_auth_for_engine()
if api_key: if api_key:
self.context['nexus_api_key'] = api_key self.context['nexus_api_key'] = api_key
self.context['nexus_oauth_info'] = oauth_info # For engine auto-refresh
else: else:
print(f"{COLOR_ERROR}Failed to retrieve auth token after authorization.{COLOR_RESET}") print(f"{COLOR_ERROR}Failed to retrieve auth token after authorization.{COLOR_RESET}")
return None return None
@@ -616,6 +618,7 @@ class ModlistInstallCLI:
modlist_arg = self.context.get('modlist_value') or self.context.get('machineid') modlist_arg = self.context.get('modlist_value') or self.context.get('machineid')
machineid = self.context.get('machineid') machineid = self.context.get('machineid')
api_key = self.context['nexus_api_key'] api_key = self.context['nexus_api_key']
oauth_info = self.context.get('nexus_oauth_info')
# Path to the engine binary # Path to the engine binary
engine_path = get_jackify_engine_path() engine_path = get_jackify_engine_path()
@@ -675,24 +678,37 @@ class ModlistInstallCLI:
# Store original environment values to restore later # Store original environment values to restore later
original_env_values = { original_env_values = {
'NEXUS_API_KEY': os.environ.get('NEXUS_API_KEY'), 'NEXUS_API_KEY': os.environ.get('NEXUS_API_KEY'),
'NEXUS_OAUTH_INFO': os.environ.get('NEXUS_OAUTH_INFO'),
'DOTNET_SYSTEM_GLOBALIZATION_INVARIANT': os.environ.get('DOTNET_SYSTEM_GLOBALIZATION_INVARIANT') 'DOTNET_SYSTEM_GLOBALIZATION_INVARIANT': os.environ.get('DOTNET_SYSTEM_GLOBALIZATION_INVARIANT')
} }
try: try:
# Temporarily modify current process's environment # Temporarily modify current process's environment
# Prefer NEXUS_OAUTH_INFO (supports auto-refresh) over NEXUS_API_KEY (legacy)
if oauth_info:
os.environ['NEXUS_OAUTH_INFO'] = oauth_info
self.logger.debug(f"Set NEXUS_OAUTH_INFO for engine (supports auto-refresh)")
# Also set NEXUS_API_KEY for backward compatibility
if api_key: if api_key:
os.environ['NEXUS_API_KEY'] = api_key os.environ['NEXUS_API_KEY'] = api_key
self.logger.debug(f"Temporarily set os.environ['NEXUS_API_KEY'] for engine call using session-provided key.") elif api_key:
elif 'NEXUS_API_KEY' in os.environ: # api_key is None/empty, but a system key might exist # No OAuth info, use API key only (no auto-refresh support)
self.logger.debug(f"Session API key not provided. Temporarily removing inherited NEXUS_API_KEY ('{'[REDACTED]' if os.environ.get('NEXUS_API_KEY') else 'None'}') from os.environ for engine call to ensure it is not used.") os.environ['NEXUS_API_KEY'] = api_key
self.logger.debug(f"Set NEXUS_API_KEY for engine (no auto-refresh)")
else:
# No auth available, clear any inherited values
if 'NEXUS_API_KEY' in os.environ:
del os.environ['NEXUS_API_KEY'] del os.environ['NEXUS_API_KEY']
# If api_key is None and NEXUS_API_KEY was not in os.environ, it remains unset, which is correct. if 'NEXUS_OAUTH_INFO' in os.environ:
del os.environ['NEXUS_OAUTH_INFO']
self.logger.debug(f"No Nexus auth available, cleared inherited env vars")
os.environ['DOTNET_SYSTEM_GLOBALIZATION_INVARIANT'] = "1" os.environ['DOTNET_SYSTEM_GLOBALIZATION_INVARIANT'] = "1"
self.logger.debug(f"Temporarily set os.environ['DOTNET_SYSTEM_GLOBALIZATION_INVARIANT'] = '1' for engine call.") self.logger.debug(f"Temporarily set os.environ['DOTNET_SYSTEM_GLOBALIZATION_INVARIANT'] = '1' for engine call.")
self.logger.info("Environment prepared for jackify-engine install process by modifying os.environ.") self.logger.info("Environment prepared for jackify-engine install process by modifying os.environ.")
self.logger.debug(f"NEXUS_API_KEY in os.environ (pre-call): {'[SET]' if os.environ.get('NEXUS_API_KEY') else '[NOT SET]'}") self.logger.debug(f"NEXUS_API_KEY in os.environ (pre-call): {'[SET]' if os.environ.get('NEXUS_API_KEY') else '[NOT SET]'}")
self.logger.debug(f"NEXUS_OAUTH_INFO in os.environ (pre-call): {'[SET]' if os.environ.get('NEXUS_OAUTH_INFO') else '[NOT SET]'}")
pretty_cmd = ' '.join([f'"{arg}"' if ' ' in arg else arg for arg in cmd]) pretty_cmd = ' '.join([f'"{arg}"' if ' ' in arg else arg for arg in cmd])
print(f"{COLOR_INFO}Launching Jackify Install Engine with command:{COLOR_RESET} {pretty_cmd}") print(f"{COLOR_INFO}Launching Jackify Install Engine with command:{COLOR_RESET} {pretty_cmd}")

View File

@@ -596,19 +596,29 @@ class ProtontricksHandler:
try: try:
if user_reg_path.exists(): if user_reg_path.exists():
content = user_reg_path.read_text(encoding='utf-8', errors='ignore') content = user_reg_path.read_text(encoding='utf-8', errors='ignore')
if "ShowDotFiles" not in content: # Check for CORRECT format with proper backslash escaping
has_correct_format = '[Software\\\\Wine]' in content and '"ShowDotFiles"="Y"' in content
has_broken_format = '[SoftwareWine]' in content and '"ShowDotFiles"="Y"' in content
if has_broken_format and not has_correct_format:
# Fix the broken format by replacing the section header
logger.debug(f"Found broken ShowDotFiles format in {user_reg_path}, fixing...")
content = content.replace('[SoftwareWine]', '[Software\\\\Wine]')
user_reg_path.write_text(content, encoding='utf-8')
dotfiles_set_success = True
elif not has_correct_format:
logger.debug(f"Adding ShowDotFiles entry to {user_reg_path}") logger.debug(f"Adding ShowDotFiles entry to {user_reg_path}")
with open(user_reg_path, 'a', encoding='utf-8') as f: with open(user_reg_path, 'a', encoding='utf-8') as f:
f.write('\n[Software\\Wine] 1603891765\n') f.write('\n[Software\\\\Wine] 1603891765\n')
f.write('"ShowDotFiles"="Y"\n') f.write('"ShowDotFiles"="Y"\n')
dotfiles_set_success = True # Count file write as success too dotfiles_set_success = True # Count file write as success too
else: else:
logger.debug("ShowDotFiles already present in user.reg") logger.debug("ShowDotFiles already present in correct format in user.reg")
dotfiles_set_success = True # Already there counts as success dotfiles_set_success = True # Already there counts as success
else: else:
logger.warning(f"user.reg not found at {user_reg_path}, creating it.") logger.warning(f"user.reg not found at {user_reg_path}, creating it.")
with open(user_reg_path, 'w', encoding='utf-8') as f: with open(user_reg_path, 'w', encoding='utf-8') as f:
f.write('[Software\\Wine] 1603891765\n') f.write('[Software\\\\Wine] 1603891765\n')
f.write('"ShowDotFiles"="Y"\n') f.write('"ShowDotFiles"="Y"\n')
dotfiles_set_success = True # Creating file counts as success dotfiles_set_success = True # Creating file counts as success
except Exception as e: except Exception as e:

View File

@@ -41,8 +41,8 @@ def get_clean_subprocess_env(extra_env=None):
""" """
Returns a copy of os.environ with bundled-runtime variables and other problematic entries removed. Returns a copy of os.environ with bundled-runtime variables and other problematic entries removed.
Optionally merges in extra_env dict. Optionally merges in extra_env dict.
Also ensures bundled tools (lz4, unzip, etc.) are in PATH when running as AppImage. Also ensures bundled tools (lz4, cabextract, winetricks) are in PATH when running as AppImage.
CRITICAL: Preserves system PATH to ensure system tools (like lz4) are available. CRITICAL: Preserves system PATH to ensure system utilities (wget, curl, unzip, xz, gzip, sha256sum) are available.
""" """
from pathlib import Path from pathlib import Path
@@ -73,7 +73,8 @@ def get_clean_subprocess_env(extra_env=None):
path_parts.append(sys_path) path_parts.append(sys_path)
# Add bundled tools directory to PATH if running as AppImage # Add bundled tools directory to PATH if running as AppImage
# This ensures lz4, unzip, xz, etc. are available to subprocesses # This ensures lz4, cabextract, and winetricks are available to subprocesses
# System utilities (wget, curl, unzip, xz, gzip, sha256sum) come from system PATH
# Note: appdir was saved before env cleanup above # Note: appdir was saved before env cleanup above
tools_dir = None tools_dir = None
@@ -100,23 +101,23 @@ def get_clean_subprocess_env(extra_env=None):
tools_dir = str(possible_dir) tools_dir = str(possible_dir)
break break
# Build final PATH: bundled tools first (if any), then original PATH with system paths # Build final PATH: system PATH first, then bundled tools (lz4, cabextract, winetricks)
# System utilities (wget, curl, unzip, xz, gzip, sha256sum) are preferred from system
final_path_parts = [] final_path_parts = []
if tools_dir and os.path.isdir(tools_dir):
# Prepend tools directory so bundled tools take precedence
# This is critical - bundled lz4 must come before system lz4
final_path_parts.append(tools_dir)
# Add all other paths (preserving order, removing duplicates) # Add all other paths first (system utilities take precedence)
# Note: AppRun already sets PATH with tools directory, but we ensure it's first
seen = set() seen = set()
if tools_dir:
seen.add(tools_dir) # Already added, don't add again
for path_part in path_parts: for path_part in path_parts:
if path_part and path_part not in seen: if path_part and path_part not in seen:
final_path_parts.append(path_part) final_path_parts.append(path_part)
seen.add(path_part) seen.add(path_part)
# Then add bundled tools directory (for lz4, cabextract, winetricks)
if tools_dir and os.path.isdir(tools_dir) and tools_dir not in seen:
final_path_parts.append(tools_dir)
seen.add(tools_dir)
env['PATH'] = ':'.join(final_path_parts) env['PATH'] = ':'.join(final_path_parts)
# Optionally restore LD_LIBRARY_PATH to system default if needed # Optionally restore LD_LIBRARY_PATH to system default if needed

View File

@@ -149,7 +149,7 @@ class WinetricksHandler:
# If user selected a specific Proton, try that first # If user selected a specific Proton, try that first
wine_binary = None wine_binary = None
if user_proton_path != 'auto': if user_proton_path and user_proton_path != 'auto':
# Check if user-selected Proton still exists # Check if user-selected Proton still exists
if os.path.exists(user_proton_path): if os.path.exists(user_proton_path):
# Resolve symlinks to handle ~/.steam/steam -> ~/.local/share/Steam # Resolve symlinks to handle ~/.steam/steam -> ~/.local/share/Steam
@@ -582,7 +582,7 @@ class WinetricksHandler:
user_proton_path = config.get_proton_path() user_proton_path = config.get_proton_path()
wine_binary = None wine_binary = None
if user_proton_path != 'auto': if user_proton_path and user_proton_path != 'auto':
if os.path.exists(user_proton_path): if os.path.exists(user_proton_path):
resolved_proton_path = os.path.realpath(user_proton_path) resolved_proton_path = os.path.realpath(user_proton_path)
valve_proton_wine = os.path.join(resolved_proton_path, 'dist', 'bin', 'wine') valve_proton_wine = os.path.join(resolved_proton_path, 'dist', 'bin', 'wine')
@@ -594,8 +594,8 @@ class WinetricksHandler:
wine_binary = ge_proton_wine wine_binary = ge_proton_wine
if not wine_binary: if not wine_binary:
if user_proton_path == 'auto': if not user_proton_path or user_proton_path == 'auto':
self.logger.info("Auto-detecting Proton (user selected 'auto')") self.logger.info("Auto-detecting Proton (user selected 'auto' or path not set)")
best_proton = WineUtils.select_best_proton() best_proton = WineUtils.select_best_proton()
if best_proton: if best_proton:
wine_binary = WineUtils.find_proton_binary(best_proton['name']) wine_binary = WineUtils.find_proton_binary(best_proton['name'])
@@ -811,7 +811,7 @@ class WinetricksHandler:
# If user selected a specific Proton, try that first # If user selected a specific Proton, try that first
wine_binary = None wine_binary = None
if user_proton_path != 'auto': if user_proton_path and user_proton_path != 'auto':
if os.path.exists(user_proton_path): if os.path.exists(user_proton_path):
resolved_proton_path = os.path.realpath(user_proton_path) resolved_proton_path = os.path.realpath(user_proton_path)
valve_proton_wine = os.path.join(resolved_proton_path, 'dist', 'bin', 'wine') valve_proton_wine = os.path.join(resolved_proton_path, 'dist', 'bin', 'wine')
@@ -822,10 +822,10 @@ class WinetricksHandler:
elif os.path.exists(ge_proton_wine): elif os.path.exists(ge_proton_wine):
wine_binary = ge_proton_wine wine_binary = ge_proton_wine
# Only auto-detect if user explicitly chose 'auto' # Only auto-detect if user explicitly chose 'auto' or path is not set
if not wine_binary: if not wine_binary:
if user_proton_path == 'auto': if not user_proton_path or user_proton_path == 'auto':
self.logger.info("Auto-detecting Proton (user selected 'auto')") self.logger.info("Auto-detecting Proton (user selected 'auto' or path not set)")
best_proton = WineUtils.select_best_proton() best_proton = WineUtils.select_best_proton()
if best_proton: if best_proton:
wine_binary = WineUtils.find_proton_binary(best_proton['name']) wine_binary = WineUtils.find_proton_binary(best_proton['name'])

View File

@@ -71,7 +71,7 @@ class AutomatedPrefixService:
config_handler = ConfigHandler() config_handler = ConfigHandler()
user_proton_path = config_handler.get_game_proton_path() user_proton_path = config_handler.get_game_proton_path()
if user_proton_path == 'auto': if not user_proton_path or user_proton_path == 'auto':
# Use enhanced fallback logic with GE-Proton preference # Use enhanced fallback logic with GE-Proton preference
logger.info("User selected auto-detect, using GE-Proton → Experimental → Proton precedence") logger.info("User selected auto-detect, using GE-Proton → Experimental → Proton precedence")
return WineUtils.select_best_proton() return WineUtils.select_best_proton()
@@ -3095,20 +3095,20 @@ echo Prefix creation complete.
env['WINEPREFIX'] = prefix_path env['WINEPREFIX'] = prefix_path
env['WINEDEBUG'] = '-all' # Suppress Wine debug output env['WINEDEBUG'] = '-all' # Suppress Wine debug output
# Registry fix 1: Set mscoree=native DLL override # Registry fix 1: Set *mscoree=native DLL override (asterisk for full override)
# This tells Wine to use native .NET runtime instead of Wine's implementation # This tells Wine to use native .NET runtime instead of Wine's implementation
logger.debug("Setting mscoree=native DLL override...") logger.debug("Setting *mscoree=native DLL override...")
cmd1 = [ cmd1 = [
wine_binary, 'reg', 'add', wine_binary, 'reg', 'add',
'HKEY_CURRENT_USER\\Software\\Wine\\DllOverrides', 'HKEY_CURRENT_USER\\Software\\Wine\\DllOverrides',
'/v', 'mscoree', '/t', 'REG_SZ', '/d', 'native', '/f' '/v', '*mscoree', '/t', 'REG_SZ', '/d', 'native', '/f'
] ]
result1 = subprocess.run(cmd1, env=env, capture_output=True, text=True, errors='replace') result1 = subprocess.run(cmd1, env=env, capture_output=True, text=True, errors='replace')
if result1.returncode == 0: if result1.returncode == 0:
logger.info("Successfully applied mscoree=native DLL override") logger.info("Successfully applied *mscoree=native DLL override")
else: else:
logger.warning(f"Failed to set mscoree DLL override: {result1.stderr}") logger.warning(f"Failed to set *mscoree DLL override: {result1.stderr}")
# Registry fix 2: Set OnlyUseLatestCLR=1 # Registry fix 2: Set OnlyUseLatestCLR=1
# This prevents .NET version conflicts by using the latest CLR # This prevents .NET version conflicts by using the latest CLR
@@ -3140,39 +3140,96 @@ echo Prefix creation complete.
def _find_wine_binary_for_registry(self, modlist_compatdata_path: str) -> Optional[str]: def _find_wine_binary_for_registry(self, modlist_compatdata_path: str) -> Optional[str]:
"""Find the appropriate Wine binary for registry operations""" """Find the appropriate Wine binary for registry operations"""
try: try:
# Method 1: Try to detect from Steam's config or use Proton from compat data from ..handlers.config_handler import ConfigHandler
# Look for wine binary in common Proton locations from ..handlers.wine_utils import WineUtils
proton_paths = [
os.path.expanduser("~/.local/share/Steam/compatibilitytools.d"), # Method 1: Use the user's configured Proton version from settings
os.path.expanduser("~/.steam/steam/steamapps/common") config_handler = ConfigHandler()
user_proton_path = config_handler.get_game_proton_path()
if user_proton_path and user_proton_path != 'auto':
# User has selected a specific Proton version
proton_path = Path(user_proton_path).expanduser()
# Check for wine binary in both GE-Proton and Valve Proton structures
wine_candidates = [
proton_path / "files" / "bin" / "wine", # GE-Proton structure
proton_path / "dist" / "bin" / "wine" # Valve Proton structure
] ]
for base_path in proton_paths: for wine_path in wine_candidates:
if os.path.exists(base_path): if wine_path.exists() and wine_path.is_file():
for item in os.listdir(base_path): logger.info(f"Using Wine binary from user's configured Proton: {wine_path}")
if 'proton' in item.lower(): return str(wine_path)
wine_path = os.path.join(base_path, item, 'files', 'bin', 'wine')
if os.path.exists(wine_path):
logger.debug(f"Found Wine binary: {wine_path}")
return wine_path
# Method 2: Fallback to system wine if available # Wine binary not found at expected paths - search recursively in Proton directory
try: logger.debug(f"Wine binary not found at expected paths in {proton_path}, searching recursively...")
result = subprocess.run(['which', 'wine'], capture_output=True, text=True) wine_binary = self._search_wine_in_proton_directory(proton_path)
if result.returncode == 0: if wine_binary:
wine_path = result.stdout.strip() logger.info(f"Found Wine binary via recursive search in Proton directory: {wine_binary}")
logger.debug(f"Using system Wine binary: {wine_path}") return wine_binary
return wine_path
except Exception:
pass
logger.error("No suitable Wine binary found for registry operations") logger.warning(f"User's configured Proton path has no wine binary: {user_proton_path}")
# Method 2: Fallback to auto-detection using WineUtils
best_proton = WineUtils.select_best_proton()
if best_proton:
wine_binary = WineUtils.find_proton_binary(best_proton['name'])
if wine_binary:
logger.info(f"Using Wine binary from detected Proton: {wine_binary}")
return wine_binary
# NEVER fall back to system wine - it will break Proton prefixes with architecture mismatches
logger.error("No suitable Proton Wine binary found for registry operations")
return None return None
except Exception as e: except Exception as e:
logger.error(f"Error finding Wine binary: {e}") logger.error(f"Error finding Wine binary: {e}")
return None return None
def _search_wine_in_proton_directory(self, proton_path: Path) -> Optional[str]:
"""
Recursively search for wine binary within a Proton directory.
This handles cases where the directory structure might differ between Proton versions.
Args:
proton_path: Path to the Proton directory to search
Returns:
Path to wine binary if found, None otherwise
"""
try:
if not proton_path.exists() or not proton_path.is_dir():
return None
# Search for 'wine' executable (not 'wine64' or 'wine-preloader')
# Limit search depth to avoid scanning entire filesystem
max_depth = 5
for root, dirs, files in os.walk(proton_path, followlinks=False):
# Calculate depth relative to proton_path
try:
depth = len(Path(root).relative_to(proton_path).parts)
except ValueError:
# Path is not relative to proton_path (shouldn't happen, but be safe)
continue
if depth > max_depth:
dirs.clear() # Don't descend further
continue
# Check if 'wine' is in this directory
if 'wine' in files:
wine_path = Path(root) / 'wine'
# Verify it's actually an executable file
if wine_path.is_file() and os.access(wine_path, os.X_OK):
logger.debug(f"Found wine binary at: {wine_path}")
return str(wine_path)
return None
except Exception as e:
logger.debug(f"Error during recursive wine search in {proton_path}: {e}")
return None
def _inject_game_registry_entries(self, modlist_compatdata_path: str): def _inject_game_registry_entries(self, modlist_compatdata_path: str):
"""Detect and inject FNV/Enderal game paths and apply universal dotnet4.x compatibility fixes""" """Detect and inject FNV/Enderal game paths and apply universal dotnet4.x compatibility fixes"""
system_reg_path = os.path.join(modlist_compatdata_path, "pfx", "system.reg") system_reg_path = os.path.join(modlist_compatdata_path, "pfx", "system.reg")

View File

@@ -276,6 +276,7 @@ class ModlistService:
download_dir_str = str(actual_download_path) download_dir_str = str(actual_download_path)
api_key = context['nexus_api_key'] api_key = context['nexus_api_key']
oauth_info = context.get('nexus_oauth_info')
# Path to the engine binary (copied from working code) # Path to the engine binary (copied from working code)
engine_path = get_jackify_engine_path() engine_path = get_jackify_engine_path()
@@ -302,15 +303,25 @@ class ModlistService:
# Store original environment values (copied from working code) # Store original environment values (copied from working code)
original_env_values = { original_env_values = {
'NEXUS_API_KEY': os.environ.get('NEXUS_API_KEY'), 'NEXUS_API_KEY': os.environ.get('NEXUS_API_KEY'),
'NEXUS_OAUTH_INFO': os.environ.get('NEXUS_OAUTH_INFO'),
'DOTNET_SYSTEM_GLOBALIZATION_INVARIANT': os.environ.get('DOTNET_SYSTEM_GLOBALIZATION_INVARIANT') 'DOTNET_SYSTEM_GLOBALIZATION_INVARIANT': os.environ.get('DOTNET_SYSTEM_GLOBALIZATION_INVARIANT')
} }
try: try:
# Environment setup (copied from working code) # Environment setup - prefer NEXUS_OAUTH_INFO (supports auto-refresh) over NEXUS_API_KEY
if oauth_info:
os.environ['NEXUS_OAUTH_INFO'] = oauth_info
# Also set NEXUS_API_KEY for backward compatibility
if api_key: if api_key:
os.environ['NEXUS_API_KEY'] = api_key os.environ['NEXUS_API_KEY'] = api_key
elif 'NEXUS_API_KEY' in os.environ: elif api_key:
os.environ['NEXUS_API_KEY'] = api_key
else:
# No auth available, clear any inherited values
if 'NEXUS_API_KEY' in os.environ:
del os.environ['NEXUS_API_KEY'] del os.environ['NEXUS_API_KEY']
if 'NEXUS_OAUTH_INFO' in os.environ:
del os.environ['NEXUS_OAUTH_INFO']
os.environ['DOTNET_SYSTEM_GLOBALIZATION_INVARIANT'] = "1" os.environ['DOTNET_SYSTEM_GLOBALIZATION_INVARIANT'] = "1"

View File

@@ -228,16 +228,65 @@ class NexusAuthService:
return auth_token return auth_token
def get_auth_for_engine(self) -> Optional[str]: def get_auth_for_engine(self) -> Tuple[Optional[str], Optional[str]]:
""" """
Get authentication token for jackify-engine Get authentication for jackify-engine with auto-refresh support
Same as ensure_valid_auth() - engine uses NEXUS_API_KEY env var for both OAuth and API keys
(This matches upstream Wabbajack behavior) Returns both NEXUS_API_KEY (for backward compat) and NEXUS_OAUTH_INFO (for auto-refresh).
When NEXUS_OAUTH_INFO is provided, the engine can automatically refresh expired tokens
during long installations.
Returns: Returns:
Valid auth token to pass via NEXUS_API_KEY environment variable, or None Tuple of (nexus_api_key, nexus_oauth_info_json)
- nexus_api_key: Access token or API key (for backward compat)
- nexus_oauth_info_json: Full OAuth state JSON (for auto-refresh) or None
""" """
return self.ensure_valid_auth() import json
import time
# Check if using OAuth and ensure token is fresh
if self.token_handler.has_token():
# Refresh token if expired (15 minute buffer for long installs)
access_token = self._get_oauth_token()
if not access_token:
logger.warning("OAuth token refresh failed, cannot provide auth to engine")
return (None, None)
# Load the refreshed token data
token_data = self.token_handler.load_token()
if token_data:
oauth_data = token_data.get('oauth', {})
# Build NexusOAuthState JSON matching upstream Wabbajack format
# This allows engine to auto-refresh tokens during long installations
nexus_oauth_state = {
"oauth": {
"access_token": oauth_data.get('access_token'),
"token_type": oauth_data.get('token_type', 'Bearer'),
"expires_in": oauth_data.get('expires_in', 3600),
"refresh_token": oauth_data.get('refresh_token'),
"scope": oauth_data.get('scope', 'public openid profile'),
"created_at": oauth_data.get('created_at', int(time.time())),
"_received_at": token_data.get('_saved_at', int(time.time())) * 10000000 + 116444736000000000 # Convert Unix to Windows FILETIME
},
"api_key": ""
}
nexus_oauth_json = json.dumps(nexus_oauth_state)
access_token = oauth_data.get('access_token')
logger.info("Providing OAuth state to engine for auto-refresh capability")
return (access_token, nexus_oauth_json)
# Fall back to API key (no auto-refresh support)
api_key = self.api_key_service.get_saved_api_key()
if api_key:
logger.info("Using API key for engine (no auto-refresh)")
return (api_key, None)
logger.warning("No authentication available for engine")
return (None, None)
def clear_all_auth(self) -> bool: def clear_all_auth(self) -> bool:
""" """

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -7,7 +7,7 @@
"targets": { "targets": {
".NETCoreApp,Version=v8.0": {}, ".NETCoreApp,Version=v8.0": {},
".NETCoreApp,Version=v8.0/linux-x64": { ".NETCoreApp,Version=v8.0/linux-x64": {
"jackify-engine/0.4.4": { "jackify-engine/0.4.5": {
"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.4.4", "Wabbajack.CLI.Builder": "0.4.5",
"Wabbajack.Downloaders.Bethesda": "0.4.4", "Wabbajack.Downloaders.Bethesda": "0.4.5",
"Wabbajack.Downloaders.Dispatcher": "0.4.4", "Wabbajack.Downloaders.Dispatcher": "0.4.5",
"Wabbajack.Hashing.xxHash64": "0.4.4", "Wabbajack.Hashing.xxHash64": "0.4.5",
"Wabbajack.Networking.Discord": "0.4.4", "Wabbajack.Networking.Discord": "0.4.5",
"Wabbajack.Networking.GitHub": "0.4.4", "Wabbajack.Networking.GitHub": "0.4.5",
"Wabbajack.Paths.IO": "0.4.4", "Wabbajack.Paths.IO": "0.4.5",
"Wabbajack.Server.Lib": "0.4.4", "Wabbajack.Server.Lib": "0.4.5",
"Wabbajack.Services.OSIntegrated": "0.4.4", "Wabbajack.Services.OSIntegrated": "0.4.5",
"Wabbajack.VFS": "0.4.4", "Wabbajack.VFS": "0.4.5",
"MegaApiClient": "1.0.0.0", "MegaApiClient": "1.0.0.0",
"runtimepack.Microsoft.NETCore.App.Runtime.linux-x64": "8.0.22" "runtimepack.Microsoft.NETCore.App.Runtime.linux-x64": "8.0.22"
}, },
@@ -1781,7 +1781,7 @@
} }
} }
}, },
"Wabbajack.CLI.Builder/0.4.4": { "Wabbajack.CLI.Builder/0.4.5": {
"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.4.4" "Wabbajack.Paths": "0.4.5"
}, },
"runtime": { "runtime": {
"Wabbajack.CLI.Builder.dll": {} "Wabbajack.CLI.Builder.dll": {}
} }
}, },
"Wabbajack.Common/0.4.4": { "Wabbajack.Common/0.4.5": {
"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.4.4", "Wabbajack.DTOs": "0.4.5",
"Wabbajack.Networking.Http": "0.4.4", "Wabbajack.Networking.Http": "0.4.5",
"Wabbajack.Paths.IO": "0.4.4" "Wabbajack.Paths.IO": "0.4.5"
}, },
"runtime": { "runtime": {
"Wabbajack.Common.dll": {} "Wabbajack.Common.dll": {}
} }
}, },
"Wabbajack.Compiler/0.4.4": { "Wabbajack.Compiler/0.4.5": {
"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.4.4", "Wabbajack.Downloaders.Dispatcher": "0.4.5",
"Wabbajack.Installer": "0.4.4", "Wabbajack.Installer": "0.4.5",
"Wabbajack.VFS": "0.4.4", "Wabbajack.VFS": "0.4.5",
"ini-parser-netstandard": "2.5.2" "ini-parser-netstandard": "2.5.2"
}, },
"runtime": { "runtime": {
"Wabbajack.Compiler.dll": {} "Wabbajack.Compiler.dll": {}
} }
}, },
"Wabbajack.Compression.BSA/0.4.4": { "Wabbajack.Compression.BSA/0.4.5": {
"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.4.4", "Wabbajack.Common": "0.4.5",
"Wabbajack.DTOs": "0.4.4" "Wabbajack.DTOs": "0.4.5"
}, },
"runtime": { "runtime": {
"Wabbajack.Compression.BSA.dll": {} "Wabbajack.Compression.BSA.dll": {}
} }
}, },
"Wabbajack.Compression.Zip/0.4.4": { "Wabbajack.Compression.Zip/0.4.5": {
"dependencies": { "dependencies": {
"Wabbajack.IO.Async": "0.4.4" "Wabbajack.IO.Async": "0.4.5"
}, },
"runtime": { "runtime": {
"Wabbajack.Compression.Zip.dll": {} "Wabbajack.Compression.Zip.dll": {}
} }
}, },
"Wabbajack.Configuration/0.4.4": { "Wabbajack.Configuration/0.4.5": {
"runtime": { "runtime": {
"Wabbajack.Configuration.dll": {} "Wabbajack.Configuration.dll": {}
} }
}, },
"Wabbajack.Downloaders.Bethesda/0.4.4": { "Wabbajack.Downloaders.Bethesda/0.4.5": {
"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.4.4", "Wabbajack.Common": "0.4.5",
"Wabbajack.Downloaders.Interfaces": "0.4.4", "Wabbajack.Downloaders.Interfaces": "0.4.5",
"Wabbajack.Networking.BethesdaNet": "0.4.4" "Wabbajack.Networking.BethesdaNet": "0.4.5"
}, },
"runtime": { "runtime": {
"Wabbajack.Downloaders.Bethesda.dll": {} "Wabbajack.Downloaders.Bethesda.dll": {}
} }
}, },
"Wabbajack.Downloaders.Dispatcher/0.4.4": { "Wabbajack.Downloaders.Dispatcher/0.4.5": {
"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.4.4", "Wabbajack.Downloaders.Bethesda": "0.4.5",
"Wabbajack.Downloaders.GameFile": "0.4.4", "Wabbajack.Downloaders.GameFile": "0.4.5",
"Wabbajack.Downloaders.GoogleDrive": "0.4.4", "Wabbajack.Downloaders.GoogleDrive": "0.4.5",
"Wabbajack.Downloaders.Http": "0.4.4", "Wabbajack.Downloaders.Http": "0.4.5",
"Wabbajack.Downloaders.IPS4OAuth2Downloader": "0.4.4", "Wabbajack.Downloaders.IPS4OAuth2Downloader": "0.4.5",
"Wabbajack.Downloaders.Interfaces": "0.4.4", "Wabbajack.Downloaders.Interfaces": "0.4.5",
"Wabbajack.Downloaders.Manual": "0.4.4", "Wabbajack.Downloaders.Manual": "0.4.5",
"Wabbajack.Downloaders.MediaFire": "0.4.4", "Wabbajack.Downloaders.MediaFire": "0.4.5",
"Wabbajack.Downloaders.Mega": "0.4.4", "Wabbajack.Downloaders.Mega": "0.4.5",
"Wabbajack.Downloaders.ModDB": "0.4.4", "Wabbajack.Downloaders.ModDB": "0.4.5",
"Wabbajack.Downloaders.Nexus": "0.4.4", "Wabbajack.Downloaders.Nexus": "0.4.5",
"Wabbajack.Downloaders.VerificationCache": "0.4.4", "Wabbajack.Downloaders.VerificationCache": "0.4.5",
"Wabbajack.Downloaders.WabbajackCDN": "0.4.4", "Wabbajack.Downloaders.WabbajackCDN": "0.4.5",
"Wabbajack.Networking.WabbajackClientApi": "0.4.4" "Wabbajack.Networking.WabbajackClientApi": "0.4.5"
}, },
"runtime": { "runtime": {
"Wabbajack.Downloaders.Dispatcher.dll": {} "Wabbajack.Downloaders.Dispatcher.dll": {}
} }
}, },
"Wabbajack.Downloaders.GameFile/0.4.4": { "Wabbajack.Downloaders.GameFile/0.4.5": {
"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.4.4", "Wabbajack.Downloaders.Interfaces": "0.4.5",
"Wabbajack.VFS": "0.4.4" "Wabbajack.VFS": "0.4.5"
}, },
"runtime": { "runtime": {
"Wabbajack.Downloaders.GameFile.dll": {} "Wabbajack.Downloaders.GameFile.dll": {}
} }
}, },
"Wabbajack.Downloaders.GoogleDrive/0.4.4": { "Wabbajack.Downloaders.GoogleDrive/0.4.5": {
"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.4.4", "Wabbajack.Common": "0.4.5",
"Wabbajack.DTOs": "0.4.4", "Wabbajack.DTOs": "0.4.5",
"Wabbajack.Downloaders.Interfaces": "0.4.4", "Wabbajack.Downloaders.Interfaces": "0.4.5",
"Wabbajack.Networking.Http": "0.4.4", "Wabbajack.Networking.Http": "0.4.5",
"Wabbajack.Networking.Http.Interfaces": "0.4.4" "Wabbajack.Networking.Http.Interfaces": "0.4.5"
}, },
"runtime": { "runtime": {
"Wabbajack.Downloaders.GoogleDrive.dll": {} "Wabbajack.Downloaders.GoogleDrive.dll": {}
} }
}, },
"Wabbajack.Downloaders.Http/0.4.4": { "Wabbajack.Downloaders.Http/0.4.5": {
"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.4.4", "Wabbajack.Common": "0.4.5",
"Wabbajack.DTOs": "0.4.4", "Wabbajack.DTOs": "0.4.5",
"Wabbajack.Downloaders.Interfaces": "0.4.4", "Wabbajack.Downloaders.Interfaces": "0.4.5",
"Wabbajack.Networking.BethesdaNet": "0.4.4", "Wabbajack.Networking.BethesdaNet": "0.4.5",
"Wabbajack.Networking.Http.Interfaces": "0.4.4", "Wabbajack.Networking.Http.Interfaces": "0.4.5",
"Wabbajack.Paths.IO": "0.4.4" "Wabbajack.Paths.IO": "0.4.5"
}, },
"runtime": { "runtime": {
"Wabbajack.Downloaders.Http.dll": {} "Wabbajack.Downloaders.Http.dll": {}
} }
}, },
"Wabbajack.Downloaders.Interfaces/0.4.4": { "Wabbajack.Downloaders.Interfaces/0.4.5": {
"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.4.4", "Wabbajack.Compression.Zip": "0.4.5",
"Wabbajack.DTOs": "0.4.4", "Wabbajack.DTOs": "0.4.5",
"Wabbajack.Paths.IO": "0.4.4" "Wabbajack.Paths.IO": "0.4.5"
}, },
"runtime": { "runtime": {
"Wabbajack.Downloaders.Interfaces.dll": {} "Wabbajack.Downloaders.Interfaces.dll": {}
} }
}, },
"Wabbajack.Downloaders.IPS4OAuth2Downloader/0.4.4": { "Wabbajack.Downloaders.IPS4OAuth2Downloader/0.4.5": {
"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.4.4", "Wabbajack.Common": "0.4.5",
"Wabbajack.Downloaders.Interfaces": "0.4.4", "Wabbajack.Downloaders.Interfaces": "0.4.5",
"Wabbajack.Networking.Http": "0.4.4", "Wabbajack.Networking.Http": "0.4.5",
"Wabbajack.Networking.Http.Interfaces": "0.4.4" "Wabbajack.Networking.Http.Interfaces": "0.4.5"
}, },
"runtime": { "runtime": {
"Wabbajack.Downloaders.IPS4OAuth2Downloader.dll": {} "Wabbajack.Downloaders.IPS4OAuth2Downloader.dll": {}
} }
}, },
"Wabbajack.Downloaders.Manual/0.4.4": { "Wabbajack.Downloaders.Manual/0.4.5": {
"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.4.4", "Wabbajack.Common": "0.4.5",
"Wabbajack.Downloaders.Interfaces": "0.4.4" "Wabbajack.Downloaders.Interfaces": "0.4.5"
}, },
"runtime": { "runtime": {
"Wabbajack.Downloaders.Manual.dll": {} "Wabbajack.Downloaders.Manual.dll": {}
} }
}, },
"Wabbajack.Downloaders.MediaFire/0.4.4": { "Wabbajack.Downloaders.MediaFire/0.4.5": {
"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.4.4", "Wabbajack.Common": "0.4.5",
"Wabbajack.Downloaders.Interfaces": "0.4.4", "Wabbajack.Downloaders.Interfaces": "0.4.5",
"Wabbajack.Networking.Http.Interfaces": "0.4.4" "Wabbajack.Networking.Http.Interfaces": "0.4.5"
}, },
"runtime": { "runtime": {
"Wabbajack.Downloaders.MediaFire.dll": {} "Wabbajack.Downloaders.MediaFire.dll": {}
} }
}, },
"Wabbajack.Downloaders.Mega/0.4.4": { "Wabbajack.Downloaders.Mega/0.4.5": {
"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.4.4", "Wabbajack.Common": "0.4.5",
"Wabbajack.Downloaders.Interfaces": "0.4.4", "Wabbajack.Downloaders.Interfaces": "0.4.5",
"Wabbajack.Paths.IO": "0.4.4" "Wabbajack.Paths.IO": "0.4.5"
}, },
"runtime": { "runtime": {
"Wabbajack.Downloaders.Mega.dll": {} "Wabbajack.Downloaders.Mega.dll": {}
} }
}, },
"Wabbajack.Downloaders.ModDB/0.4.4": { "Wabbajack.Downloaders.ModDB/0.4.5": {
"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.4.4", "Wabbajack.Common": "0.4.5",
"Wabbajack.Downloaders.Interfaces": "0.4.4", "Wabbajack.Downloaders.Interfaces": "0.4.5",
"Wabbajack.Networking.Http": "0.4.4", "Wabbajack.Networking.Http": "0.4.5",
"Wabbajack.Networking.Http.Interfaces": "0.4.4" "Wabbajack.Networking.Http.Interfaces": "0.4.5"
}, },
"runtime": { "runtime": {
"Wabbajack.Downloaders.ModDB.dll": {} "Wabbajack.Downloaders.ModDB.dll": {}
} }
}, },
"Wabbajack.Downloaders.Nexus/0.4.4": { "Wabbajack.Downloaders.Nexus/0.4.5": {
"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.4.4", "Wabbajack.DTOs": "0.4.5",
"Wabbajack.Downloaders.Interfaces": "0.4.4", "Wabbajack.Downloaders.Interfaces": "0.4.5",
"Wabbajack.Hashing.xxHash64": "0.4.4", "Wabbajack.Hashing.xxHash64": "0.4.5",
"Wabbajack.Networking.Http": "0.4.4", "Wabbajack.Networking.Http": "0.4.5",
"Wabbajack.Networking.Http.Interfaces": "0.4.4", "Wabbajack.Networking.Http.Interfaces": "0.4.5",
"Wabbajack.Networking.NexusApi": "0.4.4", "Wabbajack.Networking.NexusApi": "0.4.5",
"Wabbajack.Paths": "0.4.4" "Wabbajack.Paths": "0.4.5"
}, },
"runtime": { "runtime": {
"Wabbajack.Downloaders.Nexus.dll": {} "Wabbajack.Downloaders.Nexus.dll": {}
} }
}, },
"Wabbajack.Downloaders.VerificationCache/0.4.4": { "Wabbajack.Downloaders.VerificationCache/0.4.5": {
"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.4.4", "Wabbajack.DTOs": "0.4.5",
"Wabbajack.Paths.IO": "0.4.4" "Wabbajack.Paths.IO": "0.4.5"
}, },
"runtime": { "runtime": {
"Wabbajack.Downloaders.VerificationCache.dll": {} "Wabbajack.Downloaders.VerificationCache.dll": {}
} }
}, },
"Wabbajack.Downloaders.WabbajackCDN/0.4.4": { "Wabbajack.Downloaders.WabbajackCDN/0.4.5": {
"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.4.4", "Wabbajack.Common": "0.4.5",
"Wabbajack.Downloaders.Interfaces": "0.4.4", "Wabbajack.Downloaders.Interfaces": "0.4.5",
"Wabbajack.Networking.Http": "0.4.4", "Wabbajack.Networking.Http": "0.4.5",
"Wabbajack.RateLimiter": "0.4.4" "Wabbajack.RateLimiter": "0.4.5"
}, },
"runtime": { "runtime": {
"Wabbajack.Downloaders.WabbajackCDN.dll": {} "Wabbajack.Downloaders.WabbajackCDN.dll": {}
} }
}, },
"Wabbajack.DTOs/0.4.4": { "Wabbajack.DTOs/0.4.5": {
"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.4.4", "Wabbajack.Hashing.xxHash64": "0.4.5",
"Wabbajack.Paths": "0.4.4" "Wabbajack.Paths": "0.4.5"
}, },
"runtime": { "runtime": {
"Wabbajack.DTOs.dll": {} "Wabbajack.DTOs.dll": {}
} }
}, },
"Wabbajack.FileExtractor/0.4.4": { "Wabbajack.FileExtractor/0.4.5": {
"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.4.4", "Wabbajack.Common": "0.4.5",
"Wabbajack.Compression.BSA": "0.4.4", "Wabbajack.Compression.BSA": "0.4.5",
"Wabbajack.Hashing.PHash": "0.4.4", "Wabbajack.Hashing.PHash": "0.4.5",
"Wabbajack.Paths": "0.4.4" "Wabbajack.Paths": "0.4.5"
}, },
"runtime": { "runtime": {
"Wabbajack.FileExtractor.dll": {} "Wabbajack.FileExtractor.dll": {}
} }
}, },
"Wabbajack.Hashing.PHash/0.4.4": { "Wabbajack.Hashing.PHash/0.4.5": {
"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.4.4", "Wabbajack.Common": "0.4.5",
"Wabbajack.DTOs": "0.4.4", "Wabbajack.DTOs": "0.4.5",
"Wabbajack.Paths": "0.4.4", "Wabbajack.Paths": "0.4.5",
"Wabbajack.Paths.IO": "0.4.4" "Wabbajack.Paths.IO": "0.4.5"
}, },
"runtime": { "runtime": {
"Wabbajack.Hashing.PHash.dll": {} "Wabbajack.Hashing.PHash.dll": {}
} }
}, },
"Wabbajack.Hashing.xxHash64/0.4.4": { "Wabbajack.Hashing.xxHash64/0.4.5": {
"dependencies": { "dependencies": {
"Wabbajack.Paths": "0.4.4", "Wabbajack.Paths": "0.4.5",
"Wabbajack.RateLimiter": "0.4.4" "Wabbajack.RateLimiter": "0.4.5"
}, },
"runtime": { "runtime": {
"Wabbajack.Hashing.xxHash64.dll": {} "Wabbajack.Hashing.xxHash64.dll": {}
} }
}, },
"Wabbajack.Installer/0.4.4": { "Wabbajack.Installer/0.4.5": {
"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.4.4", "Wabbajack.DTOs": "0.4.5",
"Wabbajack.Downloaders.Dispatcher": "0.4.4", "Wabbajack.Downloaders.Dispatcher": "0.4.5",
"Wabbajack.Downloaders.GameFile": "0.4.4", "Wabbajack.Downloaders.GameFile": "0.4.5",
"Wabbajack.FileExtractor": "0.4.4", "Wabbajack.FileExtractor": "0.4.5",
"Wabbajack.Networking.WabbajackClientApi": "0.4.4", "Wabbajack.Networking.WabbajackClientApi": "0.4.5",
"Wabbajack.Paths": "0.4.4", "Wabbajack.Paths": "0.4.5",
"Wabbajack.Paths.IO": "0.4.4", "Wabbajack.Paths.IO": "0.4.5",
"Wabbajack.VFS": "0.4.4", "Wabbajack.VFS": "0.4.5",
"ini-parser-netstandard": "2.5.2" "ini-parser-netstandard": "2.5.2"
}, },
"runtime": { "runtime": {
"Wabbajack.Installer.dll": {} "Wabbajack.Installer.dll": {}
} }
}, },
"Wabbajack.IO.Async/0.4.4": { "Wabbajack.IO.Async/0.4.5": {
"runtime": { "runtime": {
"Wabbajack.IO.Async.dll": {} "Wabbajack.IO.Async.dll": {}
} }
}, },
"Wabbajack.Networking.BethesdaNet/0.4.4": { "Wabbajack.Networking.BethesdaNet/0.4.5": {
"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.4.4", "Wabbajack.DTOs": "0.4.5",
"Wabbajack.Networking.Http": "0.4.4", "Wabbajack.Networking.Http": "0.4.5",
"Wabbajack.Networking.Http.Interfaces": "0.4.4" "Wabbajack.Networking.Http.Interfaces": "0.4.5"
}, },
"runtime": { "runtime": {
"Wabbajack.Networking.BethesdaNet.dll": {} "Wabbajack.Networking.BethesdaNet.dll": {}
} }
}, },
"Wabbajack.Networking.Discord/0.4.4": { "Wabbajack.Networking.Discord/0.4.5": {
"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.4.4" "Wabbajack.Networking.Http.Interfaces": "0.4.5"
}, },
"runtime": { "runtime": {
"Wabbajack.Networking.Discord.dll": {} "Wabbajack.Networking.Discord.dll": {}
} }
}, },
"Wabbajack.Networking.GitHub/0.4.4": { "Wabbajack.Networking.GitHub/0.4.5": {
"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.4.4", "Wabbajack.DTOs": "0.4.5",
"Wabbajack.Networking.Http.Interfaces": "0.4.4" "Wabbajack.Networking.Http.Interfaces": "0.4.5"
}, },
"runtime": { "runtime": {
"Wabbajack.Networking.GitHub.dll": {} "Wabbajack.Networking.GitHub.dll": {}
} }
}, },
"Wabbajack.Networking.Http/0.4.4": { "Wabbajack.Networking.Http/0.4.5": {
"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.4.4", "Wabbajack.Configuration": "0.4.5",
"Wabbajack.Downloaders.Interfaces": "0.4.4", "Wabbajack.Downloaders.Interfaces": "0.4.5",
"Wabbajack.Hashing.xxHash64": "0.4.4", "Wabbajack.Hashing.xxHash64": "0.4.5",
"Wabbajack.Networking.Http.Interfaces": "0.4.4", "Wabbajack.Networking.Http.Interfaces": "0.4.5",
"Wabbajack.Paths": "0.4.4", "Wabbajack.Paths": "0.4.5",
"Wabbajack.Paths.IO": "0.4.4" "Wabbajack.Paths.IO": "0.4.5"
}, },
"runtime": { "runtime": {
"Wabbajack.Networking.Http.dll": {} "Wabbajack.Networking.Http.dll": {}
} }
}, },
"Wabbajack.Networking.Http.Interfaces/0.4.4": { "Wabbajack.Networking.Http.Interfaces/0.4.5": {
"dependencies": { "dependencies": {
"Wabbajack.Hashing.xxHash64": "0.4.4" "Wabbajack.Hashing.xxHash64": "0.4.5"
}, },
"runtime": { "runtime": {
"Wabbajack.Networking.Http.Interfaces.dll": {} "Wabbajack.Networking.Http.Interfaces.dll": {}
} }
}, },
"Wabbajack.Networking.NexusApi/0.4.4": { "Wabbajack.Networking.NexusApi/0.4.5": {
"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.4.4", "Wabbajack.DTOs": "0.4.5",
"Wabbajack.Networking.Http": "0.4.4", "Wabbajack.Networking.Http": "0.4.5",
"Wabbajack.Networking.Http.Interfaces": "0.4.4", "Wabbajack.Networking.Http.Interfaces": "0.4.5",
"Wabbajack.Networking.WabbajackClientApi": "0.4.4" "Wabbajack.Networking.WabbajackClientApi": "0.4.5"
}, },
"runtime": { "runtime": {
"Wabbajack.Networking.NexusApi.dll": {} "Wabbajack.Networking.NexusApi.dll": {}
} }
}, },
"Wabbajack.Networking.WabbajackClientApi/0.4.4": { "Wabbajack.Networking.WabbajackClientApi/0.4.5": {
"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.4.4", "Wabbajack.Common": "0.4.5",
"Wabbajack.DTOs": "0.4.4", "Wabbajack.DTOs": "0.4.5",
"Wabbajack.Paths.IO": "0.4.4", "Wabbajack.Paths.IO": "0.4.5",
"Wabbajack.VFS.Interfaces": "0.4.4", "Wabbajack.VFS.Interfaces": "0.4.5",
"YamlDotNet": "16.3.0" "YamlDotNet": "16.3.0"
}, },
"runtime": { "runtime": {
"Wabbajack.Networking.WabbajackClientApi.dll": {} "Wabbajack.Networking.WabbajackClientApi.dll": {}
} }
}, },
"Wabbajack.Paths/0.4.4": { "Wabbajack.Paths/0.4.5": {
"runtime": { "runtime": {
"Wabbajack.Paths.dll": {} "Wabbajack.Paths.dll": {}
} }
}, },
"Wabbajack.Paths.IO/0.4.4": { "Wabbajack.Paths.IO/0.4.5": {
"dependencies": { "dependencies": {
"Wabbajack.Paths": "0.4.4", "Wabbajack.Paths": "0.4.5",
"shortid": "4.0.0" "shortid": "4.0.0"
}, },
"runtime": { "runtime": {
"Wabbajack.Paths.IO.dll": {} "Wabbajack.Paths.IO.dll": {}
} }
}, },
"Wabbajack.RateLimiter/0.4.4": { "Wabbajack.RateLimiter/0.4.5": {
"runtime": { "runtime": {
"Wabbajack.RateLimiter.dll": {} "Wabbajack.RateLimiter.dll": {}
} }
}, },
"Wabbajack.Server.Lib/0.4.4": { "Wabbajack.Server.Lib/0.4.5": {
"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.4.4", "Wabbajack.Common": "0.4.5",
"Wabbajack.Networking.Http.Interfaces": "0.4.4", "Wabbajack.Networking.Http.Interfaces": "0.4.5",
"Wabbajack.Services.OSIntegrated": "0.4.4" "Wabbajack.Services.OSIntegrated": "0.4.5"
}, },
"runtime": { "runtime": {
"Wabbajack.Server.Lib.dll": {} "Wabbajack.Server.Lib.dll": {}
} }
}, },
"Wabbajack.Services.OSIntegrated/0.4.4": { "Wabbajack.Services.OSIntegrated/0.4.5": {
"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.4.4", "Wabbajack.Compiler": "0.4.5",
"Wabbajack.Downloaders.Dispatcher": "0.4.4", "Wabbajack.Downloaders.Dispatcher": "0.4.5",
"Wabbajack.Installer": "0.4.4", "Wabbajack.Installer": "0.4.5",
"Wabbajack.Networking.BethesdaNet": "0.4.4", "Wabbajack.Networking.BethesdaNet": "0.4.5",
"Wabbajack.Networking.Discord": "0.4.4", "Wabbajack.Networking.Discord": "0.4.5",
"Wabbajack.VFS": "0.4.4" "Wabbajack.VFS": "0.4.5"
}, },
"runtime": { "runtime": {
"Wabbajack.Services.OSIntegrated.dll": {} "Wabbajack.Services.OSIntegrated.dll": {}
} }
}, },
"Wabbajack.VFS/0.4.4": { "Wabbajack.VFS/0.4.5": {
"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.4.4", "Wabbajack.Common": "0.4.5",
"Wabbajack.FileExtractor": "0.4.4", "Wabbajack.FileExtractor": "0.4.5",
"Wabbajack.Hashing.PHash": "0.4.4", "Wabbajack.Hashing.PHash": "0.4.5",
"Wabbajack.Hashing.xxHash64": "0.4.4", "Wabbajack.Hashing.xxHash64": "0.4.5",
"Wabbajack.Paths": "0.4.4", "Wabbajack.Paths": "0.4.5",
"Wabbajack.Paths.IO": "0.4.4", "Wabbajack.Paths.IO": "0.4.5",
"Wabbajack.VFS.Interfaces": "0.4.4" "Wabbajack.VFS.Interfaces": "0.4.5"
}, },
"runtime": { "runtime": {
"Wabbajack.VFS.dll": {} "Wabbajack.VFS.dll": {}
} }
}, },
"Wabbajack.VFS.Interfaces/0.4.4": { "Wabbajack.VFS.Interfaces/0.4.5": {
"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.4.4", "Wabbajack.DTOs": "0.4.5",
"Wabbajack.Hashing.xxHash64": "0.4.4", "Wabbajack.Hashing.xxHash64": "0.4.5",
"Wabbajack.Paths": "0.4.4" "Wabbajack.Paths": "0.4.5"
}, },
"runtime": { "runtime": {
"Wabbajack.VFS.Interfaces.dll": {} "Wabbajack.VFS.Interfaces.dll": {}
@@ -2332,7 +2332,7 @@
} }
}, },
"libraries": { "libraries": {
"jackify-engine/0.4.4": { "jackify-engine/0.4.5": {
"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.4.4": { "Wabbajack.CLI.Builder/0.4.5": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Common/0.4.4": { "Wabbajack.Common/0.4.5": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Compiler/0.4.4": { "Wabbajack.Compiler/0.4.5": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Compression.BSA/0.4.4": { "Wabbajack.Compression.BSA/0.4.5": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Compression.Zip/0.4.4": { "Wabbajack.Compression.Zip/0.4.5": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Configuration/0.4.4": { "Wabbajack.Configuration/0.4.5": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Downloaders.Bethesda/0.4.4": { "Wabbajack.Downloaders.Bethesda/0.4.5": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Downloaders.Dispatcher/0.4.4": { "Wabbajack.Downloaders.Dispatcher/0.4.5": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Downloaders.GameFile/0.4.4": { "Wabbajack.Downloaders.GameFile/0.4.5": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Downloaders.GoogleDrive/0.4.4": { "Wabbajack.Downloaders.GoogleDrive/0.4.5": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Downloaders.Http/0.4.4": { "Wabbajack.Downloaders.Http/0.4.5": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Downloaders.Interfaces/0.4.4": { "Wabbajack.Downloaders.Interfaces/0.4.5": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Downloaders.IPS4OAuth2Downloader/0.4.4": { "Wabbajack.Downloaders.IPS4OAuth2Downloader/0.4.5": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Downloaders.Manual/0.4.4": { "Wabbajack.Downloaders.Manual/0.4.5": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Downloaders.MediaFire/0.4.4": { "Wabbajack.Downloaders.MediaFire/0.4.5": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Downloaders.Mega/0.4.4": { "Wabbajack.Downloaders.Mega/0.4.5": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Downloaders.ModDB/0.4.4": { "Wabbajack.Downloaders.ModDB/0.4.5": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Downloaders.Nexus/0.4.4": { "Wabbajack.Downloaders.Nexus/0.4.5": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Downloaders.VerificationCache/0.4.4": { "Wabbajack.Downloaders.VerificationCache/0.4.5": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Downloaders.WabbajackCDN/0.4.4": { "Wabbajack.Downloaders.WabbajackCDN/0.4.5": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.DTOs/0.4.4": { "Wabbajack.DTOs/0.4.5": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.FileExtractor/0.4.4": { "Wabbajack.FileExtractor/0.4.5": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Hashing.PHash/0.4.4": { "Wabbajack.Hashing.PHash/0.4.5": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Hashing.xxHash64/0.4.4": { "Wabbajack.Hashing.xxHash64/0.4.5": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Installer/0.4.4": { "Wabbajack.Installer/0.4.5": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.IO.Async/0.4.4": { "Wabbajack.IO.Async/0.4.5": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Networking.BethesdaNet/0.4.4": { "Wabbajack.Networking.BethesdaNet/0.4.5": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Networking.Discord/0.4.4": { "Wabbajack.Networking.Discord/0.4.5": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Networking.GitHub/0.4.4": { "Wabbajack.Networking.GitHub/0.4.5": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Networking.Http/0.4.4": { "Wabbajack.Networking.Http/0.4.5": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Networking.Http.Interfaces/0.4.4": { "Wabbajack.Networking.Http.Interfaces/0.4.5": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Networking.NexusApi/0.4.4": { "Wabbajack.Networking.NexusApi/0.4.5": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Networking.WabbajackClientApi/0.4.4": { "Wabbajack.Networking.WabbajackClientApi/0.4.5": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Paths/0.4.4": { "Wabbajack.Paths/0.4.5": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Paths.IO/0.4.4": { "Wabbajack.Paths.IO/0.4.5": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.RateLimiter/0.4.4": { "Wabbajack.RateLimiter/0.4.5": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Server.Lib/0.4.4": { "Wabbajack.Server.Lib/0.4.5": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Services.OSIntegrated/0.4.4": { "Wabbajack.Services.OSIntegrated/0.4.5": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.VFS/0.4.4": { "Wabbajack.VFS/0.4.5": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.VFS.Interfaces/0.4.4": { "Wabbajack.VFS.Interfaces/0.4.5": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""

Binary file not shown.

View File

@@ -1891,7 +1891,8 @@ def main():
# Initialize file logging on root logger so all modules inherit it # Initialize file logging on root logger so all modules inherit it
from jackify.shared.logging import LoggingHandler from jackify.shared.logging import LoggingHandler
logging_handler = LoggingHandler() logging_handler = LoggingHandler()
# Rotate log file before setting up new logger # Only rotate log file when debug mode is enabled
if debug_mode:
logging_handler.rotate_log_for_logger('jackify_gui', 'jackify-gui.log') logging_handler.rotate_log_for_logger('jackify_gui', 'jackify-gui.log')
root_logger = logging_handler.setup_logger('', 'jackify-gui.log', is_general=True) # Empty name = root logger root_logger = logging_handler.setup_logger('', 'jackify-gui.log', is_general=True) # Empty name = root logger

View File

@@ -639,6 +639,8 @@ class ConfigureExistingModlistScreen(QWidget):
# Start time tracking # Start time tracking
self._workflow_start_time = time.time() self._workflow_start_time = time.time()
from jackify import __version__ as jackify_version
self._safe_append_text(f"Jackify v{jackify_version}")
self._safe_append_text("[Jackify] Starting post-install configuration...") self._safe_append_text("[Jackify] Starting post-install configuration...")
# Create configuration thread using backend service # Create configuration thread using backend service

View File

@@ -921,6 +921,8 @@ class ConfigureNewModlistScreen(QWidget):
self._safe_append_text("Steam restarted successfully.") self._safe_append_text("Steam restarted successfully.")
# Start configuration immediately - the CLI will handle any manual steps # Start configuration immediately - the CLI will handle any manual steps
from jackify import __version__ as jackify_version
self._safe_append_text(f"Jackify v{jackify_version}")
self._safe_append_text("Starting modlist configuration...") self._safe_append_text("Starting modlist configuration...")
self.configure_modlist() self.configure_modlist()
else: else:
@@ -950,6 +952,8 @@ class ConfigureNewModlistScreen(QWidget):
def _start_automated_prefix_workflow(self, modlist_name, install_dir, mo2_exe_path, resolution): def _start_automated_prefix_workflow(self, modlist_name, install_dir, mo2_exe_path, resolution):
"""Start the automated prefix workflow using AutomatedPrefixService in a background thread""" """Start the automated prefix workflow using AutomatedPrefixService in a background thread"""
from jackify import __version__ as jackify_version
self._safe_append_text(f"Jackify v{jackify_version}")
self._safe_append_text(f"Initializing automated Steam setup for '{modlist_name}'...") self._safe_append_text(f"Initializing automated Steam setup for '{modlist_name}'...")
self._safe_append_text("Starting automated Steam shortcut creation and configuration...") self._safe_append_text("Starting automated Steam shortcut creation and configuration...")

View File

@@ -1869,7 +1869,7 @@ class InstallModlistScreen(QWidget):
downloads_dir = self.downloads_dir_edit.text().strip() downloads_dir = self.downloads_dir_edit.text().strip()
# Get authentication token (OAuth or API key) with automatic refresh # Get authentication token (OAuth or API key) with automatic refresh
api_key = self.auth_service.ensure_valid_auth() api_key, oauth_info = self.auth_service.get_auth_for_engine()
if not api_key: if not api_key:
self._abort_with_message( self._abort_with_message(
"warning", "warning",
@@ -2097,7 +2097,7 @@ class InstallModlistScreen(QWidget):
return return
debug_print(f'DEBUG: Calling run_modlist_installer with modlist={modlist}, install_dir={install_dir}, downloads_dir={downloads_dir}, install_mode={install_mode}') debug_print(f'DEBUG: Calling run_modlist_installer with modlist={modlist}, install_dir={install_dir}, downloads_dir={downloads_dir}, install_mode={install_mode}')
self.run_modlist_installer(modlist, install_dir, downloads_dir, api_key, install_mode) self.run_modlist_installer(modlist, install_dir, downloads_dir, api_key, install_mode, oauth_info)
except Exception as e: except Exception as e:
debug_print(f"DEBUG: Exception in validate_and_start_install: {e}") debug_print(f"DEBUG: Exception in validate_and_start_install: {e}")
import traceback import traceback
@@ -2108,7 +2108,7 @@ class InstallModlistScreen(QWidget):
self.cancel_install_btn.setVisible(False) self.cancel_install_btn.setVisible(False)
debug_print(f"DEBUG: Controls re-enabled in exception handler") debug_print(f"DEBUG: Controls re-enabled in exception handler")
def run_modlist_installer(self, modlist, install_dir, downloads_dir, api_key, install_mode='online'): def run_modlist_installer(self, modlist, install_dir, downloads_dir, api_key, install_mode='online', oauth_info=None):
debug_print('DEBUG: run_modlist_installer called - USING THREADED BACKEND WRAPPER') debug_print('DEBUG: run_modlist_installer called - USING THREADED BACKEND WRAPPER')
# Rotate log file at start of each workflow run (keep 5 backups) # Rotate log file at start of each workflow run (keep 5 backups)
@@ -2119,6 +2119,8 @@ class InstallModlistScreen(QWidget):
# Clear console for fresh installation output # Clear console for fresh installation output
self.console.clear() self.console.clear()
from jackify import __version__ as jackify_version
self._safe_append_text(f"Jackify v{jackify_version}")
self._safe_append_text("Starting modlist installation with custom progress handling...") self._safe_append_text("Starting modlist installation with custom progress handling...")
# Update UI state for installation # Update UI state for installation
@@ -2136,7 +2138,7 @@ class InstallModlistScreen(QWidget):
installation_finished = Signal(bool, str) installation_finished = Signal(bool, str)
premium_required_detected = Signal(str) premium_required_detected = Signal(str)
def __init__(self, modlist, install_dir, downloads_dir, api_key, modlist_name, install_mode='online', progress_state_manager=None): def __init__(self, modlist, install_dir, downloads_dir, api_key, modlist_name, install_mode='online', progress_state_manager=None, auth_service=None, oauth_info=None):
super().__init__() super().__init__()
self.modlist = modlist self.modlist = modlist
self.install_dir = install_dir self.install_dir = install_dir
@@ -2148,6 +2150,8 @@ class InstallModlistScreen(QWidget):
self.process_manager = None self.process_manager = None
# R&D: Progress state manager for parsing # R&D: Progress state manager for parsing
self.progress_state_manager = progress_state_manager self.progress_state_manager = progress_state_manager
self.auth_service = auth_service
self.oauth_info = oauth_info
self._premium_signal_sent = False self._premium_signal_sent = False
# Rolling buffer for Premium detection diagnostics # Rolling buffer for Premium detection diagnostics
self._engine_output_buffer = [] self._engine_output_buffer = []
@@ -2196,7 +2200,10 @@ class InstallModlistScreen(QWidget):
# Use clean subprocess environment to prevent AppImage variable inheritance # Use clean subprocess environment to prevent AppImage variable inheritance
from jackify.backend.handlers.subprocess_utils import get_clean_subprocess_env from jackify.backend.handlers.subprocess_utils import get_clean_subprocess_env
env = get_clean_subprocess_env({'NEXUS_API_KEY': self.api_key}) env_vars = {'NEXUS_API_KEY': self.api_key}
if self.oauth_info:
env_vars['NEXUS_OAUTH_INFO'] = self.oauth_info
env = get_clean_subprocess_env(env_vars)
self.process_manager = ProcessManager(cmd, env=env, text=False) self.process_manager = ProcessManager(cmd, env=env, text=False)
ansi_escape = re.compile(rb'\x1b\[[0-9;?]*[ -/]*[@-~]') ansi_escape = re.compile(rb'\x1b\[[0-9;?]*[ -/]*[@-~]')
buffer = b'' buffer = b''
@@ -2451,7 +2458,9 @@ class InstallModlistScreen(QWidget):
# After the InstallationThread class definition, add: # After the InstallationThread class definition, add:
self.install_thread = InstallationThread( self.install_thread = InstallationThread(
modlist, install_dir, downloads_dir, api_key, self.modlist_name_edit.text().strip(), install_mode, modlist, install_dir, downloads_dir, api_key, self.modlist_name_edit.text().strip(), install_mode,
progress_state_manager=self.progress_state_manager # R&D: Pass progress state manager progress_state_manager=self.progress_state_manager, # R&D: Pass progress state manager
auth_service=self.auth_service, # Fix Issue #127: Pass auth_service for Premium detection diagnostics
oauth_info=oauth_info # Pass OAuth state for auto-refresh
) )
self.install_thread.output_received.connect(self.on_installation_output) self.install_thread.output_received.connect(self.on_installation_output)
self.install_thread.progress_received.connect(self.on_installation_progress) self.install_thread.progress_received.connect(self.on_installation_progress)