mirror of
https://github.com/Omni-guides/Jackify.git
synced 2026-01-17 19:47:00 +01:00
Sync from development - prepare for v0.2.1
This commit is contained in:
@@ -2885,8 +2885,9 @@ echo Prefix creation complete.
|
||||
logger.info(f"Replacing existing shortcut: {shortcut_name}")
|
||||
|
||||
# First, remove the existing shortcut using STL
|
||||
if getattr(sys, 'frozen', False):
|
||||
stl_path = Path(sys._MEIPASS) / "steamtinkerlaunch"
|
||||
appdir = os.environ.get('APPDIR')
|
||||
if appdir:
|
||||
stl_path = Path(appdir) / "opt" / "jackify" / "steamtinkerlaunch"
|
||||
else:
|
||||
project_root = Path(__file__).parent.parent.parent.parent.parent
|
||||
stl_path = project_root / "external_repos/steamtinkerlaunch/steamtinkerlaunch"
|
||||
@@ -3045,7 +3046,25 @@ echo Prefix creation complete.
|
||||
|
||||
in_target_section = False
|
||||
path_updated = False
|
||||
wine_path = new_path.replace('/', '\\\\')
|
||||
|
||||
# Determine Wine drive letter based on SD card detection
|
||||
from jackify.backend.handlers.filesystem_handler import FileSystemHandler
|
||||
from jackify.backend.handlers.path_handler import PathHandler
|
||||
|
||||
linux_path = Path(new_path)
|
||||
|
||||
if FileSystemHandler.is_sd_card(linux_path):
|
||||
# SD card paths use D: drive
|
||||
# Strip SD card prefix using the same method as other handlers
|
||||
relative_sd_path_str = PathHandler._strip_sdcard_path_prefix(linux_path)
|
||||
wine_path = relative_sd_path_str.replace('/', '\\')
|
||||
wine_drive = "D:"
|
||||
logger.debug(f"SD card path detected: {new_path} -> D:\\{wine_path}")
|
||||
else:
|
||||
# Regular paths use Z: drive with full path
|
||||
wine_path = new_path.strip('/').replace('/', '\\')
|
||||
wine_drive = "Z:"
|
||||
logger.debug(f"Regular path: {new_path} -> Z:\\{wine_path}")
|
||||
|
||||
# Update existing path if found
|
||||
for i, line in enumerate(lines):
|
||||
@@ -3055,14 +3074,14 @@ echo Prefix creation complete.
|
||||
elif stripped_line.startswith('[') and in_target_section:
|
||||
in_target_section = False
|
||||
elif in_target_section and f'"{path_key}"' in line:
|
||||
lines[i] = f'"{path_key}"="Z:\\\\{wine_path}\\\\"\n' # Add trailing backslashes
|
||||
lines[i] = f'"{path_key}"="{wine_drive}\\\\{wine_path}\\\\"\n' # Add trailing backslashes
|
||||
path_updated = True
|
||||
break
|
||||
|
||||
# Add new section if path wasn't updated
|
||||
if not path_updated:
|
||||
lines.append(f'\n{section_name}\n')
|
||||
lines.append(f'"{path_key}"="Z:\\\\{wine_path}\\\\"\n') # Add trailing backslashes
|
||||
lines.append(f'"{path_key}"="{wine_drive}\\\\{wine_path}\\\\"\n') # Add trailing backslashes
|
||||
|
||||
# Write updated content
|
||||
with open(system_reg_path, 'w', encoding='utf-8') as f:
|
||||
|
||||
@@ -275,8 +275,16 @@ class ModlistService:
|
||||
actual_download_path = Path(download_dir_context)
|
||||
download_dir_str = str(actual_download_path)
|
||||
|
||||
api_key = context['nexus_api_key']
|
||||
oauth_info = context.get('nexus_oauth_info')
|
||||
# CRITICAL: Re-check authentication right before launching engine
|
||||
# This ensures we use current auth state, not stale cached values from context
|
||||
# (e.g., if user revoked OAuth after context was created)
|
||||
from ..services.nexus_auth_service import NexusAuthService
|
||||
auth_service = NexusAuthService()
|
||||
current_api_key, current_oauth_info = auth_service.get_auth_for_engine()
|
||||
|
||||
# Use current auth state, fallback to context values only if current check failed
|
||||
api_key = current_api_key or context.get('nexus_api_key')
|
||||
oauth_info = current_oauth_info or context.get('nexus_oauth_info')
|
||||
|
||||
# Path to the engine binary (copied from working code)
|
||||
engine_path = get_jackify_engine_path()
|
||||
@@ -311,6 +319,10 @@ class ModlistService:
|
||||
# Environment setup - prefer NEXUS_OAUTH_INFO (supports auto-refresh) over NEXUS_API_KEY
|
||||
if oauth_info:
|
||||
os.environ['NEXUS_OAUTH_INFO'] = oauth_info
|
||||
# CRITICAL: Set client_id so engine can refresh tokens with correct client_id
|
||||
# Engine's RefreshToken method reads this to use our "jackify" client_id instead of hardcoded "wabbajack"
|
||||
from jackify.backend.services.nexus_oauth_service import NexusOAuthService
|
||||
os.environ['NEXUS_OAUTH_CLIENT_ID'] = NexusOAuthService.CLIENT_ID
|
||||
# Also set NEXUS_API_KEY for backward compatibility
|
||||
if api_key:
|
||||
os.environ['NEXUS_API_KEY'] = api_key
|
||||
@@ -549,18 +561,42 @@ class ModlistService:
|
||||
success = modlist_menu.run_modlist_configuration_phase(config_context)
|
||||
debug_callback(f"Configuration phase result: {success}")
|
||||
|
||||
# Restore stdout before calling completion callback
|
||||
# Restore stdout before ENB detection and completion callback
|
||||
if original_stdout:
|
||||
sys.stdout = original_stdout
|
||||
original_stdout = None
|
||||
|
||||
# Configure ENB for Linux compatibility (non-blocking)
|
||||
# Do this BEFORE completion callback so we can pass detection status
|
||||
enb_detected = False
|
||||
try:
|
||||
from ..handlers.enb_handler import ENBHandler
|
||||
enb_handler = ENBHandler()
|
||||
enb_success, enb_message, enb_detected = enb_handler.configure_enb_for_linux(context.install_dir)
|
||||
|
||||
if enb_message:
|
||||
if enb_success:
|
||||
logger.info(enb_message)
|
||||
if progress_callback:
|
||||
progress_callback(enb_message)
|
||||
else:
|
||||
logger.warning(enb_message)
|
||||
# Non-blocking: continue workflow even if ENB config fails
|
||||
except Exception as e:
|
||||
logger.warning(f"ENB configuration skipped due to error: {e}")
|
||||
# Continue workflow - ENB config is optional
|
||||
|
||||
# Store ENB detection status in context for GUI to use
|
||||
context.enb_detected = enb_detected
|
||||
|
||||
if completion_callback:
|
||||
if success:
|
||||
debug_callback("Configuration completed successfully, calling completion callback")
|
||||
completion_callback(True, "Configuration completed successfully!", context.name)
|
||||
# Pass ENB detection status through callback
|
||||
completion_callback(True, "Configuration completed successfully!", context.name, enb_detected)
|
||||
else:
|
||||
debug_callback("Configuration failed, calling completion callback with failure")
|
||||
completion_callback(False, "Configuration failed", context.name)
|
||||
completion_callback(False, "Configuration failed", context.name, False)
|
||||
|
||||
return success
|
||||
|
||||
|
||||
@@ -569,4 +569,57 @@ class NativeSteamService:
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error removing shortcut: {e}")
|
||||
return False
|
||||
|
||||
def create_steam_library_symlinks(self, app_id: int) -> bool:
|
||||
"""
|
||||
Create symlink to libraryfolders.vdf in Wine prefix for game detection.
|
||||
|
||||
This allows Wabbajack running in the prefix to detect Steam games.
|
||||
Based on Wabbajack-Proton-AuCu implementation.
|
||||
|
||||
Args:
|
||||
app_id: Steam AppID (unsigned)
|
||||
|
||||
Returns:
|
||||
True if successful
|
||||
"""
|
||||
# Ensure Steam user detection is completed first
|
||||
if not self.steam_path:
|
||||
if not self.find_steam_user():
|
||||
logger.error("Cannot create symlinks: Steam user detection failed")
|
||||
return False
|
||||
|
||||
# Find libraryfolders.vdf
|
||||
libraryfolders_vdf = self.steam_path / "config" / "libraryfolders.vdf"
|
||||
if not libraryfolders_vdf.exists():
|
||||
logger.error(f"libraryfolders.vdf not found at: {libraryfolders_vdf}")
|
||||
return False
|
||||
|
||||
# Get compatdata path for this AppID
|
||||
compat_data = self.steam_path / f"steamapps/compatdata/{app_id}"
|
||||
if not compat_data.exists():
|
||||
logger.error(f"Compatdata directory not found: {compat_data}")
|
||||
return False
|
||||
|
||||
# Target directory in Wine prefix
|
||||
prefix_config_dir = compat_data / "pfx/drive_c/Program Files (x86)/Steam/config"
|
||||
prefix_config_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Symlink target
|
||||
symlink_target = prefix_config_dir / "libraryfolders.vdf"
|
||||
|
||||
try:
|
||||
# Remove existing symlink/file if it exists
|
||||
if symlink_target.exists() or symlink_target.is_symlink():
|
||||
symlink_target.unlink()
|
||||
|
||||
# Create symlink
|
||||
symlink_target.symlink_to(libraryfolders_vdf)
|
||||
logger.info(f"Created symlink: {symlink_target} -> {libraryfolders_vdf}")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error creating symlink: {e}")
|
||||
return False
|
||||
@@ -102,7 +102,6 @@ class NexusOAuthService:
|
||||
# Determine executable path (DEV mode vs AppImage)
|
||||
# Check multiple indicators for AppImage execution
|
||||
is_appimage = (
|
||||
getattr(sys, 'frozen', False) or # PyInstaller frozen
|
||||
'APPIMAGE' in env or # AppImage environment variable
|
||||
'APPDIR' in env or # AppImage directory variable
|
||||
(sys.argv[0] and sys.argv[0].endswith('.AppImage')) # Executable name
|
||||
@@ -127,7 +126,8 @@ class NexusOAuthService:
|
||||
# Running from source (DEV mode)
|
||||
# Need to ensure we run from the correct directory
|
||||
src_dir = Path(__file__).parent.parent.parent.parent # Go up to src/
|
||||
exec_path = f"cd {src_dir} && {sys.executable} -m jackify.frontends.gui"
|
||||
# Use bash -c with proper quoting for paths with spaces
|
||||
exec_path = f'bash -c \'cd "{src_dir}" && "{sys.executable}" -m jackify.frontends.gui "$@"\' --'
|
||||
logger.info(f"DEV mode exec path: {exec_path}")
|
||||
logger.info(f"Source directory: {src_dir}")
|
||||
|
||||
@@ -139,29 +139,43 @@ class NexusOAuthService:
|
||||
else:
|
||||
# Check if Exec path matches current mode
|
||||
current_content = desktop_file.read_text()
|
||||
if f"Exec={exec_path} %u" not in current_content:
|
||||
# Check for both quoted (AppImage) and unquoted (DEV mode with bash -c) formats
|
||||
if is_appimage:
|
||||
expected_exec = f'Exec="{exec_path}" %u'
|
||||
else:
|
||||
expected_exec = f"Exec={exec_path} %u"
|
||||
|
||||
if expected_exec not in current_content:
|
||||
needs_update = True
|
||||
logger.info(f"Updating desktop file with new Exec path: {exec_path}")
|
||||
|
||||
# Explicitly detect and fix malformed entries (unquoted paths with spaces)
|
||||
# Check if any Exec line exists without quotes but contains spaces
|
||||
if is_appimage and ' ' in exec_path:
|
||||
import re
|
||||
# Look for Exec=<path with spaces> without quotes
|
||||
if re.search(r'Exec=[^"]\S*\s+\S*\.AppImage', current_content):
|
||||
needs_update = True
|
||||
logger.info("Fixing malformed desktop file (unquoted path with spaces)")
|
||||
|
||||
if needs_update:
|
||||
desktop_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
||||
# Build desktop file content with proper working directory
|
||||
if is_appimage:
|
||||
# AppImage doesn't need working directory
|
||||
# AppImage - quote path to handle spaces
|
||||
desktop_content = f"""[Desktop Entry]
|
||||
Type=Application
|
||||
Name=Jackify
|
||||
Comment=Wabbajack modlist manager for Linux
|
||||
Exec={exec_path} %u
|
||||
Exec="{exec_path}" %u
|
||||
Icon=com.jackify.app
|
||||
Terminal=false
|
||||
Categories=Game;Utility;
|
||||
MimeType=x-scheme-handler/jackify;
|
||||
"""
|
||||
else:
|
||||
# DEV mode needs working directory set to src/
|
||||
# exec_path already contains the correct format: "cd {src_dir} && {sys.executable} -m jackify.frontends.gui"
|
||||
# DEV mode - exec_path already contains bash -c with proper quoting
|
||||
src_dir = Path(__file__).parent.parent.parent.parent # Go up to src/
|
||||
desktop_content = f"""[Desktop Entry]
|
||||
Type=Application
|
||||
|
||||
Reference in New Issue
Block a user