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
## 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
**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.
"""
__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
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
logging.info("User selected auto-detect, using GE-Proton → Experimental → Proton precedence")
return WineUtils.select_best_proton()
@@ -502,10 +502,11 @@ class ModlistInstallCLI:
print("\n" + "-" * 28)
print(f"{COLOR_INFO}Nexus Authentication: Using API Key (Legacy){COLOR_RESET}")
# Get valid token/key
api_key = auth_service.ensure_valid_auth()
# Get valid token/key and OAuth state for engine auto-refresh
api_key, oauth_info = auth_service.get_auth_for_engine()
if api_key:
self.context['nexus_api_key'] = api_key
self.context['nexus_oauth_info'] = oauth_info # For engine auto-refresh
else:
# Auth expired or invalid - prompt to set up
print(f"\n{COLOR_WARNING}Your authentication has expired or is invalid.{COLOR_RESET}")
@@ -538,9 +539,10 @@ class ModlistInstallCLI:
if username:
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:
self.context['nexus_api_key'] = api_key
self.context['nexus_oauth_info'] = oauth_info # For engine auto-refresh
else:
print(f"{COLOR_ERROR}Failed to retrieve auth token after authorization.{COLOR_RESET}")
return None
@@ -738,6 +740,7 @@ class ModlistInstallCLI:
modlist_arg = self.context.get('modlist_value') or self.context.get('machineid')
machineid = self.context.get('machineid')
api_key = self.context.get('nexus_api_key')
oauth_info = self.context.get('nexus_oauth_info')
# Path to the engine binary
engine_path = get_jackify_engine_path()
@@ -779,24 +782,37 @@ class ModlistInstallCLI:
# Store original environment values to restore later
original_env_values = {
'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')
}
try:
# Temporarily modify current process's environment
if api_key:
# 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:
os.environ['NEXUS_API_KEY'] = api_key
elif api_key:
# No OAuth info, use API key only (no auto-refresh support)
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 'NEXUS_API_KEY' in os.environ: # api_key is None/empty, but a system key might exist
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.")
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.
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']
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"
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.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])
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.
"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
"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"
"window_width": None, # Saved window width (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.
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:
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}")
return proton_path
except Exception as e:
logger.error(f"Error retrieving install proton_path: {e}")
return "auto"
return None
def get_game_proton_path(self):
"""
@@ -775,7 +781,7 @@ class ConfigHandler:
Always reads fresh from disk.
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:
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 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}")
return game_proton_path
except Exception as e:
@@ -821,15 +831,20 @@ class ConfigHandler:
logger.info(f"Auto-detected Proton: {best_proton['name']} ({proton_type})")
self.save_config()
else:
# Fallback to auto-detect mode
self.settings["proton_path"] = "auto"
self.settings["proton_version"] = "auto"
logger.info("No compatible Proton versions found, using auto-detect mode")
# Set proton_path to None (will appear as null in JSON) so jackify-engine doesn't get invalid path
# Code will auto-detect on each run when proton_path is None
self.settings["proton_path"] = None
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()
except Exception as e:
logger.error(f"Failed to auto-detect Proton: {e}")
self.settings["proton_path"] = "auto"
self.settings["proton_version"] = "auto"
# Set proton_path to None (will appear as null in JSON)
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.
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.propagate = False
self.logger.propagate = True
self.steamdeck = steamdeck
# DEBUG: Log ModlistHandler instantiation details for SD card path debugging
@@ -746,15 +746,20 @@ class ModlistHandler:
try:
registry_success = self._apply_universal_dotnet_fixes()
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
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("WARNING: Universal dotnet4.x registry fixes FAILED!")
self.logger.error("This modlist may experience .NET Framework compatibility issues.")
self.logger.error(failure_msg)
self.logger.error("Consider manually setting mscoree=native in winecfg if problems occur.")
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
# Step 4.6: Enable dotfiles visibility for Wine prefix
@@ -1596,20 +1601,21 @@ class ModlistHandler:
except Exception as 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
self.logger.debug("Setting mscoree=native DLL override...")
self.logger.debug("Setting *mscoree=native DLL override...")
cmd1 = [
wine_binary, 'reg', 'add',
'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)
self.logger.info(f"*mscoree registry command result: returncode={result1.returncode}, stdout={result1.stdout[:200]}, stderr={result1.stderr[:200]}")
if result1.returncode == 0:
self.logger.info("Successfully applied mscoree=native DLL override")
self.logger.info("Successfully applied *mscoree=native DLL override")
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
# 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)
self.logger.info(f"OnlyUseLatestCLR registry command result: returncode={result2.returncode}, stdout={result2.stdout[:200]}, stderr={result2.stderr[:200]}")
if result2.returncode == 0:
self.logger.info("Successfully applied OnlyUseLatestCLR=1 registry entry")
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
if wineserver_binary:
@@ -1639,17 +1646,17 @@ class ModlistHandler:
self.logger.info("Verifying registry entries were applied and persisted...")
verification_passed = True
# Verify mscoree=native
# Verify *mscoree=native
verify_cmd1 = [
wine_binary, 'reg', 'query',
'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)
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:
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
# Verify OnlyUseLatestCLR=1
@@ -1696,10 +1703,17 @@ class ModlistHandler:
]
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}")
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}")
# 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}")
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(f"{COLOR_INFO}Nexus Authentication: Using API Key (Legacy){COLOR_RESET}")
# Get valid token/key
api_key = auth_service.ensure_valid_auth()
# Get valid token/key and OAuth state for engine auto-refresh
api_key, oauth_info = auth_service.get_auth_for_engine()
if api_key:
self.context['nexus_api_key'] = api_key
self.context['nexus_oauth_info'] = oauth_info # For engine auto-refresh
else:
# Auth expired or invalid - prompt to set up
print(f"\n{COLOR_WARNING}Your authentication has expired or is invalid.{COLOR_RESET}")
@@ -463,9 +464,10 @@ class ModlistInstallCLI:
if username:
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:
self.context['nexus_api_key'] = api_key
self.context['nexus_oauth_info'] = oauth_info # For engine auto-refresh
else:
print(f"{COLOR_ERROR}Failed to retrieve auth token after authorization.{COLOR_RESET}")
return None
@@ -616,6 +618,7 @@ class ModlistInstallCLI:
modlist_arg = self.context.get('modlist_value') or self.context.get('machineid')
machineid = self.context.get('machineid')
api_key = self.context['nexus_api_key']
oauth_info = self.context.get('nexus_oauth_info')
# Path to the engine binary
engine_path = get_jackify_engine_path()
@@ -675,24 +678,37 @@ class ModlistInstallCLI:
# Store original environment values to restore later
original_env_values = {
'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')
}
try:
# Temporarily modify current process's environment
if api_key:
# 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:
os.environ['NEXUS_API_KEY'] = api_key
elif api_key:
# No OAuth info, use API key only (no auto-refresh support)
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 'NEXUS_API_KEY' in os.environ: # api_key is None/empty, but a system key might exist
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.")
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.
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']
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"
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.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])
print(f"{COLOR_INFO}Launching Jackify Install Engine with command:{COLOR_RESET} {pretty_cmd}")

View File

@@ -596,19 +596,29 @@ class ProtontricksHandler:
try:
if user_reg_path.exists():
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}")
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')
dotfiles_set_success = True # Count file write as success too
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
else:
logger.warning(f"user.reg not found at {user_reg_path}, creating it.")
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')
dotfiles_set_success = True # Creating file counts as success
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.
Optionally merges in extra_env dict.
Also ensures bundled tools (lz4, unzip, etc.) are in PATH when running as AppImage.
CRITICAL: Preserves system PATH to ensure system tools (like lz4) are available.
Also ensures bundled tools (lz4, cabextract, winetricks) are in PATH when running as AppImage.
CRITICAL: Preserves system PATH to ensure system utilities (wget, curl, unzip, xz, gzip, sha256sum) are available.
"""
from pathlib import Path
@@ -73,7 +73,8 @@ def get_clean_subprocess_env(extra_env=None):
path_parts.append(sys_path)
# 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
tools_dir = None
@@ -100,23 +101,23 @@ def get_clean_subprocess_env(extra_env=None):
tools_dir = str(possible_dir)
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 = []
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)
# Note: AppRun already sets PATH with tools directory, but we ensure it's first
# Add all other paths first (system utilities take precedence)
seen = set()
if tools_dir:
seen.add(tools_dir) # Already added, don't add again
for path_part in path_parts:
if path_part and path_part not in seen:
final_path_parts.append(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)
# 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
wine_binary = None
if user_proton_path != 'auto':
if user_proton_path and user_proton_path != 'auto':
# Check if user-selected Proton still exists
if os.path.exists(user_proton_path):
# Resolve symlinks to handle ~/.steam/steam -> ~/.local/share/Steam
@@ -582,7 +582,7 @@ class WinetricksHandler:
user_proton_path = config.get_proton_path()
wine_binary = None
if user_proton_path != 'auto':
if user_proton_path and user_proton_path != 'auto':
if os.path.exists(user_proton_path):
resolved_proton_path = os.path.realpath(user_proton_path)
valve_proton_wine = os.path.join(resolved_proton_path, 'dist', 'bin', 'wine')
@@ -594,8 +594,8 @@ class WinetricksHandler:
wine_binary = ge_proton_wine
if not wine_binary:
if user_proton_path == 'auto':
self.logger.info("Auto-detecting Proton (user selected 'auto')")
if not user_proton_path or user_proton_path == 'auto':
self.logger.info("Auto-detecting Proton (user selected 'auto' or path not set)")
best_proton = WineUtils.select_best_proton()
if best_proton:
wine_binary = WineUtils.find_proton_binary(best_proton['name'])
@@ -811,7 +811,7 @@ class WinetricksHandler:
# If user selected a specific Proton, try that first
wine_binary = None
if user_proton_path != 'auto':
if user_proton_path and user_proton_path != 'auto':
if os.path.exists(user_proton_path):
resolved_proton_path = os.path.realpath(user_proton_path)
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):
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 user_proton_path == 'auto':
self.logger.info("Auto-detecting Proton (user selected 'auto')")
if not user_proton_path or user_proton_path == 'auto':
self.logger.info("Auto-detecting Proton (user selected 'auto' or path not set)")
best_proton = WineUtils.select_best_proton()
if best_proton:
wine_binary = WineUtils.find_proton_binary(best_proton['name'])

View File

@@ -71,7 +71,7 @@ class AutomatedPrefixService:
config_handler = ConfigHandler()
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
logger.info("User selected auto-detect, using GE-Proton → Experimental → Proton precedence")
return WineUtils.select_best_proton()
@@ -3095,20 +3095,20 @@ echo Prefix creation complete.
env['WINEPREFIX'] = prefix_path
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
logger.debug("Setting mscoree=native DLL override...")
logger.debug("Setting *mscoree=native DLL override...")
cmd1 = [
wine_binary, 'reg', 'add',
'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')
if result1.returncode == 0:
logger.info("Successfully applied mscoree=native DLL override")
logger.info("Successfully applied *mscoree=native DLL override")
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
# 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]:
"""Find the appropriate Wine binary for registry operations"""
try:
# Method 1: Try to detect from Steam's config or use Proton from compat data
# Look for wine binary in common Proton locations
proton_paths = [
os.path.expanduser("~/.local/share/Steam/compatibilitytools.d"),
os.path.expanduser("~/.steam/steam/steamapps/common")
]
from ..handlers.config_handler import ConfigHandler
from ..handlers.wine_utils import WineUtils
# Method 1: Use the user's configured Proton version from settings
config_handler = ConfigHandler()
user_proton_path = config_handler.get_game_proton_path()
for base_path in proton_paths:
if os.path.exists(base_path):
for item in os.listdir(base_path):
if 'proton' in item.lower():
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
if user_proton_path and user_proton_path != 'auto':
# User has selected a specific Proton version
proton_path = Path(user_proton_path).expanduser()
# Method 2: Fallback to system wine if available
try:
result = subprocess.run(['which', 'wine'], capture_output=True, text=True)
if result.returncode == 0:
wine_path = result.stdout.strip()
logger.debug(f"Using system Wine binary: {wine_path}")
return wine_path
except Exception:
pass
# 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
]
logger.error("No suitable Wine binary found for registry operations")
for wine_path in wine_candidates:
if wine_path.exists() and wine_path.is_file():
logger.info(f"Using Wine binary from user's configured Proton: {wine_path}")
return str(wine_path)
# Wine binary not found at expected paths - search recursively in Proton directory
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:
logger.info(f"Found Wine binary via recursive search in Proton directory: {wine_binary}")
return wine_binary
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
except Exception as e:
logger.error(f"Error finding Wine binary: {e}")
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):
"""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")

View File

@@ -276,7 +276,8 @@ class ModlistService:
download_dir_str = str(actual_download_path)
api_key = context['nexus_api_key']
oauth_info = context.get('nexus_oauth_info')
# Path to the engine binary (copied from working code)
engine_path = get_jackify_engine_path()
engine_dir = os.path.dirname(engine_path)
@@ -302,16 +303,26 @@ class ModlistService:
# Store original environment values (copied from working code)
original_env_values = {
'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')
}
try:
# Environment setup (copied from working code)
if api_key:
# 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:
os.environ['NEXUS_API_KEY'] = api_key
elif api_key:
os.environ['NEXUS_API_KEY'] = api_key
elif 'NEXUS_API_KEY' in os.environ:
del os.environ['NEXUS_API_KEY']
else:
# No auth available, clear any inherited values
if 'NEXUS_API_KEY' in os.environ:
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"
pretty_cmd = ' '.join([f'"{arg}"' if ' ' in arg else arg for arg in cmd])

View File

@@ -228,16 +228,65 @@ class NexusAuthService:
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
Same as ensure_valid_auth() - engine uses NEXUS_API_KEY env var for both OAuth and API keys
(This matches upstream Wabbajack behavior)
Get authentication for jackify-engine with auto-refresh support
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:
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:
"""

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

Binary file not shown.

View File

@@ -1891,8 +1891,9 @@ def main():
# Initialize file logging on root logger so all modules inherit it
from jackify.shared.logging import LoggingHandler
logging_handler = LoggingHandler()
# Rotate log file before setting up new logger
logging_handler.rotate_log_for_logger('jackify_gui', 'jackify-gui.log')
# Only rotate log file when debug mode is enabled
if debug_mode:
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
if debug_mode:

View File

@@ -639,6 +639,8 @@ class ConfigureExistingModlistScreen(QWidget):
# Start time tracking
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...")
# Create configuration thread using backend service

View File

@@ -919,8 +919,10 @@ class ConfigureNewModlistScreen(QWidget):
self._enable_controls_after_operation()
if success:
self._safe_append_text("Steam restarted successfully.")
# 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.configure_modlist()
else:
@@ -950,6 +952,8 @@ class ConfigureNewModlistScreen(QWidget):
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"""
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("Starting automated Steam shortcut creation and configuration...")

View File

@@ -1869,7 +1869,7 @@ class InstallModlistScreen(QWidget):
downloads_dir = self.downloads_dir_edit.text().strip()
# 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:
self._abort_with_message(
"warning",
@@ -2097,7 +2097,7 @@ class InstallModlistScreen(QWidget):
return
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:
debug_print(f"DEBUG: Exception in validate_and_start_install: {e}")
import traceback
@@ -2108,7 +2108,7 @@ class InstallModlistScreen(QWidget):
self.cancel_install_btn.setVisible(False)
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')
# 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
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...")
# Update UI state for installation
@@ -2136,7 +2138,7 @@ class InstallModlistScreen(QWidget):
installation_finished = Signal(bool, 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__()
self.modlist = modlist
self.install_dir = install_dir
@@ -2148,6 +2150,8 @@ class InstallModlistScreen(QWidget):
self.process_manager = None
# R&D: Progress state manager for parsing
self.progress_state_manager = progress_state_manager
self.auth_service = auth_service
self.oauth_info = oauth_info
self._premium_signal_sent = False
# Rolling buffer for Premium detection diagnostics
self._engine_output_buffer = []
@@ -2196,7 +2200,10 @@ class InstallModlistScreen(QWidget):
# Use clean subprocess environment to prevent AppImage variable inheritance
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)
ansi_escape = re.compile(rb'\x1b\[[0-9;?]*[ -/]*[@-~]')
buffer = b''
@@ -2451,7 +2458,9 @@ class InstallModlistScreen(QWidget):
# After the InstallationThread class definition, add:
self.install_thread = InstallationThread(
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.progress_received.connect(self.on_installation_progress)