mirror of
https://github.com/Omni-guides/Jackify.git
synced 2026-01-17 11:37:01 +01:00
Sync from development - prepare for v0.1.2
This commit is contained in:
25
CHANGELOG.md
25
CHANGELOG.md
@@ -1,5 +1,30 @@
|
|||||||
# Jackify Changelog
|
# Jackify Changelog
|
||||||
|
|
||||||
|
## v0.1.2 - About Dialog and System Information
|
||||||
|
**Release Date:** September 16, 2025
|
||||||
|
|
||||||
|
### New Features
|
||||||
|
- **About Dialog**: System information display with OS, kernel, desktop environment, and display server detection
|
||||||
|
- **Engine Version Detection**: Real-time jackify-engine version reporting
|
||||||
|
- **Update Integration**: Check for Updates functionality within About dialog
|
||||||
|
- **Support Tools**: Copy system info for troubleshooting
|
||||||
|
- **Configurable Jackify Directory**: Users can now customize the Jackify data directory location via Settings
|
||||||
|
|
||||||
|
### UX Improvements
|
||||||
|
- **Control Management**: Form controls are now disabled during install/configure workflows to prevent user conflicts (only Cancel remains active)
|
||||||
|
- **Auto-Accept Steam Restart**: Optional checkbox to automatically accept Steam restart dialogs for unattended workflows
|
||||||
|
- **Layout Optimization**: Resolution dropdown and Steam restart option share the same line for better space utilization
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
- **Resolution Handler**: Fixed regression in resolution setting for Fallout 4 and other games when modlists use vanilla game directories instead of traditional "Stock Game" folders
|
||||||
|
- **DXVK Configuration**: Fixed dxvk.conf creation failure when modlists point directly to vanilla game installations
|
||||||
|
- **CLI Resolution Setting**: Fixed missing resolution prompting in CLI Install workflow
|
||||||
|
|
||||||
|
### Engine Updates
|
||||||
|
- **jackify-engine v0.3.14**: Updated to support configurable Jackify data directory, improved Nexus API error handling with better 404/403 responses, and enhanced error logging for troubleshooting
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## v0.1.1 - Self-Updater Implementation
|
## v0.1.1 - Self-Updater Implementation
|
||||||
**Release Date:** September 17, 2025
|
**Release Date:** September 17, 2025
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
||||||
[Wiki](https://github.com/Omni-guides/Jackify/wiki) | [Nexus](https://www.nexusmods.com/site/mods/1427) | [Download](https://www.nexusmods.com/Core/Libs/Common/Widgets/DownloadPopUp?id=5807&game_id=2295) | [Wabbajack Discord](https://discord.gg/wabbajack) | [Jackify Issues](https://github.com/Omni-guides/Jackify/issues) | [Legacy Guides](https://github.com/Omni-guides/Jackify/tree/master/Legacy) | [Ko-fi](https://ko-fi.com/omni1)
|
[Wiki](https://github.com/Omni-guides/Jackify/wiki) | [Nexus](https://www.nexusmods.com/site/mods/1427) | [Download](https://www.nexusmods.com/site/mods/1427?tab=files) | [Wabbajack Discord](https://discord.gg/wabbajack) | [Jackify Issues](https://github.com/Omni-guides/Jackify/issues) | [Legacy Guides](https://github.com/Omni-guides/Jackify/tree/master/Legacy) | [Ko-fi](https://ko-fi.com/omni1)
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -93,7 +93,7 @@ For a complete step-by-step guide with screenshots, see the [User Guide](https:/
|
|||||||
|
|
||||||
### Quick Start
|
### Quick Start
|
||||||
|
|
||||||
1. **Download**: Get the latest release from [NexusMods](https://www.nexusmods.com/Core/Libs/Common/Widgets/DownloadPopUp?id=5807&game_id=2295)
|
1. **Download**: Get the latest release from [NexusMods](https://www.nexusmods.com/site/mods/1427?tab=files)
|
||||||
2. **Extract**: Unzip the .7z archive to get `Jackify.AppImage`
|
2. **Extract**: Unzip the .7z archive to get `Jackify.AppImage`
|
||||||
3. **Run**: `chmod +x Jackify.AppImage && ./Jackify.AppImage`
|
3. **Run**: `chmod +x Jackify.AppImage && ./Jackify.AppImage`
|
||||||
4. **Install**: Choose "Install a Modlist", select your game and modlist, configure directories and API key
|
4. **Install**: Choose "Install a Modlist", select your game and modlist, configure directories and API key
|
||||||
|
|||||||
@@ -5,4 +5,4 @@ This package provides both CLI and GUI interfaces for managing
|
|||||||
Wabbajack modlists natively on Linux systems.
|
Wabbajack modlists natively on Linux systems.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__version__ = "0.1.1"
|
__version__ = "0.1.2"
|
||||||
|
|||||||
@@ -104,8 +104,8 @@ class ModlistInstallCLI:
|
|||||||
|
|
||||||
if isinstance(menu_handler_or_system_info, SystemInfo):
|
if isinstance(menu_handler_or_system_info, SystemInfo):
|
||||||
# GUI frontend initialization pattern
|
# GUI frontend initialization pattern
|
||||||
system_info = menu_handler_or_system_info
|
self.system_info = menu_handler_or_system_info
|
||||||
self.steamdeck = system_info.is_steamdeck
|
self.steamdeck = self.system_info.is_steamdeck
|
||||||
|
|
||||||
# Initialize menu_handler for GUI mode
|
# Initialize menu_handler for GUI mode
|
||||||
from ..handlers.menu_handler import MenuHandler
|
from ..handlers.menu_handler import MenuHandler
|
||||||
@@ -114,6 +114,9 @@ class ModlistInstallCLI:
|
|||||||
# CLI frontend initialization pattern
|
# CLI frontend initialization pattern
|
||||||
self.menu_handler = menu_handler_or_system_info
|
self.menu_handler = menu_handler_or_system_info
|
||||||
self.steamdeck = steamdeck
|
self.steamdeck = steamdeck
|
||||||
|
# Create system_info for CLI mode
|
||||||
|
from ..models.configuration import SystemInfo
|
||||||
|
self.system_info = SystemInfo(is_steamdeck=steamdeck)
|
||||||
|
|
||||||
self.protontricks_handler = ProtontricksHandler(steamdeck=self.steamdeck)
|
self.protontricks_handler = ProtontricksHandler(steamdeck=self.steamdeck)
|
||||||
self.shortcut_handler = ShortcutHandler(steamdeck=self.steamdeck)
|
self.shortcut_handler = ShortcutHandler(steamdeck=self.steamdeck)
|
||||||
@@ -914,6 +917,20 @@ class ModlistInstallCLI:
|
|||||||
|
|
||||||
self.logger.debug("configuration_phase: Proceeding with Steam configuration...")
|
self.logger.debug("configuration_phase: Proceeding with Steam configuration...")
|
||||||
|
|
||||||
|
# Add resolution prompting for CLI mode (before Steam operations)
|
||||||
|
if not is_gui_mode:
|
||||||
|
from jackify.backend.handlers.resolution_handler import ResolutionHandler
|
||||||
|
resolution_handler = ResolutionHandler()
|
||||||
|
|
||||||
|
# Check if Steam Deck
|
||||||
|
is_steamdeck = self.steamdeck if hasattr(self, 'steamdeck') else False
|
||||||
|
|
||||||
|
# Prompt for resolution in CLI mode
|
||||||
|
selected_resolution = resolution_handler.select_resolution(steamdeck=is_steamdeck)
|
||||||
|
if selected_resolution:
|
||||||
|
self.context['resolution'] = selected_resolution
|
||||||
|
self.logger.info(f"Resolution set to: {selected_resolution}")
|
||||||
|
|
||||||
# Proceed with Steam configuration
|
# Proceed with Steam configuration
|
||||||
self.logger.info(f"Starting Steam configuration for '{shortcut_name}'")
|
self.logger.info(f"Starting Steam configuration for '{shortcut_name}'")
|
||||||
|
|
||||||
@@ -957,8 +974,8 @@ class ModlistInstallCLI:
|
|||||||
shortcut_name, install_dir_str, mo2_exe_path, progress_callback, steamdeck=_is_steamdeck
|
shortcut_name, install_dir_str, mo2_exe_path, progress_callback, steamdeck=_is_steamdeck
|
||||||
)
|
)
|
||||||
|
|
||||||
# Handle the result
|
# Handle the result (same logic as GUI)
|
||||||
if isinstance(result, tuple) and len(result) == 3:
|
if isinstance(result, tuple) and len(result) == 4:
|
||||||
if result[0] == "CONFLICT":
|
if result[0] == "CONFLICT":
|
||||||
# Handle conflict
|
# Handle conflict
|
||||||
conflicts = result[1]
|
conflicts = result[1]
|
||||||
@@ -984,8 +1001,8 @@ class ModlistInstallCLI:
|
|||||||
result = prefix_service.continue_workflow_after_conflict_resolution(
|
result = prefix_service.continue_workflow_after_conflict_resolution(
|
||||||
shortcut_name, install_dir_str, mo2_exe_path, app_id, progress_callback
|
shortcut_name, install_dir_str, mo2_exe_path, app_id, progress_callback
|
||||||
)
|
)
|
||||||
if isinstance(result, tuple) and len(result) == 3:
|
if isinstance(result, tuple) and len(result) >= 3:
|
||||||
success, prefix_path, app_id = result
|
success, prefix_path, app_id = result[0], result[1], result[2]
|
||||||
else:
|
else:
|
||||||
success, prefix_path, app_id = False, None, None
|
success, prefix_path, app_id = False, None, None
|
||||||
else:
|
else:
|
||||||
@@ -1000,10 +1017,58 @@ class ModlistInstallCLI:
|
|||||||
print(f"{COLOR_ERROR}Invalid choice. Cancelling.{COLOR_RESET}")
|
print(f"{COLOR_ERROR}Invalid choice. Cancelling.{COLOR_RESET}")
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
# Normal result
|
# Normal result with timestamp (4-tuple)
|
||||||
|
success, prefix_path, app_id, last_timestamp = result
|
||||||
|
elif isinstance(result, tuple) and len(result) == 3:
|
||||||
|
if result[0] == "CONFLICT":
|
||||||
|
# Handle conflict (3-tuple format)
|
||||||
|
conflicts = result[1]
|
||||||
|
print(f"\n{COLOR_WARNING}Found existing Steam shortcut(s) with the same name and path:{COLOR_RESET}")
|
||||||
|
|
||||||
|
for i, conflict in enumerate(conflicts, 1):
|
||||||
|
print(f" {i}. Name: {conflict['name']}")
|
||||||
|
print(f" Executable: {conflict['exe']}")
|
||||||
|
print(f" Start Directory: {conflict['startdir']}")
|
||||||
|
|
||||||
|
print(f"\n{COLOR_PROMPT}Options:{COLOR_RESET}")
|
||||||
|
print(" • Replace - Remove the existing shortcut and create a new one")
|
||||||
|
print(" • Cancel - Keep the existing shortcut and stop the installation")
|
||||||
|
print(" • Skip - Continue without creating a Steam shortcut")
|
||||||
|
|
||||||
|
choice = input(f"\n{COLOR_PROMPT}Choose an option (replace/cancel/skip): {COLOR_RESET}").strip().lower()
|
||||||
|
|
||||||
|
if choice == 'replace':
|
||||||
|
print(f"{COLOR_INFO}Replacing existing shortcut...{COLOR_RESET}")
|
||||||
|
success, app_id = prefix_service.replace_existing_shortcut(shortcut_name, mo2_exe_path, install_dir_str)
|
||||||
|
if success and app_id:
|
||||||
|
# Continue the workflow after replacement
|
||||||
|
result = prefix_service.continue_workflow_after_conflict_resolution(
|
||||||
|
shortcut_name, install_dir_str, mo2_exe_path, app_id, progress_callback
|
||||||
|
)
|
||||||
|
if isinstance(result, tuple) and len(result) >= 3:
|
||||||
|
success, prefix_path, app_id = result[0], result[1], result[2]
|
||||||
|
else:
|
||||||
|
success, prefix_path, app_id = False, None, None
|
||||||
|
else:
|
||||||
|
success, prefix_path, app_id = False, None, None
|
||||||
|
elif choice == 'cancel':
|
||||||
|
print(f"{COLOR_INFO}Cancelling installation.{COLOR_RESET}")
|
||||||
|
return
|
||||||
|
elif choice == 'skip':
|
||||||
|
print(f"{COLOR_INFO}Skipping Steam shortcut creation.{COLOR_RESET}")
|
||||||
|
success, prefix_path, app_id = True, None, None
|
||||||
|
else:
|
||||||
|
print(f"{COLOR_ERROR}Invalid choice. Cancelling.{COLOR_RESET}")
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
# Normal result (3-tuple format)
|
||||||
success, prefix_path, app_id = result
|
success, prefix_path, app_id = result
|
||||||
else:
|
else:
|
||||||
success, prefix_path, app_id = False, None, None
|
# Result is not a tuple, check if it's just a boolean success
|
||||||
|
if result is True:
|
||||||
|
success, prefix_path, app_id = True, None, None
|
||||||
|
else:
|
||||||
|
success, prefix_path, app_id = False, None, None
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
print(f"{COLOR_SUCCESS}Automated Steam setup completed successfully!{COLOR_RESET}")
|
print(f"{COLOR_SUCCESS}Automated Steam setup completed successfully!{COLOR_RESET}")
|
||||||
@@ -1011,128 +1076,54 @@ class ModlistInstallCLI:
|
|||||||
print(f"{COLOR_INFO}Proton prefix created at: {prefix_path}{COLOR_RESET}")
|
print(f"{COLOR_INFO}Proton prefix created at: {prefix_path}{COLOR_RESET}")
|
||||||
if app_id:
|
if app_id:
|
||||||
print(f"{COLOR_INFO}Steam AppID: {app_id}{COLOR_RESET}")
|
print(f"{COLOR_INFO}Steam AppID: {app_id}{COLOR_RESET}")
|
||||||
return
|
# Continue to configuration phase
|
||||||
else:
|
else:
|
||||||
print(f"{COLOR_WARNING}Automated Steam setup failed. Falling back to manual setup...{COLOR_RESET}")
|
print(f"{COLOR_ERROR}Automated Steam setup failed. Result: {result}{COLOR_RESET}")
|
||||||
|
print(f"{COLOR_ERROR}Steam integration was not completed. Please check the logs for details.{COLOR_RESET}")
|
||||||
|
return
|
||||||
|
|
||||||
# Fallback to manual shortcut creation process
|
# Step 3: Use SAME backend service as GUI
|
||||||
print(f"\n{COLOR_INFO}Using manual Steam setup workflow...{COLOR_RESET}")
|
from jackify.backend.services.modlist_service import ModlistService
|
||||||
|
from jackify.backend.models.modlist import ModlistContext
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
# Use the working shortcut creation process from legacy code
|
# Create ModlistContext with engine_installed=True (same as GUI)
|
||||||
from ..handlers.shortcut_handler import ShortcutHandler
|
modlist_context = ModlistContext(
|
||||||
shortcut_handler = ShortcutHandler(steamdeck=self.steamdeck, verbose=False)
|
name=shortcut_name,
|
||||||
|
install_dir=Path(install_dir_str),
|
||||||
# Create nxmhandler.ini to suppress NXM popup
|
download_dir=Path(install_dir_str) / "downloads", # Standard location
|
||||||
shortcut_handler.write_nxmhandler_ini(install_dir_str, mo2_exe_path)
|
game_type=self.context.get('detected_game', 'Unknown'),
|
||||||
|
nexus_api_key='', # Not needed for configuration
|
||||||
# Create shortcut with working NativeSteamService
|
modlist_value=self.context.get('modlist_value', ''),
|
||||||
from ..services.native_steam_service import NativeSteamService
|
modlist_source=self.context.get('modlist_source', 'identifier'),
|
||||||
steam_service = NativeSteamService()
|
resolution=self.context.get('resolution'),
|
||||||
|
mo2_exe_path=Path(mo2_exe_path),
|
||||||
success, app_id = steam_service.create_shortcut_with_proton(
|
skip_confirmation=True, # Always skip confirmation in CLI
|
||||||
app_name=shortcut_name,
|
engine_installed=True # Skip path manipulation for engine workflows
|
||||||
exe_path=mo2_exe_path,
|
|
||||||
start_dir=os.path.dirname(mo2_exe_path),
|
|
||||||
launch_options="%command%",
|
|
||||||
tags=["Jackify"],
|
|
||||||
proton_version="proton_experimental"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if not success or not app_id:
|
# Add app_id to context
|
||||||
self.logger.error("Failed to create Steam shortcut")
|
modlist_context.app_id = app_id
|
||||||
print(f"{COLOR_ERROR}Failed to create Steam shortcut. Check logs for details.{COLOR_RESET}")
|
|
||||||
return
|
|
||||||
|
|
||||||
# Step 2: Handle Steam restart and manual steps (if not in GUI mode)
|
# Step 4: Configure modlist using SAME service as GUI
|
||||||
if not is_gui_mode:
|
modlist_service = ModlistService(self.system_info)
|
||||||
print(f"\n{COLOR_INFO}Steam shortcut created successfully!{COLOR_RESET}")
|
|
||||||
print("Steam needs to restart to detect the new shortcut. WARNING: This will close all running Steam instances, and games.")
|
|
||||||
|
|
||||||
restart_choice = input("\nRestart Steam automatically now? (Y/n): ").strip().lower()
|
|
||||||
if restart_choice == 'n':
|
|
||||||
print("\nPlease restart Steam manually and complete the Proton setup steps.")
|
|
||||||
print("You can configure this modlist later using 'Configure Existing Modlist'.")
|
|
||||||
return
|
|
||||||
|
|
||||||
# Restart Steam
|
|
||||||
print("\nRestarting Steam...")
|
|
||||||
if shortcut_handler.secure_steam_restart():
|
|
||||||
print(f"{COLOR_INFO}Steam restarted successfully.{COLOR_RESET}")
|
|
||||||
|
|
||||||
# Display manual Proton steps
|
|
||||||
from ..handlers.menu_handler import ModlistMenuHandler
|
|
||||||
from ..handlers.config_handler import ConfigHandler
|
|
||||||
config_handler = ConfigHandler()
|
|
||||||
menu_handler = ModlistMenuHandler(config_handler)
|
|
||||||
menu_handler._display_manual_proton_steps(shortcut_name)
|
|
||||||
|
|
||||||
retry_count = 0
|
|
||||||
max_retries = 3
|
|
||||||
while retry_count < max_retries:
|
|
||||||
input(f"\n{COLOR_PROMPT}Once you have completed ALL the steps above, press Enter to continue...{COLOR_RESET}")
|
|
||||||
print(f"\n{COLOR_INFO}Verifying manual steps...{COLOR_RESET}")
|
|
||||||
new_app_id = shortcut_handler.get_appid_for_shortcut(shortcut_name, mo2_exe_path)
|
|
||||||
if new_app_id and new_app_id.isdigit() and int(new_app_id) > 0:
|
|
||||||
app_id = new_app_id
|
|
||||||
from ..handlers.modlist_handler import ModlistHandler
|
|
||||||
modlist_handler = ModlistHandler({}, steamdeck=self.steamdeck)
|
|
||||||
verified, status_code = modlist_handler.verify_proton_setup(app_id)
|
|
||||||
if verified:
|
|
||||||
print(f"{COLOR_SUCCESS}Manual steps verification successful!{COLOR_RESET}")
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
retry_count += 1
|
|
||||||
if retry_count < max_retries:
|
|
||||||
print(f"\n{COLOR_ERROR}Verification failed: {status_code}{COLOR_RESET}")
|
|
||||||
print(f"{COLOR_WARNING}Please ensure you have completed all manual steps correctly.{COLOR_RESET}")
|
|
||||||
menu_handler._display_manual_proton_steps(shortcut_name)
|
|
||||||
else:
|
|
||||||
print(f"\n{COLOR_ERROR}Manual steps verification failed after {max_retries} attempts.{COLOR_RESET}")
|
|
||||||
print(f"{COLOR_WARNING}Configuration may not work properly.{COLOR_RESET}")
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
retry_count += 1
|
|
||||||
if retry_count < max_retries:
|
|
||||||
print(f"\n{COLOR_ERROR}Could not find valid AppID after launch.{COLOR_RESET}")
|
|
||||||
print(f"{COLOR_WARNING}Please ensure you have launched the shortcut from Steam.{COLOR_RESET}")
|
|
||||||
menu_handler._display_manual_proton_steps(shortcut_name)
|
|
||||||
else:
|
|
||||||
print(f"\n{COLOR_ERROR}Could not find valid AppID after {max_retries} attempts.{COLOR_RESET}")
|
|
||||||
print(f"{COLOR_WARNING}Configuration may not work properly.{COLOR_RESET}")
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
print(f"{COLOR_ERROR}Steam restart failed. Please restart manually and configure later.{COLOR_RESET}")
|
|
||||||
return
|
|
||||||
|
|
||||||
# Step 3: Build configuration context with the AppID
|
|
||||||
config_context = {
|
|
||||||
'name': shortcut_name,
|
|
||||||
'appid': app_id,
|
|
||||||
'path': install_dir_str,
|
|
||||||
'mo2_exe_path': mo2_exe_path,
|
|
||||||
'resolution': self.context.get('resolution'),
|
|
||||||
'skip_confirmation': is_gui_mode,
|
|
||||||
'manual_steps_completed': not is_gui_mode # True if we did manual steps above
|
|
||||||
}
|
|
||||||
|
|
||||||
# Step 4: Use ModlistMenuHandler to run the complete configuration
|
|
||||||
from ..handlers.menu_handler import ModlistMenuHandler
|
|
||||||
from ..handlers.config_handler import ConfigHandler
|
|
||||||
|
|
||||||
config_handler = ConfigHandler()
|
|
||||||
modlist_menu = ModlistMenuHandler(config_handler)
|
|
||||||
|
|
||||||
# Add section header for configuration phase if progress callback is available
|
# Add section header for configuration phase if progress callback is available
|
||||||
if 'progress_callback' in locals() and progress_callback:
|
if 'progress_callback' in locals() and progress_callback:
|
||||||
progress_callback("") # Blank line for spacing
|
progress_callback("") # Blank line for spacing
|
||||||
progress_callback("=== Configuring Modlist ===")
|
progress_callback("=== Configuration Phase ===")
|
||||||
|
|
||||||
self.logger.info("Running post-installation configuration phase")
|
print(f"\n{COLOR_INFO}=== Configuration Phase ==={COLOR_RESET}")
|
||||||
configuration_success = modlist_menu.run_modlist_configuration_phase(config_context)
|
self.logger.info("Running post-installation configuration phase using ModlistService")
|
||||||
|
|
||||||
|
# Configure modlist using SAME method as GUI
|
||||||
|
configuration_success = modlist_service.configure_modlist_post_steam(modlist_context)
|
||||||
|
|
||||||
if configuration_success:
|
if configuration_success:
|
||||||
|
print(f"{COLOR_SUCCESS}Configuration completed successfully!{COLOR_RESET}")
|
||||||
self.logger.info("Post-installation configuration completed successfully")
|
self.logger.info("Post-installation configuration completed successfully")
|
||||||
else:
|
else:
|
||||||
|
print(f"{COLOR_WARNING}Configuration had some issues but completed.{COLOR_RESET}")
|
||||||
self.logger.warning("Post-installation configuration had issues")
|
self.logger.warning("Post-installation configuration had issues")
|
||||||
else:
|
else:
|
||||||
# Game not supported for automated configuration
|
# Game not supported for automated configuration
|
||||||
@@ -1162,10 +1153,9 @@ class ModlistInstallCLI:
|
|||||||
# Section header now provided by GUI layer to avoid duplication
|
# Section header now provided by GUI layer to avoid duplication
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Set GUI mode for backend operations
|
# CLI Install: keep original GUI mode (don't force GUI mode)
|
||||||
import os
|
import os
|
||||||
original_gui_mode = os.environ.get('JACKIFY_GUI_MODE')
|
original_gui_mode = os.environ.get('JACKIFY_GUI_MODE')
|
||||||
os.environ['JACKIFY_GUI_MODE'] = '1'
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Build context for configuration
|
# Build context for configuration
|
||||||
@@ -1176,7 +1166,7 @@ class ModlistInstallCLI:
|
|||||||
'modlist_value': context.get('modlist_value'),
|
'modlist_value': context.get('modlist_value'),
|
||||||
'modlist_source': context.get('modlist_source'),
|
'modlist_source': context.get('modlist_source'),
|
||||||
'resolution': context.get('resolution'),
|
'resolution': context.get('resolution'),
|
||||||
'skip_confirmation': True, # GUI mode is non-interactive
|
'skip_confirmation': True, # CLI Install is non-interactive
|
||||||
'manual_steps_completed': False
|
'manual_steps_completed': False
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,8 @@ class ConfigHandler:
|
|||||||
"default_install_parent_dir": None, # Parent directory for modlist installations
|
"default_install_parent_dir": None, # Parent directory for modlist installations
|
||||||
"default_download_parent_dir": None, # Parent directory for downloads
|
"default_download_parent_dir": None, # Parent directory for downloads
|
||||||
"modlist_install_base_dir": os.path.expanduser("~/Games"), # Configurable base directory for modlist installations
|
"modlist_install_base_dir": os.path.expanduser("~/Games"), # Configurable base directory for modlist installations
|
||||||
"modlist_downloads_base_dir": os.path.expanduser("~/Games/Modlist_Downloads") # Configurable base directory for downloads
|
"modlist_downloads_base_dir": os.path.expanduser("~/Games/Modlist_Downloads"), # Configurable base directory for downloads
|
||||||
|
"jackify_data_dir": None # Configurable Jackify data directory (default: ~/Jackify)
|
||||||
}
|
}
|
||||||
|
|
||||||
# Load configuration if exists
|
# Load configuration if exists
|
||||||
@@ -48,6 +49,12 @@ class ConfigHandler:
|
|||||||
self.settings["steam_path"] = self._detect_steam_path()
|
self.settings["steam_path"] = self._detect_steam_path()
|
||||||
# Save the updated settings
|
# Save the updated settings
|
||||||
self.save_config()
|
self.save_config()
|
||||||
|
|
||||||
|
# If jackify_data_dir is not set, initialize it to default
|
||||||
|
if not self.settings.get("jackify_data_dir"):
|
||||||
|
self.settings["jackify_data_dir"] = os.path.expanduser("~/Jackify")
|
||||||
|
# Save the updated settings
|
||||||
|
self.save_config()
|
||||||
|
|
||||||
def _detect_steam_path(self):
|
def _detect_steam_path(self):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -788,10 +788,16 @@ class ModlistHandler:
|
|||||||
status_callback(f"{self._get_progress_timestamp()} Updating resolution settings")
|
status_callback(f"{self._get_progress_timestamp()} Updating resolution settings")
|
||||||
# Ensure resolution_handler call uses correct args if needed
|
# Ensure resolution_handler call uses correct args if needed
|
||||||
# Assuming it uses modlist_dir (str) and game_var_full (str)
|
# Assuming it uses modlist_dir (str) and game_var_full (str)
|
||||||
|
# Construct vanilla game directory path for fallback
|
||||||
|
vanilla_game_dir = None
|
||||||
|
if self.steam_library and self.game_var_full:
|
||||||
|
vanilla_game_dir = str(Path(self.steam_library) / "steamapps" / "common" / self.game_var_full)
|
||||||
|
|
||||||
if not self.resolution_handler.update_ini_resolution(
|
if not self.resolution_handler.update_ini_resolution(
|
||||||
modlist_dir=self.modlist_dir,
|
modlist_dir=self.modlist_dir,
|
||||||
game_var=self.game_var_full,
|
game_var=self.game_var_full,
|
||||||
set_res=self.selected_resolution
|
set_res=self.selected_resolution,
|
||||||
|
vanilla_game_dir=vanilla_game_dir
|
||||||
):
|
):
|
||||||
self.logger.warning("Failed to update resolution settings in some INI files.")
|
self.logger.warning("Failed to update resolution settings in some INI files.")
|
||||||
print("Warning: Failed to update resolution settings.")
|
print("Warning: Failed to update resolution settings.")
|
||||||
@@ -818,12 +824,18 @@ class ModlistHandler:
|
|||||||
status_callback(f"{self._get_progress_timestamp()} Creating dxvk.conf file")
|
status_callback(f"{self._get_progress_timestamp()} Creating dxvk.conf file")
|
||||||
self.logger.info("Step 10: Creating dxvk.conf file...")
|
self.logger.info("Step 10: Creating dxvk.conf file...")
|
||||||
# Assuming create_dxvk_conf still uses string paths
|
# Assuming create_dxvk_conf still uses string paths
|
||||||
|
# Construct vanilla game directory path for fallback
|
||||||
|
vanilla_game_dir = None
|
||||||
|
if self.steam_library and self.game_var_full:
|
||||||
|
vanilla_game_dir = str(Path(self.steam_library) / "steamapps" / "common" / self.game_var_full)
|
||||||
|
|
||||||
if not self.path_handler.create_dxvk_conf(
|
if not self.path_handler.create_dxvk_conf(
|
||||||
modlist_dir=self.modlist_dir,
|
modlist_dir=self.modlist_dir,
|
||||||
modlist_sdcard=self.modlist_sdcard,
|
modlist_sdcard=self.modlist_sdcard,
|
||||||
steam_library=str(self.steam_library) if self.steam_library else None, # Pass as string or None
|
steam_library=str(self.steam_library) if self.steam_library else None, # Pass as string or None
|
||||||
basegame_sdcard=self.basegame_sdcard,
|
basegame_sdcard=self.basegame_sdcard,
|
||||||
game_var_full=self.game_var_full
|
game_var_full=self.game_var_full,
|
||||||
|
vanilla_game_dir=vanilla_game_dir
|
||||||
):
|
):
|
||||||
self.logger.warning("Failed to create dxvk.conf file.")
|
self.logger.warning("Failed to create dxvk.conf file.")
|
||||||
print("Warning: Failed to create dxvk.conf file.")
|
print("Warning: Failed to create dxvk.conf file.")
|
||||||
|
|||||||
@@ -616,7 +616,8 @@ class ModlistInstallCLI:
|
|||||||
if machineid:
|
if machineid:
|
||||||
# Convert machineid to filename (e.g., "Tuxborn/Tuxborn" -> "Tuxborn.wabbajack")
|
# Convert machineid to filename (e.g., "Tuxborn/Tuxborn" -> "Tuxborn.wabbajack")
|
||||||
modlist_name = machineid.split('/')[-1] if '/' in machineid else machineid
|
modlist_name = machineid.split('/')[-1] if '/' in machineid else machineid
|
||||||
cached_wabbajack_path = os.path.expanduser(f"~/Jackify/downloaded_mod_lists/{modlist_name}.wabbajack")
|
from jackify.shared.paths import get_jackify_downloads_dir
|
||||||
|
cached_wabbajack_path = get_jackify_downloads_dir() / f"{modlist_name}.wabbajack"
|
||||||
self.logger.debug(f"Checking for cached .wabbajack file: {cached_wabbajack_path}")
|
self.logger.debug(f"Checking for cached .wabbajack file: {cached_wabbajack_path}")
|
||||||
|
|
||||||
if modlist_value and modlist_value.endswith('.wabbajack') and os.path.isfile(modlist_value):
|
if modlist_value and modlist_value.endswith('.wabbajack') and os.path.isfile(modlist_value):
|
||||||
|
|||||||
@@ -251,7 +251,7 @@ class PathHandler:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def create_dxvk_conf(modlist_dir, modlist_sdcard, steam_library, basegame_sdcard, game_var_full):
|
def create_dxvk_conf(modlist_dir, modlist_sdcard, steam_library, basegame_sdcard, game_var_full, vanilla_game_dir=None):
|
||||||
"""
|
"""
|
||||||
Create dxvk.conf file in the appropriate location
|
Create dxvk.conf file in the appropriate location
|
||||||
|
|
||||||
@@ -261,6 +261,7 @@ class PathHandler:
|
|||||||
steam_library (str): Path to the Steam library
|
steam_library (str): Path to the Steam library
|
||||||
basegame_sdcard (bool): Whether the base game is on an SD card
|
basegame_sdcard (bool): Whether the base game is on an SD card
|
||||||
game_var_full (str): Full name of the game (e.g., "Skyrim Special Edition")
|
game_var_full (str): Full name of the game (e.g., "Skyrim Special Edition")
|
||||||
|
vanilla_game_dir (str): Optional path to vanilla game directory for fallback
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
bool: True on success, False on failure
|
bool: True on success, False on failure
|
||||||
@@ -271,25 +272,35 @@ class PathHandler:
|
|||||||
# Determine the location for dxvk.conf
|
# Determine the location for dxvk.conf
|
||||||
dxvk_conf_path = None
|
dxvk_conf_path = None
|
||||||
|
|
||||||
# Check for common stock game directories
|
# Check for common stock game directories first, then vanilla as fallback
|
||||||
stock_game_paths = [
|
stock_game_paths = [
|
||||||
os.path.join(modlist_dir, "Stock Game"),
|
os.path.join(modlist_dir, "Stock Game"),
|
||||||
os.path.join(modlist_dir, "STOCK GAME"),
|
|
||||||
os.path.join(modlist_dir, "Game Root"),
|
os.path.join(modlist_dir, "Game Root"),
|
||||||
|
os.path.join(modlist_dir, "STOCK GAME"),
|
||||||
|
os.path.join(modlist_dir, "Stock Game Folder"),
|
||||||
os.path.join(modlist_dir, "Stock Folder"),
|
os.path.join(modlist_dir, "Stock Folder"),
|
||||||
os.path.join(modlist_dir, "Skyrim Stock"),
|
os.path.join(modlist_dir, "Skyrim Stock"),
|
||||||
os.path.join(modlist_dir, "root", "Skyrim Special Edition"),
|
os.path.join(modlist_dir, "root", "Skyrim Special Edition")
|
||||||
os.path.join(steam_library, game_var_full)
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Add vanilla game directory as fallback if steam_library and game_var_full are provided
|
||||||
|
if steam_library and game_var_full:
|
||||||
|
stock_game_paths.append(os.path.join(steam_library, "steamapps", "common", game_var_full))
|
||||||
|
|
||||||
for path in stock_game_paths:
|
for path in stock_game_paths:
|
||||||
if os.path.exists(path):
|
if os.path.exists(path):
|
||||||
dxvk_conf_path = os.path.join(path, "dxvk.conf")
|
dxvk_conf_path = os.path.join(path, "dxvk.conf")
|
||||||
break
|
break
|
||||||
|
|
||||||
if not dxvk_conf_path:
|
if not dxvk_conf_path:
|
||||||
logger.error("Could not determine location for dxvk.conf")
|
# Fallback: Try vanilla game directory if provided
|
||||||
return False
|
if vanilla_game_dir and os.path.exists(vanilla_game_dir):
|
||||||
|
logger.info(f"Attempting fallback to vanilla game directory: {vanilla_game_dir}")
|
||||||
|
dxvk_conf_path = os.path.join(vanilla_game_dir, "dxvk.conf")
|
||||||
|
logger.info(f"Using vanilla game directory for dxvk.conf: {dxvk_conf_path}")
|
||||||
|
else:
|
||||||
|
logger.error("Could not determine location for dxvk.conf")
|
||||||
|
return False
|
||||||
|
|
||||||
# The required line that Jackify needs
|
# The required line that Jackify needs
|
||||||
required_line = "dxvk.enableGraphicsPipelineLibrary = False"
|
required_line = "dxvk.enableGraphicsPipelineLibrary = False"
|
||||||
@@ -773,6 +784,21 @@ class PathHandler:
|
|||||||
return False
|
return False
|
||||||
with open(modlist_ini_path, 'r', encoding='utf-8') as f:
|
with open(modlist_ini_path, 'r', encoding='utf-8') as f:
|
||||||
lines = f.readlines()
|
lines = f.readlines()
|
||||||
|
|
||||||
|
# Extract existing gamePath to use as source of truth for vanilla game location
|
||||||
|
existing_game_path = None
|
||||||
|
for line in lines:
|
||||||
|
if re.match(r'^\s*gamepath\s*=.*@ByteArray\(([^)]+)\)', line, re.IGNORECASE):
|
||||||
|
match = re.search(r'@ByteArray\(([^)]+)\)', line)
|
||||||
|
if match:
|
||||||
|
raw_path = match.group(1)
|
||||||
|
# Convert Windows path back to Linux path
|
||||||
|
if raw_path.startswith(('Z:', 'D:')):
|
||||||
|
linux_path = raw_path[2:].replace('\\\\', '/').replace('\\', '/')
|
||||||
|
existing_game_path = linux_path
|
||||||
|
logger.debug(f"Extracted existing gamePath: {existing_game_path}")
|
||||||
|
break
|
||||||
|
|
||||||
game_path_updated = False
|
game_path_updated = False
|
||||||
binary_paths_updated = 0
|
binary_paths_updated = 0
|
||||||
working_dirs_updated = 0
|
working_dirs_updated = 0
|
||||||
@@ -791,9 +817,16 @@ class PathHandler:
|
|||||||
backslash_style = wd_match.group(2)
|
backslash_style = wd_match.group(2)
|
||||||
working_dir_lines.append((i, stripped, index, backslash_style))
|
working_dir_lines.append((i, stripped, index, backslash_style))
|
||||||
binary_paths_by_index = {}
|
binary_paths_by_index = {}
|
||||||
# Use provided steam_libraries if available, else detect
|
# Use existing gamePath to determine correct Steam library, fallback to detection
|
||||||
if steam_libraries is None or not steam_libraries:
|
if existing_game_path and '/steamapps/common/' in existing_game_path:
|
||||||
|
# Extract the Steam library root from the existing gamePath
|
||||||
|
steamapps_index = existing_game_path.find('/steamapps/common/')
|
||||||
|
steam_lib_root = existing_game_path[:steamapps_index]
|
||||||
|
steam_libraries = [Path(steam_lib_root)]
|
||||||
|
logger.info(f"Using Steam library from existing gamePath: {steam_lib_root}")
|
||||||
|
elif steam_libraries is None or not steam_libraries:
|
||||||
steam_libraries = PathHandler.get_all_steam_library_paths()
|
steam_libraries = PathHandler.get_all_steam_library_paths()
|
||||||
|
logger.debug(f"Fallback to detected Steam libraries: {steam_libraries}")
|
||||||
for i, line, index, backslash_style in binary_lines:
|
for i, line, index, backslash_style in binary_lines:
|
||||||
parts = line.split('=', 1)
|
parts = line.split('=', 1)
|
||||||
if len(parts) != 2:
|
if len(parts) != 2:
|
||||||
|
|||||||
@@ -149,7 +149,7 @@ class ResolutionHandler:
|
|||||||
return ["1280x720", "1280x800", "1920x1080", "1920x1200", "2560x1440"]
|
return ["1280x720", "1280x800", "1920x1080", "1920x1200", "2560x1440"]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def update_ini_resolution(modlist_dir: str, game_var: str, set_res: str) -> bool:
|
def update_ini_resolution(modlist_dir: str, game_var: str, set_res: str, vanilla_game_dir: str = None) -> bool:
|
||||||
"""
|
"""
|
||||||
Updates the resolution in relevant INI files for the specified game.
|
Updates the resolution in relevant INI files for the specified game.
|
||||||
|
|
||||||
@@ -157,6 +157,7 @@ class ResolutionHandler:
|
|||||||
modlist_dir (str): Path to the modlist directory.
|
modlist_dir (str): Path to the modlist directory.
|
||||||
game_var (str): The game identifier (e.g., "Skyrim Special Edition", "Fallout 4").
|
game_var (str): The game identifier (e.g., "Skyrim Special Edition", "Fallout 4").
|
||||||
set_res (str): The desired resolution (e.g., "1920x1080").
|
set_res (str): The desired resolution (e.g., "1920x1080").
|
||||||
|
vanilla_game_dir (str): Optional path to vanilla game directory for fallback.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
bool: True if successful or not applicable, False on error.
|
bool: True if successful or not applicable, False on error.
|
||||||
@@ -211,22 +212,30 @@ class ResolutionHandler:
|
|||||||
|
|
||||||
logger.debug(f"Processing {prefs_filenames}...")
|
logger.debug(f"Processing {prefs_filenames}...")
|
||||||
prefs_files_found = []
|
prefs_files_found = []
|
||||||
# Search common locations: profiles/, stock game dirs
|
# Search entire modlist directory recursively for all target files
|
||||||
search_dirs = [modlist_path / "profiles"]
|
logger.debug(f"Searching entire modlist directory for: {prefs_filenames}")
|
||||||
# Add potential stock game directories dynamically (case-insensitive)
|
for fname in prefs_filenames:
|
||||||
potential_stock_dirs = [d for d in modlist_path.iterdir() if d.is_dir() and
|
found_files = list(modlist_path.rglob(fname))
|
||||||
d.name.lower() in ["stock game", "game root", "stock folder", "skyrim stock"]] # Add more if needed
|
prefs_files_found.extend(found_files)
|
||||||
search_dirs.extend(potential_stock_dirs)
|
if found_files:
|
||||||
|
logger.debug(f"Found {len(found_files)} {fname} files: {[str(f) for f in found_files]}")
|
||||||
for search_dir in search_dirs:
|
|
||||||
if search_dir.is_dir():
|
|
||||||
for fname in prefs_filenames:
|
|
||||||
prefs_files_found.extend(list(search_dir.rglob(fname)))
|
|
||||||
|
|
||||||
if not prefs_files_found:
|
if not prefs_files_found:
|
||||||
logger.warning(f"No preference files ({prefs_filenames}) found in standard locations ({search_dirs}). Manual INI edit might be needed.")
|
logger.warning(f"No preference files ({prefs_filenames}) found in modlist directory.")
|
||||||
# Consider this success as the main operation didn't fail?
|
|
||||||
return True
|
# Fallback: Try vanilla game directory if provided
|
||||||
|
if vanilla_game_dir:
|
||||||
|
logger.info(f"Attempting fallback to vanilla game directory: {vanilla_game_dir}")
|
||||||
|
vanilla_path = Path(vanilla_game_dir)
|
||||||
|
for fname in prefs_filenames:
|
||||||
|
vanilla_files = list(vanilla_path.rglob(fname))
|
||||||
|
prefs_files_found.extend(vanilla_files)
|
||||||
|
if vanilla_files:
|
||||||
|
logger.info(f"Found {len(vanilla_files)} {fname} files in vanilla game directory")
|
||||||
|
|
||||||
|
if not prefs_files_found:
|
||||||
|
logger.warning("No preference files found in modlist or vanilla game directory. Manual INI edit might be needed.")
|
||||||
|
return True
|
||||||
|
|
||||||
for ini_file in prefs_files_found:
|
for ini_file in prefs_files_found:
|
||||||
files_processed += 1
|
files_processed += 1
|
||||||
@@ -314,19 +323,23 @@ class ResolutionHandler:
|
|||||||
|
|
||||||
new_lines = []
|
new_lines = []
|
||||||
modified = False
|
modified = False
|
||||||
# Prepare the replacement strings for width and height
|
|
||||||
# Ensure correct spacing for Oblivion vs other games
|
|
||||||
# Corrected f-string syntax for conditional expression
|
|
||||||
equals_operator = "=" if is_oblivion else " = "
|
|
||||||
width_replace = f"iSize W{equals_operator}{width}\n"
|
|
||||||
height_replace = f"iSize H{equals_operator}{height}\n"
|
|
||||||
|
|
||||||
for line in lines:
|
for line in lines:
|
||||||
stripped_line = line.strip()
|
stripped_line = line.strip()
|
||||||
if stripped_line.lower().endswith("isize w"):
|
if stripped_line.lower().startswith("isize w"):
|
||||||
|
# Preserve original spacing around equals sign
|
||||||
|
if " = " in stripped_line:
|
||||||
|
width_replace = f"iSize W = {width}\n"
|
||||||
|
else:
|
||||||
|
width_replace = f"iSize W={width}\n"
|
||||||
new_lines.append(width_replace)
|
new_lines.append(width_replace)
|
||||||
modified = True
|
modified = True
|
||||||
elif stripped_line.lower().endswith("isize h"):
|
elif stripped_line.lower().startswith("isize h"):
|
||||||
|
# Preserve original spacing around equals sign
|
||||||
|
if " = " in stripped_line:
|
||||||
|
height_replace = f"iSize H = {height}\n"
|
||||||
|
else:
|
||||||
|
height_replace = f"iSize H={height}\n"
|
||||||
new_lines.append(height_replace)
|
new_lines.append(height_replace)
|
||||||
modified = True
|
modified = True
|
||||||
else:
|
else:
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -7,7 +7,7 @@
|
|||||||
"targets": {
|
"targets": {
|
||||||
".NETCoreApp,Version=v8.0": {},
|
".NETCoreApp,Version=v8.0": {},
|
||||||
".NETCoreApp,Version=v8.0/linux-x64": {
|
".NETCoreApp,Version=v8.0/linux-x64": {
|
||||||
"jackify-engine/0.3.13": {
|
"jackify-engine/0.3.14": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Markdig": "0.40.0",
|
"Markdig": "0.40.0",
|
||||||
"Microsoft.Extensions.Configuration.Json": "9.0.1",
|
"Microsoft.Extensions.Configuration.Json": "9.0.1",
|
||||||
@@ -22,16 +22,16 @@
|
|||||||
"SixLabors.ImageSharp": "3.1.6",
|
"SixLabors.ImageSharp": "3.1.6",
|
||||||
"System.CommandLine": "2.0.0-beta4.22272.1",
|
"System.CommandLine": "2.0.0-beta4.22272.1",
|
||||||
"System.CommandLine.NamingConventionBinder": "2.0.0-beta4.22272.1",
|
"System.CommandLine.NamingConventionBinder": "2.0.0-beta4.22272.1",
|
||||||
"Wabbajack.CLI.Builder": "0.3.13",
|
"Wabbajack.CLI.Builder": "0.3.14",
|
||||||
"Wabbajack.Downloaders.Bethesda": "0.3.13",
|
"Wabbajack.Downloaders.Bethesda": "0.3.14",
|
||||||
"Wabbajack.Downloaders.Dispatcher": "0.3.13",
|
"Wabbajack.Downloaders.Dispatcher": "0.3.14",
|
||||||
"Wabbajack.Hashing.xxHash64": "0.3.13",
|
"Wabbajack.Hashing.xxHash64": "0.3.14",
|
||||||
"Wabbajack.Networking.Discord": "0.3.13",
|
"Wabbajack.Networking.Discord": "0.3.14",
|
||||||
"Wabbajack.Networking.GitHub": "0.3.13",
|
"Wabbajack.Networking.GitHub": "0.3.14",
|
||||||
"Wabbajack.Paths.IO": "0.3.13",
|
"Wabbajack.Paths.IO": "0.3.14",
|
||||||
"Wabbajack.Server.Lib": "0.3.13",
|
"Wabbajack.Server.Lib": "0.3.14",
|
||||||
"Wabbajack.Services.OSIntegrated": "0.3.13",
|
"Wabbajack.Services.OSIntegrated": "0.3.14",
|
||||||
"Wabbajack.VFS": "0.3.13",
|
"Wabbajack.VFS": "0.3.14",
|
||||||
"MegaApiClient": "1.0.0.0",
|
"MegaApiClient": "1.0.0.0",
|
||||||
"runtimepack.Microsoft.NETCore.App.Runtime.linux-x64": "8.0.19"
|
"runtimepack.Microsoft.NETCore.App.Runtime.linux-x64": "8.0.19"
|
||||||
},
|
},
|
||||||
@@ -1781,7 +1781,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.CLI.Builder/0.3.13": {
|
"Wabbajack.CLI.Builder/0.3.14": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.Configuration.Json": "9.0.1",
|
"Microsoft.Extensions.Configuration.Json": "9.0.1",
|
||||||
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
||||||
@@ -1791,109 +1791,109 @@
|
|||||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
||||||
"System.CommandLine": "2.0.0-beta4.22272.1",
|
"System.CommandLine": "2.0.0-beta4.22272.1",
|
||||||
"System.CommandLine.NamingConventionBinder": "2.0.0-beta4.22272.1",
|
"System.CommandLine.NamingConventionBinder": "2.0.0-beta4.22272.1",
|
||||||
"Wabbajack.Paths": "0.3.13"
|
"Wabbajack.Paths": "0.3.14"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.CLI.Builder.dll": {}
|
"Wabbajack.CLI.Builder.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.Common/0.3.13": {
|
"Wabbajack.Common/0.3.14": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
||||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
||||||
"System.Reactive": "6.0.1",
|
"System.Reactive": "6.0.1",
|
||||||
"Wabbajack.DTOs": "0.3.13",
|
"Wabbajack.DTOs": "0.3.14",
|
||||||
"Wabbajack.Networking.Http": "0.3.13",
|
"Wabbajack.Networking.Http": "0.3.14",
|
||||||
"Wabbajack.Paths.IO": "0.3.13"
|
"Wabbajack.Paths.IO": "0.3.14"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.Common.dll": {}
|
"Wabbajack.Common.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.Compiler/0.3.13": {
|
"Wabbajack.Compiler/0.3.14": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"F23.StringSimilarity": "6.0.0",
|
"F23.StringSimilarity": "6.0.0",
|
||||||
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
||||||
"Newtonsoft.Json": "13.0.3",
|
"Newtonsoft.Json": "13.0.3",
|
||||||
"SixLabors.ImageSharp": "3.1.6",
|
"SixLabors.ImageSharp": "3.1.6",
|
||||||
"Wabbajack.Downloaders.Dispatcher": "0.3.13",
|
"Wabbajack.Downloaders.Dispatcher": "0.3.14",
|
||||||
"Wabbajack.Installer": "0.3.13",
|
"Wabbajack.Installer": "0.3.14",
|
||||||
"Wabbajack.VFS": "0.3.13",
|
"Wabbajack.VFS": "0.3.14",
|
||||||
"ini-parser-netstandard": "2.5.2"
|
"ini-parser-netstandard": "2.5.2"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.Compiler.dll": {}
|
"Wabbajack.Compiler.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.Compression.BSA/0.3.13": {
|
"Wabbajack.Compression.BSA/0.3.14": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"K4os.Compression.LZ4.Streams": "1.3.8",
|
"K4os.Compression.LZ4.Streams": "1.3.8",
|
||||||
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
||||||
"SharpZipLib": "1.4.2",
|
"SharpZipLib": "1.4.2",
|
||||||
"Wabbajack.Common": "0.3.13",
|
"Wabbajack.Common": "0.3.14",
|
||||||
"Wabbajack.DTOs": "0.3.13"
|
"Wabbajack.DTOs": "0.3.14"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.Compression.BSA.dll": {}
|
"Wabbajack.Compression.BSA.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.Compression.Zip/0.3.13": {
|
"Wabbajack.Compression.Zip/0.3.14": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Wabbajack.IO.Async": "0.3.13"
|
"Wabbajack.IO.Async": "0.3.14"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.Compression.Zip.dll": {}
|
"Wabbajack.Compression.Zip.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.Configuration/0.3.13": {
|
"Wabbajack.Configuration/0.3.14": {
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.Configuration.dll": {}
|
"Wabbajack.Configuration.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.Downloaders.Bethesda/0.3.13": {
|
"Wabbajack.Downloaders.Bethesda/0.3.14": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"LibAES-CTR": "1.1.0",
|
"LibAES-CTR": "1.1.0",
|
||||||
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
||||||
"SharpZipLib": "1.4.2",
|
"SharpZipLib": "1.4.2",
|
||||||
"Wabbajack.Common": "0.3.13",
|
"Wabbajack.Common": "0.3.14",
|
||||||
"Wabbajack.Downloaders.Interfaces": "0.3.13",
|
"Wabbajack.Downloaders.Interfaces": "0.3.14",
|
||||||
"Wabbajack.Networking.BethesdaNet": "0.3.13"
|
"Wabbajack.Networking.BethesdaNet": "0.3.14"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.Downloaders.Bethesda.dll": {}
|
"Wabbajack.Downloaders.Bethesda.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.Downloaders.Dispatcher/0.3.13": {
|
"Wabbajack.Downloaders.Dispatcher/0.3.14": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
||||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
||||||
"Newtonsoft.Json": "13.0.3",
|
"Newtonsoft.Json": "13.0.3",
|
||||||
"SixLabors.ImageSharp": "3.1.6",
|
"SixLabors.ImageSharp": "3.1.6",
|
||||||
"Wabbajack.Downloaders.Bethesda": "0.3.13",
|
"Wabbajack.Downloaders.Bethesda": "0.3.14",
|
||||||
"Wabbajack.Downloaders.GameFile": "0.3.13",
|
"Wabbajack.Downloaders.GameFile": "0.3.14",
|
||||||
"Wabbajack.Downloaders.GoogleDrive": "0.3.13",
|
"Wabbajack.Downloaders.GoogleDrive": "0.3.14",
|
||||||
"Wabbajack.Downloaders.Http": "0.3.13",
|
"Wabbajack.Downloaders.Http": "0.3.14",
|
||||||
"Wabbajack.Downloaders.IPS4OAuth2Downloader": "0.3.13",
|
"Wabbajack.Downloaders.IPS4OAuth2Downloader": "0.3.14",
|
||||||
"Wabbajack.Downloaders.Interfaces": "0.3.13",
|
"Wabbajack.Downloaders.Interfaces": "0.3.14",
|
||||||
"Wabbajack.Downloaders.Manual": "0.3.13",
|
"Wabbajack.Downloaders.Manual": "0.3.14",
|
||||||
"Wabbajack.Downloaders.MediaFire": "0.3.13",
|
"Wabbajack.Downloaders.MediaFire": "0.3.14",
|
||||||
"Wabbajack.Downloaders.Mega": "0.3.13",
|
"Wabbajack.Downloaders.Mega": "0.3.14",
|
||||||
"Wabbajack.Downloaders.ModDB": "0.3.13",
|
"Wabbajack.Downloaders.ModDB": "0.3.14",
|
||||||
"Wabbajack.Downloaders.Nexus": "0.3.13",
|
"Wabbajack.Downloaders.Nexus": "0.3.14",
|
||||||
"Wabbajack.Downloaders.VerificationCache": "0.3.13",
|
"Wabbajack.Downloaders.VerificationCache": "0.3.14",
|
||||||
"Wabbajack.Downloaders.WabbajackCDN": "0.3.13",
|
"Wabbajack.Downloaders.WabbajackCDN": "0.3.14",
|
||||||
"Wabbajack.Networking.WabbajackClientApi": "0.3.13"
|
"Wabbajack.Networking.WabbajackClientApi": "0.3.14"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.Downloaders.Dispatcher.dll": {}
|
"Wabbajack.Downloaders.Dispatcher.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.Downloaders.GameFile/0.3.13": {
|
"Wabbajack.Downloaders.GameFile/0.3.14": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"GameFinder.StoreHandlers.EADesktop": "4.5.0",
|
"GameFinder.StoreHandlers.EADesktop": "4.5.0",
|
||||||
"GameFinder.StoreHandlers.EGS": "4.5.0",
|
"GameFinder.StoreHandlers.EGS": "4.5.0",
|
||||||
@@ -1903,360 +1903,360 @@
|
|||||||
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
||||||
"SixLabors.ImageSharp": "3.1.6",
|
"SixLabors.ImageSharp": "3.1.6",
|
||||||
"Wabbajack.Downloaders.Interfaces": "0.3.13",
|
"Wabbajack.Downloaders.Interfaces": "0.3.14",
|
||||||
"Wabbajack.VFS": "0.3.13"
|
"Wabbajack.VFS": "0.3.14"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.Downloaders.GameFile.dll": {}
|
"Wabbajack.Downloaders.GameFile.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.Downloaders.GoogleDrive/0.3.13": {
|
"Wabbajack.Downloaders.GoogleDrive/0.3.14": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"HtmlAgilityPack": "1.11.72",
|
"HtmlAgilityPack": "1.11.72",
|
||||||
"Microsoft.AspNetCore.Http.Extensions": "2.3.0",
|
"Microsoft.AspNetCore.Http.Extensions": "2.3.0",
|
||||||
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
||||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
||||||
"Wabbajack.Common": "0.3.13",
|
"Wabbajack.Common": "0.3.14",
|
||||||
"Wabbajack.DTOs": "0.3.13",
|
"Wabbajack.DTOs": "0.3.14",
|
||||||
"Wabbajack.Downloaders.Interfaces": "0.3.13",
|
"Wabbajack.Downloaders.Interfaces": "0.3.14",
|
||||||
"Wabbajack.Networking.Http": "0.3.13",
|
"Wabbajack.Networking.Http": "0.3.14",
|
||||||
"Wabbajack.Networking.Http.Interfaces": "0.3.13"
|
"Wabbajack.Networking.Http.Interfaces": "0.3.14"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.Downloaders.GoogleDrive.dll": {}
|
"Wabbajack.Downloaders.GoogleDrive.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.Downloaders.Http/0.3.13": {
|
"Wabbajack.Downloaders.Http/0.3.14": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
||||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
||||||
"Wabbajack.Common": "0.3.13",
|
"Wabbajack.Common": "0.3.14",
|
||||||
"Wabbajack.DTOs": "0.3.13",
|
"Wabbajack.DTOs": "0.3.14",
|
||||||
"Wabbajack.Downloaders.Interfaces": "0.3.13",
|
"Wabbajack.Downloaders.Interfaces": "0.3.14",
|
||||||
"Wabbajack.Networking.BethesdaNet": "0.3.13",
|
"Wabbajack.Networking.BethesdaNet": "0.3.14",
|
||||||
"Wabbajack.Networking.Http.Interfaces": "0.3.13",
|
"Wabbajack.Networking.Http.Interfaces": "0.3.14",
|
||||||
"Wabbajack.Paths.IO": "0.3.13"
|
"Wabbajack.Paths.IO": "0.3.14"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.Downloaders.Http.dll": {}
|
"Wabbajack.Downloaders.Http.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.Downloaders.Interfaces/0.3.13": {
|
"Wabbajack.Downloaders.Interfaces/0.3.14": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
||||||
"Wabbajack.Compression.Zip": "0.3.13",
|
"Wabbajack.Compression.Zip": "0.3.14",
|
||||||
"Wabbajack.DTOs": "0.3.13",
|
"Wabbajack.DTOs": "0.3.14",
|
||||||
"Wabbajack.Paths.IO": "0.3.13"
|
"Wabbajack.Paths.IO": "0.3.14"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.Downloaders.Interfaces.dll": {}
|
"Wabbajack.Downloaders.Interfaces.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.Downloaders.IPS4OAuth2Downloader/0.3.13": {
|
"Wabbajack.Downloaders.IPS4OAuth2Downloader/0.3.14": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"F23.StringSimilarity": "6.0.0",
|
"F23.StringSimilarity": "6.0.0",
|
||||||
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
||||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
||||||
"Wabbajack.Common": "0.3.13",
|
"Wabbajack.Common": "0.3.14",
|
||||||
"Wabbajack.Downloaders.Interfaces": "0.3.13",
|
"Wabbajack.Downloaders.Interfaces": "0.3.14",
|
||||||
"Wabbajack.Networking.Http": "0.3.13",
|
"Wabbajack.Networking.Http": "0.3.14",
|
||||||
"Wabbajack.Networking.Http.Interfaces": "0.3.13"
|
"Wabbajack.Networking.Http.Interfaces": "0.3.14"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.Downloaders.IPS4OAuth2Downloader.dll": {}
|
"Wabbajack.Downloaders.IPS4OAuth2Downloader.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.Downloaders.Manual/0.3.13": {
|
"Wabbajack.Downloaders.Manual/0.3.14": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
||||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
||||||
"Wabbajack.Common": "0.3.13",
|
"Wabbajack.Common": "0.3.14",
|
||||||
"Wabbajack.Downloaders.Interfaces": "0.3.13"
|
"Wabbajack.Downloaders.Interfaces": "0.3.14"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.Downloaders.Manual.dll": {}
|
"Wabbajack.Downloaders.Manual.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.Downloaders.MediaFire/0.3.13": {
|
"Wabbajack.Downloaders.MediaFire/0.3.14": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"HtmlAgilityPack": "1.11.72",
|
"HtmlAgilityPack": "1.11.72",
|
||||||
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
||||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
||||||
"Wabbajack.Common": "0.3.13",
|
"Wabbajack.Common": "0.3.14",
|
||||||
"Wabbajack.Downloaders.Interfaces": "0.3.13",
|
"Wabbajack.Downloaders.Interfaces": "0.3.14",
|
||||||
"Wabbajack.Networking.Http.Interfaces": "0.3.13"
|
"Wabbajack.Networking.Http.Interfaces": "0.3.14"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.Downloaders.MediaFire.dll": {}
|
"Wabbajack.Downloaders.MediaFire.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.Downloaders.Mega/0.3.13": {
|
"Wabbajack.Downloaders.Mega/0.3.14": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
||||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
||||||
"Newtonsoft.Json": "13.0.3",
|
"Newtonsoft.Json": "13.0.3",
|
||||||
"Wabbajack.Common": "0.3.13",
|
"Wabbajack.Common": "0.3.14",
|
||||||
"Wabbajack.Downloaders.Interfaces": "0.3.13",
|
"Wabbajack.Downloaders.Interfaces": "0.3.14",
|
||||||
"Wabbajack.Paths.IO": "0.3.13"
|
"Wabbajack.Paths.IO": "0.3.14"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.Downloaders.Mega.dll": {}
|
"Wabbajack.Downloaders.Mega.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.Downloaders.ModDB/0.3.13": {
|
"Wabbajack.Downloaders.ModDB/0.3.14": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"HtmlAgilityPack": "1.11.72",
|
"HtmlAgilityPack": "1.11.72",
|
||||||
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
||||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
||||||
"Newtonsoft.Json": "13.0.3",
|
"Newtonsoft.Json": "13.0.3",
|
||||||
"Wabbajack.Common": "0.3.13",
|
"Wabbajack.Common": "0.3.14",
|
||||||
"Wabbajack.Downloaders.Interfaces": "0.3.13",
|
"Wabbajack.Downloaders.Interfaces": "0.3.14",
|
||||||
"Wabbajack.Networking.Http": "0.3.13",
|
"Wabbajack.Networking.Http": "0.3.14",
|
||||||
"Wabbajack.Networking.Http.Interfaces": "0.3.13"
|
"Wabbajack.Networking.Http.Interfaces": "0.3.14"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.Downloaders.ModDB.dll": {}
|
"Wabbajack.Downloaders.ModDB.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.Downloaders.Nexus/0.3.13": {
|
"Wabbajack.Downloaders.Nexus/0.3.14": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
||||||
"Wabbajack.DTOs": "0.3.13",
|
"Wabbajack.DTOs": "0.3.14",
|
||||||
"Wabbajack.Downloaders.Interfaces": "0.3.13",
|
"Wabbajack.Downloaders.Interfaces": "0.3.14",
|
||||||
"Wabbajack.Hashing.xxHash64": "0.3.13",
|
"Wabbajack.Hashing.xxHash64": "0.3.14",
|
||||||
"Wabbajack.Networking.Http": "0.3.13",
|
"Wabbajack.Networking.Http": "0.3.14",
|
||||||
"Wabbajack.Networking.Http.Interfaces": "0.3.13",
|
"Wabbajack.Networking.Http.Interfaces": "0.3.14",
|
||||||
"Wabbajack.Networking.NexusApi": "0.3.13",
|
"Wabbajack.Networking.NexusApi": "0.3.14",
|
||||||
"Wabbajack.Paths": "0.3.13"
|
"Wabbajack.Paths": "0.3.14"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.Downloaders.Nexus.dll": {}
|
"Wabbajack.Downloaders.Nexus.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.Downloaders.VerificationCache/0.3.13": {
|
"Wabbajack.Downloaders.VerificationCache/0.3.14": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
||||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
||||||
"Stub.System.Data.SQLite.Core.NetStandard": "1.0.119",
|
"Stub.System.Data.SQLite.Core.NetStandard": "1.0.119",
|
||||||
"Wabbajack.DTOs": "0.3.13",
|
"Wabbajack.DTOs": "0.3.14",
|
||||||
"Wabbajack.Paths.IO": "0.3.13"
|
"Wabbajack.Paths.IO": "0.3.14"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.Downloaders.VerificationCache.dll": {}
|
"Wabbajack.Downloaders.VerificationCache.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.Downloaders.WabbajackCDN/0.3.13": {
|
"Wabbajack.Downloaders.WabbajackCDN/0.3.14": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
||||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
||||||
"Microsoft.Toolkit.HighPerformance": "7.1.2",
|
"Microsoft.Toolkit.HighPerformance": "7.1.2",
|
||||||
"Wabbajack.Common": "0.3.13",
|
"Wabbajack.Common": "0.3.14",
|
||||||
"Wabbajack.Downloaders.Interfaces": "0.3.13",
|
"Wabbajack.Downloaders.Interfaces": "0.3.14",
|
||||||
"Wabbajack.Networking.Http": "0.3.13",
|
"Wabbajack.Networking.Http": "0.3.14",
|
||||||
"Wabbajack.RateLimiter": "0.3.13"
|
"Wabbajack.RateLimiter": "0.3.14"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.Downloaders.WabbajackCDN.dll": {}
|
"Wabbajack.Downloaders.WabbajackCDN.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.DTOs/0.3.13": {
|
"Wabbajack.DTOs/0.3.14": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
||||||
"Wabbajack.Hashing.xxHash64": "0.3.13",
|
"Wabbajack.Hashing.xxHash64": "0.3.14",
|
||||||
"Wabbajack.Paths": "0.3.13"
|
"Wabbajack.Paths": "0.3.14"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.DTOs.dll": {}
|
"Wabbajack.DTOs.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.FileExtractor/0.3.13": {
|
"Wabbajack.FileExtractor/0.3.14": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
||||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
||||||
"OMODFramework": "3.0.1",
|
"OMODFramework": "3.0.1",
|
||||||
"Wabbajack.Common": "0.3.13",
|
"Wabbajack.Common": "0.3.14",
|
||||||
"Wabbajack.Compression.BSA": "0.3.13",
|
"Wabbajack.Compression.BSA": "0.3.14",
|
||||||
"Wabbajack.Hashing.PHash": "0.3.13",
|
"Wabbajack.Hashing.PHash": "0.3.14",
|
||||||
"Wabbajack.Paths": "0.3.13"
|
"Wabbajack.Paths": "0.3.14"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.FileExtractor.dll": {}
|
"Wabbajack.FileExtractor.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.Hashing.PHash/0.3.13": {
|
"Wabbajack.Hashing.PHash/0.3.14": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"BCnEncoder.Net.ImageSharp": "1.1.1",
|
"BCnEncoder.Net.ImageSharp": "1.1.1",
|
||||||
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
||||||
"Shipwreck.Phash": "0.5.0",
|
"Shipwreck.Phash": "0.5.0",
|
||||||
"SixLabors.ImageSharp": "3.1.6",
|
"SixLabors.ImageSharp": "3.1.6",
|
||||||
"Wabbajack.Common": "0.3.13",
|
"Wabbajack.Common": "0.3.14",
|
||||||
"Wabbajack.DTOs": "0.3.13",
|
"Wabbajack.DTOs": "0.3.14",
|
||||||
"Wabbajack.Paths": "0.3.13",
|
"Wabbajack.Paths": "0.3.14",
|
||||||
"Wabbajack.Paths.IO": "0.3.13"
|
"Wabbajack.Paths.IO": "0.3.14"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.Hashing.PHash.dll": {}
|
"Wabbajack.Hashing.PHash.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.Hashing.xxHash64/0.3.13": {
|
"Wabbajack.Hashing.xxHash64/0.3.14": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Wabbajack.Paths": "0.3.13",
|
"Wabbajack.Paths": "0.3.14",
|
||||||
"Wabbajack.RateLimiter": "0.3.13"
|
"Wabbajack.RateLimiter": "0.3.14"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.Hashing.xxHash64.dll": {}
|
"Wabbajack.Hashing.xxHash64.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.Installer/0.3.13": {
|
"Wabbajack.Installer/0.3.14": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
||||||
"Newtonsoft.Json": "13.0.3",
|
"Newtonsoft.Json": "13.0.3",
|
||||||
"Octopus.Octodiff": "2.0.548",
|
"Octopus.Octodiff": "2.0.548",
|
||||||
"SixLabors.ImageSharp": "3.1.6",
|
"SixLabors.ImageSharp": "3.1.6",
|
||||||
"Wabbajack.DTOs": "0.3.13",
|
"Wabbajack.DTOs": "0.3.14",
|
||||||
"Wabbajack.Downloaders.Dispatcher": "0.3.13",
|
"Wabbajack.Downloaders.Dispatcher": "0.3.14",
|
||||||
"Wabbajack.Downloaders.GameFile": "0.3.13",
|
"Wabbajack.Downloaders.GameFile": "0.3.14",
|
||||||
"Wabbajack.FileExtractor": "0.3.13",
|
"Wabbajack.FileExtractor": "0.3.14",
|
||||||
"Wabbajack.Networking.WabbajackClientApi": "0.3.13",
|
"Wabbajack.Networking.WabbajackClientApi": "0.3.14",
|
||||||
"Wabbajack.Paths": "0.3.13",
|
"Wabbajack.Paths": "0.3.14",
|
||||||
"Wabbajack.Paths.IO": "0.3.13",
|
"Wabbajack.Paths.IO": "0.3.14",
|
||||||
"Wabbajack.VFS": "0.3.13",
|
"Wabbajack.VFS": "0.3.14",
|
||||||
"ini-parser-netstandard": "2.5.2"
|
"ini-parser-netstandard": "2.5.2"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.Installer.dll": {}
|
"Wabbajack.Installer.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.IO.Async/0.3.13": {
|
"Wabbajack.IO.Async/0.3.14": {
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.IO.Async.dll": {}
|
"Wabbajack.IO.Async.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.Networking.BethesdaNet/0.3.13": {
|
"Wabbajack.Networking.BethesdaNet/0.3.14": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
||||||
"Wabbajack.DTOs": "0.3.13",
|
"Wabbajack.DTOs": "0.3.14",
|
||||||
"Wabbajack.Networking.Http": "0.3.13",
|
"Wabbajack.Networking.Http": "0.3.14",
|
||||||
"Wabbajack.Networking.Http.Interfaces": "0.3.13"
|
"Wabbajack.Networking.Http.Interfaces": "0.3.14"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.Networking.BethesdaNet.dll": {}
|
"Wabbajack.Networking.BethesdaNet.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.Networking.Discord/0.3.13": {
|
"Wabbajack.Networking.Discord/0.3.14": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
||||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
||||||
"Wabbajack.Networking.Http.Interfaces": "0.3.13"
|
"Wabbajack.Networking.Http.Interfaces": "0.3.14"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.Networking.Discord.dll": {}
|
"Wabbajack.Networking.Discord.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.Networking.GitHub/0.3.13": {
|
"Wabbajack.Networking.GitHub/0.3.14": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
||||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
||||||
"Octokit": "14.0.0",
|
"Octokit": "14.0.0",
|
||||||
"Wabbajack.DTOs": "0.3.13",
|
"Wabbajack.DTOs": "0.3.14",
|
||||||
"Wabbajack.Networking.Http.Interfaces": "0.3.13"
|
"Wabbajack.Networking.Http.Interfaces": "0.3.14"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.Networking.GitHub.dll": {}
|
"Wabbajack.Networking.GitHub.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.Networking.Http/0.3.13": {
|
"Wabbajack.Networking.Http/0.3.14": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
||||||
"Microsoft.Extensions.Http": "9.0.1",
|
"Microsoft.Extensions.Http": "9.0.1",
|
||||||
"Microsoft.Extensions.Logging": "9.0.1",
|
"Microsoft.Extensions.Logging": "9.0.1",
|
||||||
"Wabbajack.Configuration": "0.3.13",
|
"Wabbajack.Configuration": "0.3.14",
|
||||||
"Wabbajack.Downloaders.Interfaces": "0.3.13",
|
"Wabbajack.Downloaders.Interfaces": "0.3.14",
|
||||||
"Wabbajack.Hashing.xxHash64": "0.3.13",
|
"Wabbajack.Hashing.xxHash64": "0.3.14",
|
||||||
"Wabbajack.Networking.Http.Interfaces": "0.3.13",
|
"Wabbajack.Networking.Http.Interfaces": "0.3.14",
|
||||||
"Wabbajack.Paths": "0.3.13",
|
"Wabbajack.Paths": "0.3.14",
|
||||||
"Wabbajack.Paths.IO": "0.3.13"
|
"Wabbajack.Paths.IO": "0.3.14"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.Networking.Http.dll": {}
|
"Wabbajack.Networking.Http.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.Networking.Http.Interfaces/0.3.13": {
|
"Wabbajack.Networking.Http.Interfaces/0.3.14": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Wabbajack.Hashing.xxHash64": "0.3.13"
|
"Wabbajack.Hashing.xxHash64": "0.3.14"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.Networking.Http.Interfaces.dll": {}
|
"Wabbajack.Networking.Http.Interfaces.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.Networking.NexusApi/0.3.13": {
|
"Wabbajack.Networking.NexusApi/0.3.14": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
||||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
||||||
"Wabbajack.DTOs": "0.3.13",
|
"Wabbajack.DTOs": "0.3.14",
|
||||||
"Wabbajack.Networking.Http": "0.3.13",
|
"Wabbajack.Networking.Http": "0.3.14",
|
||||||
"Wabbajack.Networking.Http.Interfaces": "0.3.13",
|
"Wabbajack.Networking.Http.Interfaces": "0.3.14",
|
||||||
"Wabbajack.Networking.WabbajackClientApi": "0.3.13"
|
"Wabbajack.Networking.WabbajackClientApi": "0.3.14"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.Networking.NexusApi.dll": {}
|
"Wabbajack.Networking.NexusApi.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.Networking.WabbajackClientApi/0.3.13": {
|
"Wabbajack.Networking.WabbajackClientApi/0.3.14": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
||||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
||||||
"Octokit": "14.0.0",
|
"Octokit": "14.0.0",
|
||||||
"Wabbajack.Common": "0.3.13",
|
"Wabbajack.Common": "0.3.14",
|
||||||
"Wabbajack.DTOs": "0.3.13",
|
"Wabbajack.DTOs": "0.3.14",
|
||||||
"Wabbajack.Paths.IO": "0.3.13",
|
"Wabbajack.Paths.IO": "0.3.14",
|
||||||
"Wabbajack.VFS.Interfaces": "0.3.13",
|
"Wabbajack.VFS.Interfaces": "0.3.14",
|
||||||
"YamlDotNet": "16.3.0"
|
"YamlDotNet": "16.3.0"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.Networking.WabbajackClientApi.dll": {}
|
"Wabbajack.Networking.WabbajackClientApi.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.Paths/0.3.13": {
|
"Wabbajack.Paths/0.3.14": {
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.Paths.dll": {}
|
"Wabbajack.Paths.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.Paths.IO/0.3.13": {
|
"Wabbajack.Paths.IO/0.3.14": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Wabbajack.Paths": "0.3.13",
|
"Wabbajack.Paths": "0.3.14",
|
||||||
"shortid": "4.0.0"
|
"shortid": "4.0.0"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.Paths.IO.dll": {}
|
"Wabbajack.Paths.IO.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.RateLimiter/0.3.13": {
|
"Wabbajack.RateLimiter/0.3.14": {
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.RateLimiter.dll": {}
|
"Wabbajack.RateLimiter.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.Server.Lib/0.3.13": {
|
"Wabbajack.Server.Lib/0.3.14": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"FluentFTP": "52.0.0",
|
"FluentFTP": "52.0.0",
|
||||||
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
||||||
@@ -2264,58 +2264,58 @@
|
|||||||
"Nettle": "3.0.0",
|
"Nettle": "3.0.0",
|
||||||
"Newtonsoft.Json": "13.0.3",
|
"Newtonsoft.Json": "13.0.3",
|
||||||
"SixLabors.ImageSharp": "3.1.6",
|
"SixLabors.ImageSharp": "3.1.6",
|
||||||
"Wabbajack.Common": "0.3.13",
|
"Wabbajack.Common": "0.3.14",
|
||||||
"Wabbajack.Networking.Http.Interfaces": "0.3.13",
|
"Wabbajack.Networking.Http.Interfaces": "0.3.14",
|
||||||
"Wabbajack.Services.OSIntegrated": "0.3.13"
|
"Wabbajack.Services.OSIntegrated": "0.3.14"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.Server.Lib.dll": {}
|
"Wabbajack.Server.Lib.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.Services.OSIntegrated/0.3.13": {
|
"Wabbajack.Services.OSIntegrated/0.3.14": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"DeviceId": "6.8.0",
|
"DeviceId": "6.8.0",
|
||||||
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
||||||
"Newtonsoft.Json": "13.0.3",
|
"Newtonsoft.Json": "13.0.3",
|
||||||
"SixLabors.ImageSharp": "3.1.6",
|
"SixLabors.ImageSharp": "3.1.6",
|
||||||
"Wabbajack.Compiler": "0.3.13",
|
"Wabbajack.Compiler": "0.3.14",
|
||||||
"Wabbajack.Downloaders.Dispatcher": "0.3.13",
|
"Wabbajack.Downloaders.Dispatcher": "0.3.14",
|
||||||
"Wabbajack.Installer": "0.3.13",
|
"Wabbajack.Installer": "0.3.14",
|
||||||
"Wabbajack.Networking.BethesdaNet": "0.3.13",
|
"Wabbajack.Networking.BethesdaNet": "0.3.14",
|
||||||
"Wabbajack.Networking.Discord": "0.3.13",
|
"Wabbajack.Networking.Discord": "0.3.14",
|
||||||
"Wabbajack.VFS": "0.3.13"
|
"Wabbajack.VFS": "0.3.14"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.Services.OSIntegrated.dll": {}
|
"Wabbajack.Services.OSIntegrated.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.VFS/0.3.13": {
|
"Wabbajack.VFS/0.3.14": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
||||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
||||||
"SixLabors.ImageSharp": "3.1.6",
|
"SixLabors.ImageSharp": "3.1.6",
|
||||||
"System.Data.SQLite.Core": "1.0.119",
|
"System.Data.SQLite.Core": "1.0.119",
|
||||||
"Wabbajack.Common": "0.3.13",
|
"Wabbajack.Common": "0.3.14",
|
||||||
"Wabbajack.FileExtractor": "0.3.13",
|
"Wabbajack.FileExtractor": "0.3.14",
|
||||||
"Wabbajack.Hashing.PHash": "0.3.13",
|
"Wabbajack.Hashing.PHash": "0.3.14",
|
||||||
"Wabbajack.Hashing.xxHash64": "0.3.13",
|
"Wabbajack.Hashing.xxHash64": "0.3.14",
|
||||||
"Wabbajack.Paths": "0.3.13",
|
"Wabbajack.Paths": "0.3.14",
|
||||||
"Wabbajack.Paths.IO": "0.3.13",
|
"Wabbajack.Paths.IO": "0.3.14",
|
||||||
"Wabbajack.VFS.Interfaces": "0.3.13"
|
"Wabbajack.VFS.Interfaces": "0.3.14"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.VFS.dll": {}
|
"Wabbajack.VFS.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Wabbajack.VFS.Interfaces/0.3.13": {
|
"Wabbajack.VFS.Interfaces/0.3.14": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
||||||
"Wabbajack.DTOs": "0.3.13",
|
"Wabbajack.DTOs": "0.3.14",
|
||||||
"Wabbajack.Hashing.xxHash64": "0.3.13",
|
"Wabbajack.Hashing.xxHash64": "0.3.14",
|
||||||
"Wabbajack.Paths": "0.3.13"
|
"Wabbajack.Paths": "0.3.14"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"Wabbajack.VFS.Interfaces.dll": {}
|
"Wabbajack.VFS.Interfaces.dll": {}
|
||||||
@@ -2332,7 +2332,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"libraries": {
|
"libraries": {
|
||||||
"jackify-engine/0.3.13": {
|
"jackify-engine/0.3.14": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
@@ -3021,202 +3021,202 @@
|
|||||||
"path": "yamldotnet/16.3.0",
|
"path": "yamldotnet/16.3.0",
|
||||||
"hashPath": "yamldotnet.16.3.0.nupkg.sha512"
|
"hashPath": "yamldotnet.16.3.0.nupkg.sha512"
|
||||||
},
|
},
|
||||||
"Wabbajack.CLI.Builder/0.3.13": {
|
"Wabbajack.CLI.Builder/0.3.14": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.Common/0.3.13": {
|
"Wabbajack.Common/0.3.14": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.Compiler/0.3.13": {
|
"Wabbajack.Compiler/0.3.14": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.Compression.BSA/0.3.13": {
|
"Wabbajack.Compression.BSA/0.3.14": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.Compression.Zip/0.3.13": {
|
"Wabbajack.Compression.Zip/0.3.14": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.Configuration/0.3.13": {
|
"Wabbajack.Configuration/0.3.14": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.Downloaders.Bethesda/0.3.13": {
|
"Wabbajack.Downloaders.Bethesda/0.3.14": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.Downloaders.Dispatcher/0.3.13": {
|
"Wabbajack.Downloaders.Dispatcher/0.3.14": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.Downloaders.GameFile/0.3.13": {
|
"Wabbajack.Downloaders.GameFile/0.3.14": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.Downloaders.GoogleDrive/0.3.13": {
|
"Wabbajack.Downloaders.GoogleDrive/0.3.14": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.Downloaders.Http/0.3.13": {
|
"Wabbajack.Downloaders.Http/0.3.14": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.Downloaders.Interfaces/0.3.13": {
|
"Wabbajack.Downloaders.Interfaces/0.3.14": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.Downloaders.IPS4OAuth2Downloader/0.3.13": {
|
"Wabbajack.Downloaders.IPS4OAuth2Downloader/0.3.14": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.Downloaders.Manual/0.3.13": {
|
"Wabbajack.Downloaders.Manual/0.3.14": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.Downloaders.MediaFire/0.3.13": {
|
"Wabbajack.Downloaders.MediaFire/0.3.14": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.Downloaders.Mega/0.3.13": {
|
"Wabbajack.Downloaders.Mega/0.3.14": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.Downloaders.ModDB/0.3.13": {
|
"Wabbajack.Downloaders.ModDB/0.3.14": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.Downloaders.Nexus/0.3.13": {
|
"Wabbajack.Downloaders.Nexus/0.3.14": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.Downloaders.VerificationCache/0.3.13": {
|
"Wabbajack.Downloaders.VerificationCache/0.3.14": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.Downloaders.WabbajackCDN/0.3.13": {
|
"Wabbajack.Downloaders.WabbajackCDN/0.3.14": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.DTOs/0.3.13": {
|
"Wabbajack.DTOs/0.3.14": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.FileExtractor/0.3.13": {
|
"Wabbajack.FileExtractor/0.3.14": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.Hashing.PHash/0.3.13": {
|
"Wabbajack.Hashing.PHash/0.3.14": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.Hashing.xxHash64/0.3.13": {
|
"Wabbajack.Hashing.xxHash64/0.3.14": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.Installer/0.3.13": {
|
"Wabbajack.Installer/0.3.14": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.IO.Async/0.3.13": {
|
"Wabbajack.IO.Async/0.3.14": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.Networking.BethesdaNet/0.3.13": {
|
"Wabbajack.Networking.BethesdaNet/0.3.14": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.Networking.Discord/0.3.13": {
|
"Wabbajack.Networking.Discord/0.3.14": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.Networking.GitHub/0.3.13": {
|
"Wabbajack.Networking.GitHub/0.3.14": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.Networking.Http/0.3.13": {
|
"Wabbajack.Networking.Http/0.3.14": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.Networking.Http.Interfaces/0.3.13": {
|
"Wabbajack.Networking.Http.Interfaces/0.3.14": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.Networking.NexusApi/0.3.13": {
|
"Wabbajack.Networking.NexusApi/0.3.14": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.Networking.WabbajackClientApi/0.3.13": {
|
"Wabbajack.Networking.WabbajackClientApi/0.3.14": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.Paths/0.3.13": {
|
"Wabbajack.Paths/0.3.14": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.Paths.IO/0.3.13": {
|
"Wabbajack.Paths.IO/0.3.14": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.RateLimiter/0.3.13": {
|
"Wabbajack.RateLimiter/0.3.14": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.Server.Lib/0.3.13": {
|
"Wabbajack.Server.Lib/0.3.14": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.Services.OSIntegrated/0.3.13": {
|
"Wabbajack.Services.OSIntegrated/0.3.14": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.VFS/0.3.13": {
|
"Wabbajack.VFS/0.3.14": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
"Wabbajack.VFS.Interfaces/0.3.13": {
|
"Wabbajack.VFS.Interfaces/0.3.14": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
|
|||||||
Binary file not shown.
@@ -32,9 +32,9 @@ class WabbajackMenuHandler:
|
|||||||
print_section_header("Modlist and Wabbajack Tasks")
|
print_section_header("Modlist and Wabbajack Tasks")
|
||||||
|
|
||||||
print(f"{COLOR_SELECTION}1.{COLOR_RESET} Install a Modlist (Automated)")
|
print(f"{COLOR_SELECTION}1.{COLOR_RESET} Install a Modlist (Automated)")
|
||||||
print(f" {COLOR_ACTION}→ Uses jackify-engine for a full install flow{COLOR_RESET}")
|
print(f" {COLOR_ACTION}→ Install a modlist in full: Select from a list or provide a .wabbajack file{COLOR_RESET}")
|
||||||
print(f"{COLOR_SELECTION}2.{COLOR_RESET} Configure New Modlist (Post-Download)")
|
print(f"{COLOR_SELECTION}2.{COLOR_RESET} Configure New Modlist (Post-Download)")
|
||||||
print(f" {COLOR_ACTION}→ Modlist .wabbajack file downloaded? Configure it for Steam{COLOR_RESET}")
|
print(f" {COLOR_ACTION}→ Modlist already downloaded? Configure and add to Steam{COLOR_RESET}")
|
||||||
print(f"{COLOR_SELECTION}3.{COLOR_RESET} Configure Existing Modlist (In Steam)")
|
print(f"{COLOR_SELECTION}3.{COLOR_RESET} Configure Existing Modlist (In Steam)")
|
||||||
print(f" {COLOR_ACTION}→ Modlist already in Steam? Re-configure it here{COLOR_RESET}")
|
print(f" {COLOR_ACTION}→ Modlist already in Steam? Re-configure it here{COLOR_RESET}")
|
||||||
# HIDDEN FOR FIRST RELEASE - UNCOMMENT WHEN READY
|
# HIDDEN FOR FIRST RELEASE - UNCOMMENT WHEN READY
|
||||||
|
|||||||
400
jackify/frontends/gui/dialogs/about_dialog.py
Normal file
400
jackify/frontends/gui/dialogs/about_dialog.py
Normal file
@@ -0,0 +1,400 @@
|
|||||||
|
"""
|
||||||
|
About dialog for Jackify.
|
||||||
|
|
||||||
|
This dialog displays system information, version details, and provides
|
||||||
|
access to update checking and external links.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import platform
|
||||||
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from PySide6.QtWidgets import (
|
||||||
|
QDialog, QVBoxLayout, QHBoxLayout, QLabel, QPushButton,
|
||||||
|
QGroupBox, QTextEdit, QApplication
|
||||||
|
)
|
||||||
|
from PySide6.QtCore import Qt, QThread, Signal
|
||||||
|
from PySide6.QtGui import QFont, QClipboard
|
||||||
|
|
||||||
|
from ....backend.services.update_service import UpdateService
|
||||||
|
from ....backend.models.configuration import SystemInfo
|
||||||
|
from .... import __version__
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateCheckThread(QThread):
|
||||||
|
"""Background thread for checking updates."""
|
||||||
|
|
||||||
|
update_check_finished = Signal(object) # UpdateInfo or None
|
||||||
|
|
||||||
|
def __init__(self, update_service: UpdateService):
|
||||||
|
super().__init__()
|
||||||
|
self.update_service = update_service
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
"""Check for updates in background."""
|
||||||
|
try:
|
||||||
|
update_info = self.update_service.check_for_updates()
|
||||||
|
self.update_check_finished.emit(update_info)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error checking for updates: {e}")
|
||||||
|
self.update_check_finished.emit(None)
|
||||||
|
|
||||||
|
|
||||||
|
class AboutDialog(QDialog):
|
||||||
|
"""About dialog showing system info and app details."""
|
||||||
|
|
||||||
|
def __init__(self, system_info: SystemInfo, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.system_info = system_info
|
||||||
|
self.update_service = UpdateService(__version__)
|
||||||
|
self.update_check_thread = None
|
||||||
|
|
||||||
|
self.setup_ui()
|
||||||
|
self.setup_connections()
|
||||||
|
|
||||||
|
def setup_ui(self):
|
||||||
|
"""Set up the dialog UI."""
|
||||||
|
self.setWindowTitle("About Jackify")
|
||||||
|
self.setModal(True)
|
||||||
|
self.setFixedSize(520, 520)
|
||||||
|
|
||||||
|
layout = QVBoxLayout(self)
|
||||||
|
|
||||||
|
# Header
|
||||||
|
header_layout = QVBoxLayout()
|
||||||
|
|
||||||
|
# App icon/name
|
||||||
|
title_label = QLabel("Jackify")
|
||||||
|
title_font = QFont()
|
||||||
|
title_font.setPointSize(18)
|
||||||
|
title_font.setBold(True)
|
||||||
|
title_label.setFont(title_font)
|
||||||
|
title_label.setAlignment(Qt.AlignCenter)
|
||||||
|
title_label.setStyleSheet("color: #3fd0ea; margin: 10px;")
|
||||||
|
header_layout.addWidget(title_label)
|
||||||
|
|
||||||
|
subtitle_label = QLabel(f"v{__version__}")
|
||||||
|
subtitle_font = QFont()
|
||||||
|
subtitle_font.setPointSize(12)
|
||||||
|
subtitle_label.setFont(subtitle_font)
|
||||||
|
subtitle_label.setAlignment(Qt.AlignCenter)
|
||||||
|
subtitle_label.setStyleSheet("color: #666; margin-bottom: 10px;")
|
||||||
|
header_layout.addWidget(subtitle_label)
|
||||||
|
|
||||||
|
tagline_label = QLabel("Simplifying Wabbajack modlist installation and configuration on Linux")
|
||||||
|
tagline_label.setAlignment(Qt.AlignCenter)
|
||||||
|
tagline_label.setStyleSheet("color: #888; margin-bottom: 20px;")
|
||||||
|
header_layout.addWidget(tagline_label)
|
||||||
|
|
||||||
|
layout.addLayout(header_layout)
|
||||||
|
|
||||||
|
# System Information Group
|
||||||
|
system_group = QGroupBox("System Information")
|
||||||
|
system_layout = QVBoxLayout(system_group)
|
||||||
|
|
||||||
|
system_info_text = self._get_system_info_text()
|
||||||
|
system_info_label = QLabel(system_info_text)
|
||||||
|
system_info_label.setStyleSheet("font-family: monospace; font-size: 10pt; color: #ccc;")
|
||||||
|
system_info_label.setWordWrap(True)
|
||||||
|
system_layout.addWidget(system_info_label)
|
||||||
|
|
||||||
|
layout.addWidget(system_group)
|
||||||
|
|
||||||
|
# Jackify Information Group
|
||||||
|
jackify_group = QGroupBox("Jackify Information")
|
||||||
|
jackify_layout = QVBoxLayout(jackify_group)
|
||||||
|
|
||||||
|
jackify_info_text = self._get_jackify_info_text()
|
||||||
|
jackify_info_label = QLabel(jackify_info_text)
|
||||||
|
jackify_info_label.setStyleSheet("font-family: monospace; font-size: 10pt; color: #ccc;")
|
||||||
|
jackify_layout.addWidget(jackify_info_label)
|
||||||
|
|
||||||
|
layout.addWidget(jackify_group)
|
||||||
|
|
||||||
|
# Update status
|
||||||
|
self.update_status_label = QLabel("")
|
||||||
|
self.update_status_label.setStyleSheet("color: #666; font-size: 10pt; margin: 5px;")
|
||||||
|
self.update_status_label.setAlignment(Qt.AlignCenter)
|
||||||
|
layout.addWidget(self.update_status_label)
|
||||||
|
|
||||||
|
# Buttons
|
||||||
|
button_layout = QHBoxLayout()
|
||||||
|
|
||||||
|
# Update check button
|
||||||
|
self.update_button = QPushButton("Check for Updates")
|
||||||
|
self.update_button.clicked.connect(self.check_for_updates)
|
||||||
|
self.update_button.setStyleSheet("""
|
||||||
|
QPushButton {
|
||||||
|
background-color: #23272e;
|
||||||
|
color: #3fd0ea;
|
||||||
|
font-weight: bold;
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 2px solid #3fd0ea;
|
||||||
|
}
|
||||||
|
QPushButton:hover {
|
||||||
|
background-color: #3fd0ea;
|
||||||
|
color: #23272e;
|
||||||
|
}
|
||||||
|
QPushButton:pressed {
|
||||||
|
background-color: #2bb8d6;
|
||||||
|
color: #23272e;
|
||||||
|
}
|
||||||
|
QPushButton:disabled {
|
||||||
|
background-color: #444;
|
||||||
|
color: #666;
|
||||||
|
border-color: #666;
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
button_layout.addWidget(self.update_button)
|
||||||
|
|
||||||
|
button_layout.addStretch()
|
||||||
|
|
||||||
|
# Copy Info button
|
||||||
|
copy_button = QPushButton("Copy Info")
|
||||||
|
copy_button.clicked.connect(self.copy_system_info)
|
||||||
|
button_layout.addWidget(copy_button)
|
||||||
|
|
||||||
|
# External links
|
||||||
|
github_button = QPushButton("GitHub")
|
||||||
|
github_button.clicked.connect(self.open_github)
|
||||||
|
button_layout.addWidget(github_button)
|
||||||
|
|
||||||
|
nexus_button = QPushButton("Nexus")
|
||||||
|
nexus_button.clicked.connect(self.open_nexus)
|
||||||
|
button_layout.addWidget(nexus_button)
|
||||||
|
|
||||||
|
layout.addLayout(button_layout)
|
||||||
|
|
||||||
|
# Close button
|
||||||
|
close_layout = QHBoxLayout()
|
||||||
|
close_layout.addStretch()
|
||||||
|
close_button = QPushButton("Close")
|
||||||
|
close_button.setDefault(True)
|
||||||
|
close_button.clicked.connect(self.accept)
|
||||||
|
close_layout.addWidget(close_button)
|
||||||
|
layout.addLayout(close_layout)
|
||||||
|
|
||||||
|
def setup_connections(self):
|
||||||
|
"""Set up signal connections."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _get_system_info_text(self) -> str:
|
||||||
|
"""Get formatted system information."""
|
||||||
|
try:
|
||||||
|
# OS info
|
||||||
|
os_info = self._get_os_info()
|
||||||
|
kernel = platform.release()
|
||||||
|
|
||||||
|
# Desktop environment
|
||||||
|
desktop = self._get_desktop_environment()
|
||||||
|
|
||||||
|
# Display server
|
||||||
|
display_server = self._get_display_server()
|
||||||
|
|
||||||
|
return f"• OS: {os_info}\n• Kernel: {kernel}\n• Desktop: {desktop}\n• Display: {display_server}"
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error getting system info: {e}")
|
||||||
|
return "• System info unavailable"
|
||||||
|
|
||||||
|
def _get_jackify_info_text(self) -> str:
|
||||||
|
"""Get formatted Jackify information."""
|
||||||
|
try:
|
||||||
|
# Engine version
|
||||||
|
engine_version = self._get_engine_version()
|
||||||
|
|
||||||
|
# Python version
|
||||||
|
python_version = platform.python_version()
|
||||||
|
|
||||||
|
return f"• Engine: {engine_version}\n• Python: {python_version}"
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error getting Jackify info: {e}")
|
||||||
|
return "• Jackify info unavailable"
|
||||||
|
|
||||||
|
def _get_os_info(self) -> str:
|
||||||
|
"""Get OS distribution name and version."""
|
||||||
|
try:
|
||||||
|
if os.path.exists("/etc/os-release"):
|
||||||
|
with open("/etc/os-release", "r") as f:
|
||||||
|
lines = f.readlines()
|
||||||
|
pretty_name = None
|
||||||
|
name = None
|
||||||
|
version = None
|
||||||
|
|
||||||
|
for line in lines:
|
||||||
|
line = line.strip()
|
||||||
|
if line.startswith("PRETTY_NAME="):
|
||||||
|
pretty_name = line.split("=", 1)[1].strip('"')
|
||||||
|
elif line.startswith("NAME="):
|
||||||
|
name = line.split("=", 1)[1].strip('"')
|
||||||
|
elif line.startswith("VERSION="):
|
||||||
|
version = line.split("=", 1)[1].strip('"')
|
||||||
|
|
||||||
|
# Prefer PRETTY_NAME, fallback to NAME + VERSION
|
||||||
|
if pretty_name:
|
||||||
|
return pretty_name
|
||||||
|
elif name and version:
|
||||||
|
return f"{name} {version}"
|
||||||
|
elif name:
|
||||||
|
return name
|
||||||
|
|
||||||
|
# Fallback to platform info
|
||||||
|
return f"{platform.system()} {platform.release()}"
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error getting OS info: {e}")
|
||||||
|
return "Unknown Linux"
|
||||||
|
|
||||||
|
def _get_desktop_environment(self) -> str:
|
||||||
|
"""Get desktop environment."""
|
||||||
|
try:
|
||||||
|
# Try XDG_CURRENT_DESKTOP first
|
||||||
|
desktop = os.environ.get("XDG_CURRENT_DESKTOP")
|
||||||
|
if desktop:
|
||||||
|
return desktop
|
||||||
|
|
||||||
|
# Fallback to DESKTOP_SESSION
|
||||||
|
desktop = os.environ.get("DESKTOP_SESSION")
|
||||||
|
if desktop:
|
||||||
|
return desktop
|
||||||
|
|
||||||
|
# Try detecting common DEs
|
||||||
|
if os.environ.get("KDE_FULL_SESSION"):
|
||||||
|
return "KDE"
|
||||||
|
elif os.environ.get("GNOME_DESKTOP_SESSION_ID"):
|
||||||
|
return "GNOME"
|
||||||
|
elif os.environ.get("XFCE4_SESSION"):
|
||||||
|
return "XFCE"
|
||||||
|
|
||||||
|
return "Unknown"
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error getting desktop environment: {e}")
|
||||||
|
return "Unknown"
|
||||||
|
|
||||||
|
def _get_display_server(self) -> str:
|
||||||
|
"""Get display server type (Wayland or X11)."""
|
||||||
|
try:
|
||||||
|
# Check XDG_SESSION_TYPE first
|
||||||
|
session_type = os.environ.get("XDG_SESSION_TYPE")
|
||||||
|
if session_type:
|
||||||
|
return session_type.capitalize()
|
||||||
|
|
||||||
|
# Check for Wayland display
|
||||||
|
if os.environ.get("WAYLAND_DISPLAY"):
|
||||||
|
return "Wayland"
|
||||||
|
|
||||||
|
# Check for X11 display
|
||||||
|
if os.environ.get("DISPLAY"):
|
||||||
|
return "X11"
|
||||||
|
|
||||||
|
return "Unknown"
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error getting display server: {e}")
|
||||||
|
return "Unknown"
|
||||||
|
|
||||||
|
def _get_engine_version(self) -> str:
|
||||||
|
"""Get jackify-engine version."""
|
||||||
|
try:
|
||||||
|
# Try to execute jackify-engine --version
|
||||||
|
engine_path = Path(__file__).parent.parent.parent.parent / "engine" / "jackify-engine"
|
||||||
|
if engine_path.exists():
|
||||||
|
result = subprocess.run([str(engine_path), "--version"],
|
||||||
|
capture_output=True, text=True, timeout=5)
|
||||||
|
if result.returncode == 0:
|
||||||
|
version = result.stdout.strip()
|
||||||
|
# Extract just the version number (before the +commit hash)
|
||||||
|
if '+' in version:
|
||||||
|
version = version.split('+')[0]
|
||||||
|
return f"v{version}"
|
||||||
|
|
||||||
|
return "Unknown"
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error getting engine version: {e}")
|
||||||
|
return "Unknown"
|
||||||
|
|
||||||
|
def check_for_updates(self):
|
||||||
|
"""Check for updates in background."""
|
||||||
|
if self.update_check_thread and self.update_check_thread.isRunning():
|
||||||
|
return
|
||||||
|
|
||||||
|
self.update_button.setEnabled(False)
|
||||||
|
self.update_button.setText("Checking...")
|
||||||
|
self.update_status_label.setText("Checking for updates...")
|
||||||
|
|
||||||
|
self.update_check_thread = UpdateCheckThread(self.update_service)
|
||||||
|
self.update_check_thread.update_check_finished.connect(self.update_check_finished)
|
||||||
|
self.update_check_thread.start()
|
||||||
|
|
||||||
|
def update_check_finished(self, update_info):
|
||||||
|
"""Handle update check completion."""
|
||||||
|
self.update_button.setEnabled(True)
|
||||||
|
self.update_button.setText("Check for Updates")
|
||||||
|
|
||||||
|
if update_info:
|
||||||
|
self.update_status_label.setText(f"Update available: v{update_info.version}")
|
||||||
|
self.update_status_label.setStyleSheet("color: #3fd0ea; font-size: 10pt; margin: 5px;")
|
||||||
|
|
||||||
|
# Show update dialog
|
||||||
|
from .update_dialog import UpdateDialog
|
||||||
|
update_dialog = UpdateDialog(update_info, self.update_service, self)
|
||||||
|
update_dialog.exec()
|
||||||
|
else:
|
||||||
|
self.update_status_label.setText("You're running the latest version")
|
||||||
|
self.update_status_label.setStyleSheet("color: #666; font-size: 10pt; margin: 5px;")
|
||||||
|
|
||||||
|
def copy_system_info(self):
|
||||||
|
"""Copy system information to clipboard."""
|
||||||
|
try:
|
||||||
|
info_text = f"""Jackify v{__version__} (Engine {self._get_engine_version()})
|
||||||
|
OS: {self._get_os_info()} ({platform.release()})
|
||||||
|
Desktop: {self._get_desktop_environment()} ({self._get_display_server()})
|
||||||
|
Python: {platform.python_version()}"""
|
||||||
|
|
||||||
|
clipboard = QApplication.clipboard()
|
||||||
|
clipboard.setText(info_text)
|
||||||
|
|
||||||
|
# Briefly update button text
|
||||||
|
sender = self.sender()
|
||||||
|
original_text = sender.text()
|
||||||
|
sender.setText("Copied!")
|
||||||
|
|
||||||
|
# Reset button text after delay
|
||||||
|
from PySide6.QtCore import QTimer
|
||||||
|
QTimer.singleShot(1000, lambda: sender.setText(original_text))
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error copying system info: {e}")
|
||||||
|
|
||||||
|
def open_github(self):
|
||||||
|
"""Open GitHub repository."""
|
||||||
|
try:
|
||||||
|
import webbrowser
|
||||||
|
webbrowser.open("https://github.com/Omni-guides/Jackify")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error opening GitHub: {e}")
|
||||||
|
|
||||||
|
def open_nexus(self):
|
||||||
|
"""Open Nexus Mods page."""
|
||||||
|
try:
|
||||||
|
import webbrowser
|
||||||
|
webbrowser.open("https://www.nexusmods.com/site/mods/1427")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error opening Nexus: {e}")
|
||||||
|
|
||||||
|
def closeEvent(self, event):
|
||||||
|
"""Handle dialog close event."""
|
||||||
|
if self.update_check_thread and self.update_check_thread.isRunning():
|
||||||
|
self.update_check_thread.terminate()
|
||||||
|
self.update_check_thread.wait()
|
||||||
|
|
||||||
|
event.accept()
|
||||||
@@ -325,6 +325,29 @@ class SettingsDialog(QDialog):
|
|||||||
download_dir_row.addWidget(self.download_dir_edit)
|
download_dir_row.addWidget(self.download_dir_edit)
|
||||||
download_dir_row.addWidget(self.download_dir_btn)
|
download_dir_row.addWidget(self.download_dir_btn)
|
||||||
dir_layout.addRow(QLabel("Downloads Base Dir:"), download_dir_row)
|
dir_layout.addRow(QLabel("Downloads Base Dir:"), download_dir_row)
|
||||||
|
|
||||||
|
# Jackify Data Directory
|
||||||
|
from jackify.shared.paths import get_jackify_data_dir
|
||||||
|
current_jackify_dir = str(get_jackify_data_dir())
|
||||||
|
self.jackify_data_dir_edit = QLineEdit(current_jackify_dir)
|
||||||
|
self.jackify_data_dir_edit.setToolTip("Directory for Jackify data (logs, downloads, temp files). Default: ~/Jackify")
|
||||||
|
self.jackify_data_dir_btn = QPushButton()
|
||||||
|
self.jackify_data_dir_btn.setIcon(QIcon.fromTheme("folder-open"))
|
||||||
|
self.jackify_data_dir_btn.setToolTip("Browse for directory")
|
||||||
|
self.jackify_data_dir_btn.setFixedWidth(32)
|
||||||
|
self.jackify_data_dir_btn.clicked.connect(lambda: self._pick_directory(self.jackify_data_dir_edit))
|
||||||
|
jackify_data_dir_row = QHBoxLayout()
|
||||||
|
jackify_data_dir_row.addWidget(self.jackify_data_dir_edit)
|
||||||
|
jackify_data_dir_row.addWidget(self.jackify_data_dir_btn)
|
||||||
|
|
||||||
|
# Reset to default button
|
||||||
|
reset_jackify_dir_btn = QPushButton("Reset")
|
||||||
|
reset_jackify_dir_btn.setToolTip("Reset to default (~/ Jackify)")
|
||||||
|
reset_jackify_dir_btn.setFixedWidth(50)
|
||||||
|
reset_jackify_dir_btn.clicked.connect(lambda: self.jackify_data_dir_edit.setText(str(Path.home() / "Jackify")))
|
||||||
|
jackify_data_dir_row.addWidget(reset_jackify_dir_btn)
|
||||||
|
|
||||||
|
dir_layout.addRow(QLabel("Jackify Data Dir:"), jackify_data_dir_row)
|
||||||
main_layout.addWidget(dir_group)
|
main_layout.addWidget(dir_group)
|
||||||
main_layout.addSpacing(12)
|
main_layout.addSpacing(12)
|
||||||
|
|
||||||
@@ -464,7 +487,14 @@ class SettingsDialog(QDialog):
|
|||||||
# Save modlist base dirs
|
# Save modlist base dirs
|
||||||
self.config_handler.set("modlist_install_base_dir", self.install_dir_edit.text().strip())
|
self.config_handler.set("modlist_install_base_dir", self.install_dir_edit.text().strip())
|
||||||
self.config_handler.set("modlist_downloads_base_dir", self.download_dir_edit.text().strip())
|
self.config_handler.set("modlist_downloads_base_dir", self.download_dir_edit.text().strip())
|
||||||
|
# Save jackify data directory (always store actual path, never None)
|
||||||
|
jackify_data_dir = self.jackify_data_dir_edit.text().strip()
|
||||||
|
self.config_handler.set("jackify_data_dir", jackify_data_dir)
|
||||||
self.config_handler.save_config()
|
self.config_handler.save_config()
|
||||||
|
|
||||||
|
# Refresh cached paths in GUI screens if Jackify directory changed
|
||||||
|
self._refresh_gui_paths()
|
||||||
|
|
||||||
# Check if debug mode changed and prompt for restart
|
# Check if debug mode changed and prompt for restart
|
||||||
new_debug_mode = self.debug_checkbox.isChecked()
|
new_debug_mode = self.debug_checkbox.isChecked()
|
||||||
if new_debug_mode != self._original_debug_mode:
|
if new_debug_mode != self._original_debug_mode:
|
||||||
@@ -484,6 +514,29 @@ class SettingsDialog(QDialog):
|
|||||||
MessageService.information(self, "Settings Saved", "Settings have been saved successfully.", safety_level="low")
|
MessageService.information(self, "Settings Saved", "Settings have been saved successfully.", safety_level="low")
|
||||||
self.accept()
|
self.accept()
|
||||||
|
|
||||||
|
def _refresh_gui_paths(self):
|
||||||
|
"""Refresh cached paths in all GUI screens."""
|
||||||
|
try:
|
||||||
|
# Get the main window through parent relationship
|
||||||
|
main_window = self.parent()
|
||||||
|
if not main_window or not hasattr(main_window, 'stacked_widget'):
|
||||||
|
return
|
||||||
|
|
||||||
|
# Refresh paths in all screens that have the method
|
||||||
|
screens_to_refresh = [
|
||||||
|
getattr(main_window, 'install_modlist_screen', None),
|
||||||
|
getattr(main_window, 'configure_new_modlist_screen', None),
|
||||||
|
getattr(main_window, 'configure_existing_modlist_screen', None),
|
||||||
|
getattr(main_window, 'tuxborn_screen', None),
|
||||||
|
]
|
||||||
|
|
||||||
|
for screen in screens_to_refresh:
|
||||||
|
if screen and hasattr(screen, 'refresh_paths'):
|
||||||
|
screen.refresh_paths()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Warning: Could not refresh GUI paths: {e}")
|
||||||
|
|
||||||
def _bold_label(self, text):
|
def _bold_label(self, text):
|
||||||
label = QLabel(text)
|
label = QLabel(text)
|
||||||
label.setStyleSheet("font-weight: bold; color: #fff;")
|
label.setStyleSheet("font-weight: bold; color: #fff;")
|
||||||
@@ -655,7 +708,7 @@ class JackifyMainWindow(QMainWindow):
|
|||||||
# Spacer
|
# Spacer
|
||||||
bottom_bar_layout.addStretch(1)
|
bottom_bar_layout.addStretch(1)
|
||||||
|
|
||||||
# Settings button (right)
|
# Settings button (right side)
|
||||||
settings_btn = QLabel('<a href="#" style="color:#6cf; text-decoration:none;">Settings</a>')
|
settings_btn = QLabel('<a href="#" style="color:#6cf; text-decoration:none;">Settings</a>')
|
||||||
settings_btn.setStyleSheet("color: #6cf; font-size: 13px; padding-right: 8px;")
|
settings_btn.setStyleSheet("color: #6cf; font-size: 13px; padding-right: 8px;")
|
||||||
settings_btn.setTextInteractionFlags(Qt.TextBrowserInteraction)
|
settings_btn.setTextInteractionFlags(Qt.TextBrowserInteraction)
|
||||||
@@ -663,6 +716,14 @@ class JackifyMainWindow(QMainWindow):
|
|||||||
settings_btn.linkActivated.connect(self.open_settings_dialog)
|
settings_btn.linkActivated.connect(self.open_settings_dialog)
|
||||||
bottom_bar_layout.addWidget(settings_btn, alignment=Qt.AlignRight)
|
bottom_bar_layout.addWidget(settings_btn, alignment=Qt.AlignRight)
|
||||||
|
|
||||||
|
# About button (right side)
|
||||||
|
about_btn = QLabel('<a href="#" style="color:#6cf; text-decoration:none;">About</a>')
|
||||||
|
about_btn.setStyleSheet("color: #6cf; font-size: 13px; padding-right: 8px;")
|
||||||
|
about_btn.setTextInteractionFlags(Qt.TextBrowserInteraction)
|
||||||
|
about_btn.setOpenExternalLinks(False)
|
||||||
|
about_btn.linkActivated.connect(self.open_about_dialog)
|
||||||
|
bottom_bar_layout.addWidget(about_btn, alignment=Qt.AlignRight)
|
||||||
|
|
||||||
# --- Main Layout ---
|
# --- Main Layout ---
|
||||||
central_widget = QWidget()
|
central_widget = QWidget()
|
||||||
main_layout = QVBoxLayout()
|
main_layout = QVBoxLayout()
|
||||||
@@ -808,6 +869,16 @@ class JackifyMainWindow(QMainWindow):
|
|||||||
import traceback
|
import traceback
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
||||||
|
def open_about_dialog(self):
|
||||||
|
try:
|
||||||
|
from jackify.frontends.gui.dialogs.about_dialog import AboutDialog
|
||||||
|
dlg = AboutDialog(self.system_info, self)
|
||||||
|
dlg.exec()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[ERROR] Exception in open_about_dialog: {e}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
|
||||||
def resource_path(relative_path):
|
def resource_path(relative_path):
|
||||||
if hasattr(sys, '_MEIPASS'):
|
if hasattr(sys, '_MEIPASS'):
|
||||||
|
|||||||
@@ -34,8 +34,7 @@ class ConfigureExistingModlistScreen(QWidget):
|
|||||||
self.stacked_widget = stacked_widget
|
self.stacked_widget = stacked_widget
|
||||||
self.main_menu_index = main_menu_index
|
self.main_menu_index = main_menu_index
|
||||||
self.debug = DEBUG_BORDERS
|
self.debug = DEBUG_BORDERS
|
||||||
self.modlist_log_path = os.path.expanduser('~/Jackify/logs/Configure_Existing_Modlist_workflow.log')
|
self.refresh_paths()
|
||||||
os.makedirs(os.path.dirname(self.modlist_log_path), exist_ok=True)
|
|
||||||
|
|
||||||
# --- Detect Steam Deck ---
|
# --- Detect Steam Deck ---
|
||||||
steamdeck = os.path.exists('/etc/os-release') and 'steamdeck' in open('/etc/os-release').read().lower()
|
steamdeck = os.path.exists('/etc/os-release') and 'steamdeck' in open('/etc/os-release').read().lower()
|
||||||
@@ -297,6 +296,41 @@ class ConfigureExistingModlistScreen(QWidget):
|
|||||||
|
|
||||||
# Time tracking for workflow completion
|
# Time tracking for workflow completion
|
||||||
self._workflow_start_time = None
|
self._workflow_start_time = None
|
||||||
|
|
||||||
|
# Initialize empty controls list - will be populated after UI is built
|
||||||
|
self._actionable_controls = []
|
||||||
|
|
||||||
|
# Now collect all actionable controls after UI is fully built
|
||||||
|
self._collect_actionable_controls()
|
||||||
|
|
||||||
|
def _collect_actionable_controls(self):
|
||||||
|
"""Collect all actionable controls that should be disabled during operations (except Cancel)"""
|
||||||
|
self._actionable_controls = [
|
||||||
|
# Main action button
|
||||||
|
self.start_btn,
|
||||||
|
# Form fields
|
||||||
|
self.shortcut_combo,
|
||||||
|
# Resolution controls
|
||||||
|
self.resolution_combo,
|
||||||
|
]
|
||||||
|
|
||||||
|
def _disable_controls_during_operation(self):
|
||||||
|
"""Disable all actionable controls during configure operations (except Cancel)"""
|
||||||
|
for control in self._actionable_controls:
|
||||||
|
if control:
|
||||||
|
control.setEnabled(False)
|
||||||
|
|
||||||
|
def _enable_controls_after_operation(self):
|
||||||
|
"""Re-enable all actionable controls after configure operations complete"""
|
||||||
|
for control in self._actionable_controls:
|
||||||
|
if control:
|
||||||
|
control.setEnabled(True)
|
||||||
|
|
||||||
|
def refresh_paths(self):
|
||||||
|
"""Refresh cached paths when config changes."""
|
||||||
|
from jackify.shared.paths import get_jackify_logs_dir
|
||||||
|
self.modlist_log_path = get_jackify_logs_dir() / 'Configure_Existing_Modlist_workflow.log'
|
||||||
|
os.makedirs(os.path.dirname(self.modlist_log_path), exist_ok=True)
|
||||||
|
|
||||||
def resizeEvent(self, event):
|
def resizeEvent(self, event):
|
||||||
"""Handle window resize to prioritize form over console"""
|
"""Handle window resize to prioritize form over console"""
|
||||||
@@ -382,17 +416,22 @@ class ConfigureExistingModlistScreen(QWidget):
|
|||||||
log_handler = LoggingHandler()
|
log_handler = LoggingHandler()
|
||||||
log_handler.rotate_log_file_per_run(Path(self.modlist_log_path), backup_count=5)
|
log_handler.rotate_log_file_per_run(Path(self.modlist_log_path), backup_count=5)
|
||||||
|
|
||||||
|
# Disable controls during configuration
|
||||||
|
self._disable_controls_during_operation()
|
||||||
|
|
||||||
# Get selected shortcut
|
# Get selected shortcut
|
||||||
idx = self.shortcut_combo.currentIndex() - 1 # Account for 'Please Select...'
|
idx = self.shortcut_combo.currentIndex() - 1 # Account for 'Please Select...'
|
||||||
from jackify.frontends.gui.services.message_service import MessageService
|
from jackify.frontends.gui.services.message_service import MessageService
|
||||||
if idx < 0 or idx >= len(self.shortcut_map):
|
if idx < 0 or idx >= len(self.shortcut_map):
|
||||||
MessageService.critical(self, "No Shortcut Selected", "Please select a ModOrganizer.exe Steam shortcut to configure.", safety_level="medium")
|
MessageService.critical(self, "No Shortcut Selected", "Please select a ModOrganizer.exe Steam shortcut to configure.", safety_level="medium")
|
||||||
|
self._enable_controls_after_operation()
|
||||||
return
|
return
|
||||||
shortcut = self.shortcut_map[idx]
|
shortcut = self.shortcut_map[idx]
|
||||||
modlist_name = shortcut.get('AppName', '')
|
modlist_name = shortcut.get('AppName', '')
|
||||||
install_dir = shortcut.get('StartDir', '')
|
install_dir = shortcut.get('StartDir', '')
|
||||||
if not modlist_name or not install_dir:
|
if not modlist_name or not install_dir:
|
||||||
MessageService.critical(self, "Invalid Shortcut", "The selected shortcut is missing required information.", safety_level="medium")
|
MessageService.critical(self, "Invalid Shortcut", "The selected shortcut is missing required information.", safety_level="medium")
|
||||||
|
self._enable_controls_after_operation()
|
||||||
return
|
return
|
||||||
resolution = self.resolution_combo.currentText()
|
resolution = self.resolution_combo.currentText()
|
||||||
# Handle resolution saving
|
# Handle resolution saving
|
||||||
@@ -505,6 +544,9 @@ class ConfigureExistingModlistScreen(QWidget):
|
|||||||
|
|
||||||
def on_configuration_complete(self, success, message, modlist_name):
|
def on_configuration_complete(self, success, message, modlist_name):
|
||||||
"""Handle configuration completion"""
|
"""Handle configuration completion"""
|
||||||
|
# Re-enable all controls when workflow completes
|
||||||
|
self._enable_controls_after_operation()
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
# Calculate time taken
|
# Calculate time taken
|
||||||
time_taken = self._calculate_time_taken()
|
time_taken = self._calculate_time_taken()
|
||||||
@@ -525,6 +567,9 @@ class ConfigureExistingModlistScreen(QWidget):
|
|||||||
|
|
||||||
def on_configuration_error(self, error_message):
|
def on_configuration_error(self, error_message):
|
||||||
"""Handle configuration error"""
|
"""Handle configuration error"""
|
||||||
|
# Re-enable all controls on error
|
||||||
|
self._enable_controls_after_operation()
|
||||||
|
|
||||||
self._safe_append_text(f"Configuration error: {error_message}")
|
self._safe_append_text(f"Configuration error: {error_message}")
|
||||||
MessageService.critical(self, "Configuration Error", f"Configuration failed: {error_message}", safety_level="medium")
|
MessageService.critical(self, "Configuration Error", f"Configuration failed: {error_message}", safety_level="medium")
|
||||||
|
|
||||||
@@ -559,8 +604,8 @@ class ConfigureExistingModlistScreen(QWidget):
|
|||||||
if self.config_process and self.config_process.state() == QProcess.Running:
|
if self.config_process and self.config_process.state() == QProcess.Running:
|
||||||
self.config_process.terminate()
|
self.config_process.terminate()
|
||||||
self.config_process.waitForFinished(2000)
|
self.config_process.waitForFinished(2000)
|
||||||
# Reset button states
|
# Re-enable all controls
|
||||||
self.start_btn.setEnabled(True)
|
self._enable_controls_after_operation()
|
||||||
self.cancel_btn.setVisible(True)
|
self.cancel_btn.setVisible(True)
|
||||||
|
|
||||||
def show_next_steps_dialog(self, message):
|
def show_next_steps_dialog(self, message):
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
ConfigureNewModlistScreen for Jackify GUI
|
ConfigureNewModlistScreen for Jackify GUI
|
||||||
"""
|
"""
|
||||||
from PySide6.QtWidgets import QWidget, QVBoxLayout, QLabel, QComboBox, QHBoxLayout, QLineEdit, QPushButton, QGridLayout, QFileDialog, QTextEdit, QSizePolicy, QTabWidget, QDialog, QListWidget, QListWidgetItem, QMessageBox, QProgressDialog
|
from PySide6.QtWidgets import QWidget, QVBoxLayout, QLabel, QComboBox, QHBoxLayout, QLineEdit, QPushButton, QGridLayout, QFileDialog, QTextEdit, QSizePolicy, QTabWidget, QDialog, QListWidget, QListWidgetItem, QMessageBox, QProgressDialog, QCheckBox
|
||||||
from PySide6.QtCore import Qt, QSize, QThread, Signal, QTimer, QProcess, QMetaObject
|
from PySide6.QtCore import Qt, QSize, QThread, Signal, QTimer, QProcess, QMetaObject
|
||||||
from PySide6.QtGui import QPixmap, QTextCursor
|
from PySide6.QtGui import QPixmap, QTextCursor
|
||||||
from ..shared_theme import JACKIFY_COLOR_BLUE, DEBUG_BORDERS
|
from ..shared_theme import JACKIFY_COLOR_BLUE, DEBUG_BORDERS
|
||||||
@@ -106,8 +106,7 @@ class ConfigureNewModlistScreen(QWidget):
|
|||||||
self.protontricks_service = ProtontricksDetectionService()
|
self.protontricks_service = ProtontricksDetectionService()
|
||||||
|
|
||||||
# Path for workflow log
|
# Path for workflow log
|
||||||
self.modlist_log_path = os.path.expanduser('~/Jackify/logs/Configure_New_Modlist_workflow.log')
|
self.refresh_paths()
|
||||||
os.makedirs(os.path.dirname(self.modlist_log_path), exist_ok=True)
|
|
||||||
|
|
||||||
# Scroll tracking for professional auto-scroll behavior
|
# Scroll tracking for professional auto-scroll behavior
|
||||||
self._user_manually_scrolled = False
|
self._user_manually_scrolled = False
|
||||||
@@ -211,7 +210,6 @@ class ConfigureNewModlistScreen(QWidget):
|
|||||||
"7680x4320"
|
"7680x4320"
|
||||||
])
|
])
|
||||||
form_grid.addWidget(resolution_label, 2, 0, alignment=Qt.AlignLeft | Qt.AlignVCenter)
|
form_grid.addWidget(resolution_label, 2, 0, alignment=Qt.AlignLeft | Qt.AlignVCenter)
|
||||||
form_grid.addWidget(self.resolution_combo, 2, 1)
|
|
||||||
|
|
||||||
# Load saved resolution if available
|
# Load saved resolution if available
|
||||||
saved_resolution = self.resolution_service.get_saved_resolution()
|
saved_resolution = self.resolution_service.get_saved_resolution()
|
||||||
@@ -236,6 +234,27 @@ class ConfigureNewModlistScreen(QWidget):
|
|||||||
else:
|
else:
|
||||||
self.resolution_combo.setCurrentIndex(0)
|
self.resolution_combo.setCurrentIndex(0)
|
||||||
# Otherwise, default is 'Leave unchanged' (index 0)
|
# Otherwise, default is 'Leave unchanged' (index 0)
|
||||||
|
|
||||||
|
# Horizontal layout for resolution dropdown and auto-restart checkbox
|
||||||
|
resolution_and_restart_layout = QHBoxLayout()
|
||||||
|
resolution_and_restart_layout.setSpacing(12)
|
||||||
|
|
||||||
|
# Resolution dropdown (made smaller)
|
||||||
|
self.resolution_combo.setMaximumWidth(280) # Constrain width but keep aesthetically pleasing
|
||||||
|
resolution_and_restart_layout.addWidget(self.resolution_combo)
|
||||||
|
|
||||||
|
# Add stretch to push checkbox to the right
|
||||||
|
resolution_and_restart_layout.addStretch()
|
||||||
|
|
||||||
|
# Auto-accept Steam restart checkbox (right-aligned)
|
||||||
|
self.auto_restart_checkbox = QCheckBox("Auto-accept Steam restart")
|
||||||
|
self.auto_restart_checkbox.setChecked(False) # Always default to unchecked per session
|
||||||
|
self.auto_restart_checkbox.setToolTip("When checked, Steam restart dialog will be automatically accepted, allowing unattended configuration")
|
||||||
|
resolution_and_restart_layout.addWidget(self.auto_restart_checkbox)
|
||||||
|
|
||||||
|
# Update the form grid to use the combined layout
|
||||||
|
form_grid.addLayout(resolution_and_restart_layout, 2, 1)
|
||||||
|
|
||||||
form_section_widget = QWidget()
|
form_section_widget = QWidget()
|
||||||
form_section_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
|
form_section_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
|
||||||
form_section_widget.setLayout(form_grid)
|
form_section_widget.setLayout(form_grid)
|
||||||
@@ -338,6 +357,44 @@ class ConfigureNewModlistScreen(QWidget):
|
|||||||
self.start_btn.clicked.connect(self.validate_and_start_configure)
|
self.start_btn.clicked.connect(self.validate_and_start_configure)
|
||||||
# --- Connect steam_restart_finished signal ---
|
# --- Connect steam_restart_finished signal ---
|
||||||
self.steam_restart_finished.connect(self._on_steam_restart_finished)
|
self.steam_restart_finished.connect(self._on_steam_restart_finished)
|
||||||
|
|
||||||
|
# Initialize empty controls list - will be populated after UI is built
|
||||||
|
self._actionable_controls = []
|
||||||
|
|
||||||
|
# Now collect all actionable controls after UI is fully built
|
||||||
|
self._collect_actionable_controls()
|
||||||
|
|
||||||
|
def _collect_actionable_controls(self):
|
||||||
|
"""Collect all actionable controls that should be disabled during operations (except Cancel)"""
|
||||||
|
self._actionable_controls = [
|
||||||
|
# Main action button
|
||||||
|
self.start_btn,
|
||||||
|
# Form fields
|
||||||
|
self.modlist_name_edit,
|
||||||
|
self.install_dir_edit,
|
||||||
|
# Resolution controls
|
||||||
|
self.resolution_combo,
|
||||||
|
# Checkboxes
|
||||||
|
self.auto_restart_checkbox,
|
||||||
|
]
|
||||||
|
|
||||||
|
def _disable_controls_during_operation(self):
|
||||||
|
"""Disable all actionable controls during configure operations (except Cancel)"""
|
||||||
|
for control in self._actionable_controls:
|
||||||
|
if control:
|
||||||
|
control.setEnabled(False)
|
||||||
|
|
||||||
|
def _enable_controls_after_operation(self):
|
||||||
|
"""Re-enable all actionable controls after configure operations complete"""
|
||||||
|
for control in self._actionable_controls:
|
||||||
|
if control:
|
||||||
|
control.setEnabled(True)
|
||||||
|
|
||||||
|
def refresh_paths(self):
|
||||||
|
"""Refresh cached paths when config changes."""
|
||||||
|
from jackify.shared.paths import get_jackify_logs_dir
|
||||||
|
self.modlist_log_path = get_jackify_logs_dir() / 'Configure_New_Modlist_workflow.log'
|
||||||
|
os.makedirs(os.path.dirname(self.modlist_log_path), exist_ok=True)
|
||||||
|
|
||||||
def resizeEvent(self, event):
|
def resizeEvent(self, event):
|
||||||
"""Handle window resize to prioritize form over console"""
|
"""Handle window resize to prioritize form over console"""
|
||||||
@@ -522,23 +579,38 @@ class ConfigureNewModlistScreen(QWidget):
|
|||||||
# Start time tracking
|
# Start time tracking
|
||||||
self._workflow_start_time = time.time()
|
self._workflow_start_time = time.time()
|
||||||
|
|
||||||
|
# Disable controls during configuration (after validation passes)
|
||||||
|
self._disable_controls_during_operation()
|
||||||
|
|
||||||
# Validate modlist name
|
# Validate modlist name
|
||||||
modlist_name = self.modlist_name_edit.text().strip()
|
modlist_name = self.modlist_name_edit.text().strip()
|
||||||
if not modlist_name:
|
if not modlist_name:
|
||||||
MessageService.warning(self, "Missing Name", "Please specify a name for your modlist", safety_level="low")
|
MessageService.warning(self, "Missing Name", "Please specify a name for your modlist", safety_level="low")
|
||||||
|
self._enable_controls_after_operation()
|
||||||
return
|
return
|
||||||
# --- Shortcut creation will be handled by automated workflow ---
|
# --- Shortcut creation will be handled by automated workflow ---
|
||||||
from jackify.backend.handlers.shortcut_handler import ShortcutHandler
|
from jackify.backend.handlers.shortcut_handler import ShortcutHandler
|
||||||
steamdeck = os.path.exists('/etc/os-release') and 'steamdeck' in open('/etc/os-release').read().lower()
|
steamdeck = os.path.exists('/etc/os-release') and 'steamdeck' in open('/etc/os-release').read().lower()
|
||||||
shortcut_handler = ShortcutHandler(steamdeck=steamdeck) # Still needed for Steam restart
|
shortcut_handler = ShortcutHandler(steamdeck=steamdeck) # Still needed for Steam restart
|
||||||
# --- User confirmation before restarting Steam ---
|
|
||||||
reply = MessageService.question(
|
# Check if auto-restart is enabled
|
||||||
self, "Ready to Configure Modlist",
|
auto_restart_enabled = hasattr(self, 'auto_restart_checkbox') and self.auto_restart_checkbox.isChecked()
|
||||||
"Would you like to restart Steam and begin post-install configuration now? Restarting Steam could close any games you have open!",
|
|
||||||
safety_level="medium"
|
if auto_restart_enabled:
|
||||||
)
|
# Auto-accept Steam restart - proceed without dialog
|
||||||
print(f"DEBUG: Steam restart dialog returned: {reply!r}")
|
self._safe_append_text("Auto-accepting Steam restart (unattended mode enabled)")
|
||||||
|
reply = QMessageBox.Yes # Simulate user clicking Yes
|
||||||
|
else:
|
||||||
|
# --- User confirmation before restarting Steam ---
|
||||||
|
reply = MessageService.question(
|
||||||
|
self, "Ready to Configure Modlist",
|
||||||
|
"Would you like to restart Steam and begin post-install configuration now? Restarting Steam could close any games you have open!",
|
||||||
|
safety_level="medium"
|
||||||
|
)
|
||||||
|
|
||||||
|
debug_print(f"DEBUG: Steam restart dialog returned: {reply!r}")
|
||||||
if reply not in (QMessageBox.Yes, QMessageBox.Ok, QMessageBox.AcceptRole):
|
if reply not in (QMessageBox.Yes, QMessageBox.Ok, QMessageBox.AcceptRole):
|
||||||
|
self._enable_controls_after_operation()
|
||||||
if self.stacked_widget:
|
if self.stacked_widget:
|
||||||
self.stacked_widget.setCurrentIndex(0)
|
self.stacked_widget.setCurrentIndex(0)
|
||||||
return
|
return
|
||||||
@@ -562,7 +634,6 @@ class ConfigureNewModlistScreen(QWidget):
|
|||||||
progress.setMinimumDuration(0)
|
progress.setMinimumDuration(0)
|
||||||
progress.setValue(0)
|
progress.setValue(0)
|
||||||
progress.show()
|
progress.show()
|
||||||
self.setEnabled(False)
|
|
||||||
def do_restart():
|
def do_restart():
|
||||||
try:
|
try:
|
||||||
ok = shortcut_handler.secure_steam_restart()
|
ok = shortcut_handler.secure_steam_restart()
|
||||||
@@ -579,7 +650,7 @@ class ConfigureNewModlistScreen(QWidget):
|
|||||||
if hasattr(self, '_steam_restart_progress'):
|
if hasattr(self, '_steam_restart_progress'):
|
||||||
self._steam_restart_progress.close()
|
self._steam_restart_progress.close()
|
||||||
del self._steam_restart_progress
|
del self._steam_restart_progress
|
||||||
self.setEnabled(True)
|
self._enable_controls_after_operation()
|
||||||
if success:
|
if success:
|
||||||
self._safe_append_text("Steam restarted successfully.")
|
self._safe_append_text("Steam restarted successfully.")
|
||||||
|
|
||||||
@@ -722,7 +793,7 @@ class ConfigureNewModlistScreen(QWidget):
|
|||||||
"""Handle error from the automated prefix workflow"""
|
"""Handle error from the automated prefix workflow"""
|
||||||
self._safe_append_text(f"Error during automated Steam setup: {error_message}")
|
self._safe_append_text(f"Error during automated Steam setup: {error_message}")
|
||||||
self._safe_append_text("Please check the logs for details.")
|
self._safe_append_text("Please check the logs for details.")
|
||||||
self.start_btn.setEnabled(True)
|
self._enable_controls_after_operation()
|
||||||
|
|
||||||
def show_shortcut_conflict_dialog(self, conflicts):
|
def show_shortcut_conflict_dialog(self, conflicts):
|
||||||
"""Show dialog to resolve shortcut name conflicts"""
|
"""Show dialog to resolve shortcut name conflicts"""
|
||||||
@@ -1162,8 +1233,8 @@ class ConfigureNewModlistScreen(QWidget):
|
|||||||
|
|
||||||
def on_configuration_complete(self, success, message, modlist_name):
|
def on_configuration_complete(self, success, message, modlist_name):
|
||||||
"""Handle configuration completion (same as Tuxborn)"""
|
"""Handle configuration completion (same as Tuxborn)"""
|
||||||
# Always re-enable the start button when workflow completes
|
# Re-enable all controls when workflow completes
|
||||||
self.start_btn.setEnabled(True)
|
self._enable_controls_after_operation()
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
# Calculate time taken
|
# Calculate time taken
|
||||||
@@ -1185,8 +1256,8 @@ class ConfigureNewModlistScreen(QWidget):
|
|||||||
|
|
||||||
def on_configuration_error(self, error_message):
|
def on_configuration_error(self, error_message):
|
||||||
"""Handle configuration error"""
|
"""Handle configuration error"""
|
||||||
# Re-enable the start button on error
|
# Re-enable all controls on error
|
||||||
self.start_btn.setEnabled(True)
|
self._enable_controls_after_operation()
|
||||||
|
|
||||||
self._safe_append_text(f"Configuration error: {error_message}")
|
self._safe_append_text(f"Configuration error: {error_message}")
|
||||||
MessageService.critical(self, "Configuration Error", f"Configuration failed: {error_message}", safety_level="medium")
|
MessageService.critical(self, "Configuration Error", f"Configuration failed: {error_message}", safety_level="medium")
|
||||||
|
|||||||
@@ -355,9 +355,8 @@ class InstallModlistScreen(QWidget):
|
|||||||
self.online_modlists = {} # {game_type: [modlist_dict, ...]}
|
self.online_modlists = {} # {game_type: [modlist_dict, ...]}
|
||||||
self.modlist_details = {} # {modlist_name: modlist_dict}
|
self.modlist_details = {} # {modlist_name: modlist_dict}
|
||||||
|
|
||||||
# Path for workflow log
|
# Initialize log path (can be refreshed via refresh_paths method)
|
||||||
self.modlist_log_path = os.path.expanduser('~/Jackify/logs/Modlist_Install_workflow.log')
|
self.refresh_paths()
|
||||||
os.makedirs(os.path.dirname(self.modlist_log_path), exist_ok=True)
|
|
||||||
|
|
||||||
# Initialize services early
|
# Initialize services early
|
||||||
from jackify.backend.services.api_key_service import APIKeyService
|
from jackify.backend.services.api_key_service import APIKeyService
|
||||||
@@ -459,11 +458,11 @@ class InstallModlistScreen(QWidget):
|
|||||||
file_layout.setContentsMargins(0, 0, 0, 0)
|
file_layout.setContentsMargins(0, 0, 0, 0)
|
||||||
self.file_edit = QLineEdit()
|
self.file_edit = QLineEdit()
|
||||||
self.file_edit.setMinimumWidth(400)
|
self.file_edit.setMinimumWidth(400)
|
||||||
file_btn = QPushButton("Browse")
|
self.file_btn = QPushButton("Browse")
|
||||||
file_btn.clicked.connect(self.browse_wabbajack_file)
|
self.file_btn.clicked.connect(self.browse_wabbajack_file)
|
||||||
file_layout.addWidget(QLabel(".wabbajack File:"))
|
file_layout.addWidget(QLabel(".wabbajack File:"))
|
||||||
file_layout.addWidget(self.file_edit)
|
file_layout.addWidget(self.file_edit)
|
||||||
file_layout.addWidget(file_btn)
|
file_layout.addWidget(self.file_btn)
|
||||||
self.file_group.setLayout(file_layout)
|
self.file_group.setLayout(file_layout)
|
||||||
file_tab_vbox.addWidget(self.file_group)
|
file_tab_vbox.addWidget(self.file_group)
|
||||||
file_tab.setLayout(file_tab_vbox)
|
file_tab.setLayout(file_tab_vbox)
|
||||||
@@ -484,22 +483,22 @@ class InstallModlistScreen(QWidget):
|
|||||||
install_dir_label = QLabel("Install Directory:")
|
install_dir_label = QLabel("Install Directory:")
|
||||||
self.install_dir_edit = QLineEdit(self.config_handler.get_modlist_install_base_dir())
|
self.install_dir_edit = QLineEdit(self.config_handler.get_modlist_install_base_dir())
|
||||||
self.install_dir_edit.setMaximumHeight(25) # Force compact height
|
self.install_dir_edit.setMaximumHeight(25) # Force compact height
|
||||||
browse_install_btn = QPushButton("Browse")
|
self.browse_install_btn = QPushButton("Browse")
|
||||||
browse_install_btn.clicked.connect(self.browse_install_dir)
|
self.browse_install_btn.clicked.connect(self.browse_install_dir)
|
||||||
install_dir_hbox = QHBoxLayout()
|
install_dir_hbox = QHBoxLayout()
|
||||||
install_dir_hbox.addWidget(self.install_dir_edit)
|
install_dir_hbox.addWidget(self.install_dir_edit)
|
||||||
install_dir_hbox.addWidget(browse_install_btn)
|
install_dir_hbox.addWidget(self.browse_install_btn)
|
||||||
form_grid.addWidget(install_dir_label, 1, 0, alignment=Qt.AlignLeft | Qt.AlignVCenter)
|
form_grid.addWidget(install_dir_label, 1, 0, alignment=Qt.AlignLeft | Qt.AlignVCenter)
|
||||||
form_grid.addLayout(install_dir_hbox, 1, 1)
|
form_grid.addLayout(install_dir_hbox, 1, 1)
|
||||||
# Downloads Dir
|
# Downloads Dir
|
||||||
downloads_dir_label = QLabel("Downloads Directory:")
|
downloads_dir_label = QLabel("Downloads Directory:")
|
||||||
self.downloads_dir_edit = QLineEdit(self.config_handler.get_modlist_downloads_base_dir())
|
self.downloads_dir_edit = QLineEdit(self.config_handler.get_modlist_downloads_base_dir())
|
||||||
self.downloads_dir_edit.setMaximumHeight(25) # Force compact height
|
self.downloads_dir_edit.setMaximumHeight(25) # Force compact height
|
||||||
browse_downloads_btn = QPushButton("Browse")
|
self.browse_downloads_btn = QPushButton("Browse")
|
||||||
browse_downloads_btn.clicked.connect(self.browse_downloads_dir)
|
self.browse_downloads_btn.clicked.connect(self.browse_downloads_dir)
|
||||||
downloads_dir_hbox = QHBoxLayout()
|
downloads_dir_hbox = QHBoxLayout()
|
||||||
downloads_dir_hbox.addWidget(self.downloads_dir_edit)
|
downloads_dir_hbox.addWidget(self.downloads_dir_edit)
|
||||||
downloads_dir_hbox.addWidget(browse_downloads_btn)
|
downloads_dir_hbox.addWidget(self.browse_downloads_btn)
|
||||||
form_grid.addWidget(downloads_dir_label, 2, 0, alignment=Qt.AlignLeft | Qt.AlignVCenter)
|
form_grid.addWidget(downloads_dir_label, 2, 0, alignment=Qt.AlignLeft | Qt.AlignVCenter)
|
||||||
form_grid.addLayout(downloads_dir_hbox, 2, 1)
|
form_grid.addLayout(downloads_dir_hbox, 2, 1)
|
||||||
# Nexus API Key
|
# Nexus API Key
|
||||||
@@ -603,7 +602,25 @@ class InstallModlistScreen(QWidget):
|
|||||||
self.resolution_combo.setCurrentIndex(0)
|
self.resolution_combo.setCurrentIndex(0)
|
||||||
# Otherwise, default is 'Leave unchanged' (index 0)
|
# Otherwise, default is 'Leave unchanged' (index 0)
|
||||||
form_grid.addWidget(resolution_label, 5, 0, alignment=Qt.AlignLeft | Qt.AlignVCenter)
|
form_grid.addWidget(resolution_label, 5, 0, alignment=Qt.AlignLeft | Qt.AlignVCenter)
|
||||||
form_grid.addWidget(self.resolution_combo, 5, 1)
|
|
||||||
|
# Horizontal layout for resolution dropdown and auto-restart checkbox
|
||||||
|
resolution_and_restart_layout = QHBoxLayout()
|
||||||
|
resolution_and_restart_layout.setSpacing(12)
|
||||||
|
|
||||||
|
# Resolution dropdown (made smaller)
|
||||||
|
self.resolution_combo.setMaximumWidth(280) # Constrain width but keep aesthetically pleasing
|
||||||
|
resolution_and_restart_layout.addWidget(self.resolution_combo)
|
||||||
|
|
||||||
|
# Add stretch to push checkbox to the right
|
||||||
|
resolution_and_restart_layout.addStretch()
|
||||||
|
|
||||||
|
# Auto-accept Steam restart checkbox (right-aligned)
|
||||||
|
self.auto_restart_checkbox = QCheckBox("Auto-accept Steam restart")
|
||||||
|
self.auto_restart_checkbox.setChecked(False) # Always default to unchecked per session
|
||||||
|
self.auto_restart_checkbox.setToolTip("When checked, Steam restart dialog will be automatically accepted, allowing unattended installation")
|
||||||
|
resolution_and_restart_layout.addWidget(self.auto_restart_checkbox)
|
||||||
|
|
||||||
|
form_grid.addLayout(resolution_and_restart_layout, 5, 1)
|
||||||
form_section_widget = QWidget()
|
form_section_widget = QWidget()
|
||||||
form_section_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
|
form_section_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
|
||||||
form_section_widget.setLayout(form_grid)
|
form_section_widget.setLayout(form_grid)
|
||||||
@@ -723,6 +740,57 @@ class InstallModlistScreen(QWidget):
|
|||||||
|
|
||||||
# Initialize process tracking
|
# Initialize process tracking
|
||||||
self.process = None
|
self.process = None
|
||||||
|
|
||||||
|
# Initialize empty controls list - will be populated after UI is built
|
||||||
|
self._actionable_controls = []
|
||||||
|
|
||||||
|
# Now collect all actionable controls after UI is fully built
|
||||||
|
self._collect_actionable_controls()
|
||||||
|
|
||||||
|
def _collect_actionable_controls(self):
|
||||||
|
"""Collect all actionable controls that should be disabled during operations (except Cancel)"""
|
||||||
|
self._actionable_controls = [
|
||||||
|
# Main action button
|
||||||
|
self.start_btn,
|
||||||
|
# Game/modlist selection
|
||||||
|
self.game_type_btn,
|
||||||
|
self.modlist_btn,
|
||||||
|
# Source tabs (entire tab widget)
|
||||||
|
self.source_tabs,
|
||||||
|
# Form fields
|
||||||
|
self.modlist_name_edit,
|
||||||
|
self.install_dir_edit,
|
||||||
|
self.downloads_dir_edit,
|
||||||
|
self.api_key_edit,
|
||||||
|
self.file_edit,
|
||||||
|
# Browse buttons
|
||||||
|
self.browse_install_btn,
|
||||||
|
self.browse_downloads_btn,
|
||||||
|
self.file_btn,
|
||||||
|
# Resolution controls
|
||||||
|
self.resolution_combo,
|
||||||
|
# Checkboxes
|
||||||
|
self.save_api_key_checkbox,
|
||||||
|
self.auto_restart_checkbox,
|
||||||
|
]
|
||||||
|
|
||||||
|
def _disable_controls_during_operation(self):
|
||||||
|
"""Disable all actionable controls during install/configure operations (except Cancel)"""
|
||||||
|
for control in self._actionable_controls:
|
||||||
|
if control:
|
||||||
|
control.setEnabled(False)
|
||||||
|
|
||||||
|
def _enable_controls_after_operation(self):
|
||||||
|
"""Re-enable all actionable controls after install/configure operations complete"""
|
||||||
|
for control in self._actionable_controls:
|
||||||
|
if control:
|
||||||
|
control.setEnabled(True)
|
||||||
|
|
||||||
|
def refresh_paths(self):
|
||||||
|
"""Refresh cached paths when config changes."""
|
||||||
|
from jackify.shared.paths import get_jackify_logs_dir
|
||||||
|
self.modlist_log_path = get_jackify_logs_dir() / 'Modlist_Install_workflow.log'
|
||||||
|
os.makedirs(os.path.dirname(self.modlist_log_path), exist_ok=True)
|
||||||
|
|
||||||
def _open_url_safe(self, url):
|
def _open_url_safe(self, url):
|
||||||
"""Safely open URL using subprocess to avoid Qt library conflicts in PyInstaller"""
|
"""Safely open URL using subprocess to avoid Qt library conflicts in PyInstaller"""
|
||||||
@@ -1121,6 +1189,9 @@ class InstallModlistScreen(QWidget):
|
|||||||
if not self._check_protontricks():
|
if not self._check_protontricks():
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Disable all controls during installation (except Cancel)
|
||||||
|
self._disable_controls_during_operation()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
tab_index = self.source_tabs.currentIndex()
|
tab_index = self.source_tabs.currentIndex()
|
||||||
install_mode = 'online'
|
install_mode = 'online'
|
||||||
@@ -1128,12 +1199,14 @@ class InstallModlistScreen(QWidget):
|
|||||||
modlist = self.file_edit.text().strip()
|
modlist = self.file_edit.text().strip()
|
||||||
if not modlist or not os.path.isfile(modlist) or not modlist.endswith('.wabbajack'):
|
if not modlist or not os.path.isfile(modlist) or not modlist.endswith('.wabbajack'):
|
||||||
MessageService.warning(self, "Invalid Modlist", "Please select a valid .wabbajack file.")
|
MessageService.warning(self, "Invalid Modlist", "Please select a valid .wabbajack file.")
|
||||||
|
self._enable_controls_after_operation()
|
||||||
return
|
return
|
||||||
install_mode = 'file'
|
install_mode = 'file'
|
||||||
else:
|
else:
|
||||||
modlist = self.modlist_btn.text().strip()
|
modlist = self.modlist_btn.text().strip()
|
||||||
if not modlist or modlist in ("Select Modlist", "Fetching modlists...", "No modlists found.", "Error fetching modlists."):
|
if not modlist or modlist in ("Select Modlist", "Fetching modlists...", "No modlists found.", "Error fetching modlists."):
|
||||||
MessageService.warning(self, "Invalid Modlist", "Please select a valid modlist.")
|
MessageService.warning(self, "Invalid Modlist", "Please select a valid modlist.")
|
||||||
|
self._enable_controls_after_operation()
|
||||||
return
|
return
|
||||||
|
|
||||||
# For online modlists, use machine_url instead of display name
|
# For online modlists, use machine_url instead of display name
|
||||||
@@ -1159,6 +1232,7 @@ class InstallModlistScreen(QWidget):
|
|||||||
missing_fields.append("Nexus API Key")
|
missing_fields.append("Nexus API Key")
|
||||||
if missing_fields:
|
if missing_fields:
|
||||||
MessageService.warning(self, "Missing Required Fields", f"Please fill in all required fields before starting the install:\n- " + "\n- ".join(missing_fields))
|
MessageService.warning(self, "Missing Required Fields", f"Please fill in all required fields before starting the install:\n- " + "\n- ".join(missing_fields))
|
||||||
|
self._enable_controls_after_operation()
|
||||||
return
|
return
|
||||||
validation_handler = ValidationHandler()
|
validation_handler = ValidationHandler()
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -1324,14 +1398,11 @@ class InstallModlistScreen(QWidget):
|
|||||||
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
|
||||||
debug_print(f"DEBUG: Traceback: {traceback.format_exc()}")
|
debug_print(f"DEBUG: Traceback: {traceback.format_exc()}")
|
||||||
# Re-enable the button in case of exception
|
# Re-enable all controls after exception
|
||||||
self.start_btn.setEnabled(True)
|
self._enable_controls_after_operation()
|
||||||
self.cancel_btn.setVisible(True)
|
self.cancel_btn.setVisible(True)
|
||||||
self.cancel_install_btn.setVisible(False)
|
self.cancel_install_btn.setVisible(False)
|
||||||
# Also re-enable the entire widget
|
debug_print(f"DEBUG: Controls re-enabled in exception handler")
|
||||||
self.setEnabled(True)
|
|
||||||
debug_print(f"DEBUG: Widget re-enabled in exception handler, widget enabled: {self.isEnabled()}")
|
|
||||||
print(f"DEBUG: Widget re-enabled in exception handler, widget enabled: {self.isEnabled()}") # Always print
|
|
||||||
|
|
||||||
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'):
|
||||||
debug_print('DEBUG: run_modlist_installer called - USING THREADED BACKEND WRAPPER')
|
debug_print('DEBUG: run_modlist_installer called - USING THREADED BACKEND WRAPPER')
|
||||||
@@ -1501,12 +1572,21 @@ class InstallModlistScreen(QWidget):
|
|||||||
self._safe_append_text(f"\nModlist installation completed successfully.")
|
self._safe_append_text(f"\nModlist installation completed successfully.")
|
||||||
self._safe_append_text(f"\nWarning: Post-install configuration skipped for unsupported game: {game_name or game_type}")
|
self._safe_append_text(f"\nWarning: Post-install configuration skipped for unsupported game: {game_name or game_type}")
|
||||||
else:
|
else:
|
||||||
# Show the normal install complete dialog for supported games
|
# Check if auto-restart is enabled
|
||||||
reply = MessageService.question(
|
auto_restart_enabled = hasattr(self, 'auto_restart_checkbox') and self.auto_restart_checkbox.isChecked()
|
||||||
self, "Modlist Install Complete!",
|
|
||||||
"Modlist install complete!\n\nWould you like to add this modlist to Steam and configure it now? Steam will restart, closing any game you have open!",
|
if auto_restart_enabled:
|
||||||
critical=False # Non-critical, won't steal focus
|
# Auto-accept Steam restart - proceed without dialog
|
||||||
)
|
self._safe_append_text("\nAuto-accepting Steam restart (unattended mode enabled)")
|
||||||
|
reply = QMessageBox.Yes # Simulate user clicking Yes
|
||||||
|
else:
|
||||||
|
# Show the normal install complete dialog for supported games
|
||||||
|
reply = MessageService.question(
|
||||||
|
self, "Modlist Install Complete!",
|
||||||
|
"Modlist install complete!\n\nWould you like to add this modlist to Steam and configure it now? Steam will restart, closing any game you have open!",
|
||||||
|
critical=False # Non-critical, won't steal focus
|
||||||
|
)
|
||||||
|
|
||||||
if reply == QMessageBox.Yes:
|
if reply == QMessageBox.Yes:
|
||||||
# --- Create Steam shortcut BEFORE restarting Steam ---
|
# --- Create Steam shortcut BEFORE restarting Steam ---
|
||||||
# Proceed directly to automated prefix creation
|
# Proceed directly to automated prefix creation
|
||||||
@@ -1522,6 +1602,8 @@ class InstallModlistScreen(QWidget):
|
|||||||
"You can manually add the modlist to Steam later if desired.",
|
"You can manually add the modlist to Steam later if desired.",
|
||||||
safety_level="medium"
|
safety_level="medium"
|
||||||
)
|
)
|
||||||
|
# Re-enable controls since operation is complete
|
||||||
|
self._enable_controls_after_operation()
|
||||||
else:
|
else:
|
||||||
# Check for user cancellation first
|
# Check for user cancellation first
|
||||||
last_output = self.console.toPlainText()
|
last_output = self.console.toPlainText()
|
||||||
@@ -1611,9 +1693,6 @@ class InstallModlistScreen(QWidget):
|
|||||||
progress.setMinimumDuration(0)
|
progress.setMinimumDuration(0)
|
||||||
progress.setValue(0)
|
progress.setValue(0)
|
||||||
progress.show()
|
progress.show()
|
||||||
self.setEnabled(False)
|
|
||||||
debug_print(f"DEBUG: Widget disabled in restart_steam_and_configure, widget enabled: {self.isEnabled()}")
|
|
||||||
print(f"DEBUG: Widget disabled in restart_steam_and_configure, widget enabled: {self.isEnabled()}") # Always print
|
|
||||||
|
|
||||||
def do_restart():
|
def do_restart():
|
||||||
debug_print("DEBUG: do_restart thread started - using direct backend service")
|
debug_print("DEBUG: do_restart thread started - using direct backend service")
|
||||||
@@ -1651,9 +1730,7 @@ class InstallModlistScreen(QWidget):
|
|||||||
finally:
|
finally:
|
||||||
self._steam_restart_progress = None
|
self._steam_restart_progress = None
|
||||||
|
|
||||||
self.setEnabled(True)
|
# Controls are managed by the proper control management system
|
||||||
debug_print(f"DEBUG: Widget re-enabled in _on_steam_restart_finished, widget enabled: {self.isEnabled()}")
|
|
||||||
print(f"DEBUG: Widget re-enabled in _on_steam_restart_finished, widget enabled: {self.isEnabled()}") # Always print
|
|
||||||
if success:
|
if success:
|
||||||
self._safe_append_text("Steam restarted successfully.")
|
self._safe_append_text("Steam restarted successfully.")
|
||||||
|
|
||||||
@@ -1676,6 +1753,8 @@ class InstallModlistScreen(QWidget):
|
|||||||
def start_automated_prefix_workflow(self):
|
def start_automated_prefix_workflow(self):
|
||||||
"""Start the automated prefix creation workflow"""
|
"""Start the automated prefix creation workflow"""
|
||||||
try:
|
try:
|
||||||
|
# Disable controls during installation
|
||||||
|
self._disable_controls_during_operation()
|
||||||
modlist_name = self.modlist_name_edit.text().strip()
|
modlist_name = self.modlist_name_edit.text().strip()
|
||||||
install_dir = self.install_dir_edit.text().strip()
|
install_dir = self.install_dir_edit.text().strip()
|
||||||
final_exe_path = os.path.join(install_dir, "ModOrganizer.exe")
|
final_exe_path = os.path.join(install_dir, "ModOrganizer.exe")
|
||||||
@@ -1784,33 +1863,43 @@ class InstallModlistScreen(QWidget):
|
|||||||
import traceback
|
import traceback
|
||||||
debug_print(f"DEBUG: Traceback: {traceback.format_exc()}")
|
debug_print(f"DEBUG: Traceback: {traceback.format_exc()}")
|
||||||
self._safe_append_text(f"ERROR: Failed to start automated workflow: {e}")
|
self._safe_append_text(f"ERROR: Failed to start automated workflow: {e}")
|
||||||
|
# Re-enable controls on exception
|
||||||
|
self._enable_controls_after_operation()
|
||||||
|
|
||||||
def on_automated_prefix_finished(self, success, prefix_path, new_appid_str, last_timestamp=None):
|
def on_automated_prefix_finished(self, success, prefix_path, new_appid_str, last_timestamp=None):
|
||||||
"""Handle completion of automated prefix creation"""
|
"""Handle completion of automated prefix creation"""
|
||||||
if success:
|
try:
|
||||||
debug_print(f"SUCCESS: Automated prefix creation completed!")
|
if success:
|
||||||
debug_print(f"Prefix created at: {prefix_path}")
|
debug_print(f"SUCCESS: Automated prefix creation completed!")
|
||||||
if new_appid_str and new_appid_str != "0":
|
debug_print(f"Prefix created at: {prefix_path}")
|
||||||
debug_print(f"AppID: {new_appid_str}")
|
if new_appid_str and new_appid_str != "0":
|
||||||
|
debug_print(f"AppID: {new_appid_str}")
|
||||||
# Convert string AppID back to integer for configuration
|
|
||||||
new_appid = int(new_appid_str) if new_appid_str and new_appid_str != "0" else None
|
# Convert string AppID back to integer for configuration
|
||||||
|
new_appid = int(new_appid_str) if new_appid_str and new_appid_str != "0" else None
|
||||||
# Continue with configuration using the new AppID and timestamp
|
|
||||||
modlist_name = self.modlist_name_edit.text().strip()
|
# Continue with configuration using the new AppID and timestamp
|
||||||
install_dir = self.install_dir_edit.text().strip()
|
modlist_name = self.modlist_name_edit.text().strip()
|
||||||
self.continue_configuration_after_automated_prefix(new_appid, modlist_name, install_dir, last_timestamp)
|
install_dir = self.install_dir_edit.text().strip()
|
||||||
else:
|
self.continue_configuration_after_automated_prefix(new_appid, modlist_name, install_dir, last_timestamp)
|
||||||
self._safe_append_text(f"ERROR: Automated prefix creation failed")
|
else:
|
||||||
self._safe_append_text("Please check the logs for details")
|
self._safe_append_text(f"ERROR: Automated prefix creation failed")
|
||||||
MessageService.critical(self, "Automated Setup Failed",
|
self._safe_append_text("Please check the logs for details")
|
||||||
"Automated prefix creation failed. Please check the console output for details.")
|
MessageService.critical(self, "Automated Setup Failed",
|
||||||
|
"Automated prefix creation failed. Please check the console output for details.")
|
||||||
|
# Re-enable controls on failure
|
||||||
|
self._enable_controls_after_operation()
|
||||||
|
finally:
|
||||||
|
# Always ensure controls are re-enabled when workflow truly completes
|
||||||
|
pass
|
||||||
|
|
||||||
def on_automated_prefix_error(self, error_msg):
|
def on_automated_prefix_error(self, error_msg):
|
||||||
"""Handle error in automated prefix creation"""
|
"""Handle error in automated prefix creation"""
|
||||||
self._safe_append_text(f"ERROR: Error during automated prefix creation: {error_msg}")
|
self._safe_append_text(f"ERROR: Error during automated prefix creation: {error_msg}")
|
||||||
MessageService.critical(self, "Automated Setup Error",
|
MessageService.critical(self, "Automated Setup Error",
|
||||||
f"Error during automated prefix creation: {error_msg}")
|
f"Error during automated prefix creation: {error_msg}")
|
||||||
|
# Re-enable controls on error
|
||||||
|
self._enable_controls_after_operation()
|
||||||
|
|
||||||
def on_automated_prefix_progress(self, progress_msg):
|
def on_automated_prefix_progress(self, progress_msg):
|
||||||
"""Handle progress updates from automated prefix creation"""
|
"""Handle progress updates from automated prefix creation"""
|
||||||
@@ -1831,7 +1920,6 @@ class InstallModlistScreen(QWidget):
|
|||||||
self.steam_restart_progress.setMinimumDuration(0)
|
self.steam_restart_progress.setMinimumDuration(0)
|
||||||
self.steam_restart_progress.setValue(0)
|
self.steam_restart_progress.setValue(0)
|
||||||
self.steam_restart_progress.show()
|
self.steam_restart_progress.show()
|
||||||
self.setEnabled(False)
|
|
||||||
|
|
||||||
def hide_steam_restart_progress(self):
|
def hide_steam_restart_progress(self):
|
||||||
"""Hide Steam restart progress dialog"""
|
"""Hide Steam restart progress dialog"""
|
||||||
@@ -1843,45 +1931,53 @@ class InstallModlistScreen(QWidget):
|
|||||||
pass
|
pass
|
||||||
finally:
|
finally:
|
||||||
self.steam_restart_progress = None
|
self.steam_restart_progress = None
|
||||||
self.setEnabled(True)
|
# Controls are managed by the proper control management system
|
||||||
|
|
||||||
def on_configuration_complete(self, success, message, modlist_name):
|
def on_configuration_complete(self, success, message, modlist_name):
|
||||||
"""Handle configuration completion on main thread"""
|
"""Handle configuration completion on main thread"""
|
||||||
if success:
|
try:
|
||||||
# Show celebration SuccessDialog after the entire workflow
|
# Re-enable controls now that installation/configuration is complete
|
||||||
from ..dialogs import SuccessDialog
|
self._enable_controls_after_operation()
|
||||||
import time
|
|
||||||
if not hasattr(self, '_install_workflow_start_time'):
|
if success:
|
||||||
self._install_workflow_start_time = time.time()
|
# Show celebration SuccessDialog after the entire workflow
|
||||||
time_taken = int(time.time() - self._install_workflow_start_time)
|
from ..dialogs import SuccessDialog
|
||||||
mins, secs = divmod(time_taken, 60)
|
import time
|
||||||
time_str = f"{mins} minutes, {secs} seconds" if mins else f"{secs} seconds"
|
if not hasattr(self, '_install_workflow_start_time'):
|
||||||
display_names = {
|
self._install_workflow_start_time = time.time()
|
||||||
'skyrim': 'Skyrim',
|
time_taken = int(time.time() - self._install_workflow_start_time)
|
||||||
'fallout4': 'Fallout 4',
|
mins, secs = divmod(time_taken, 60)
|
||||||
'falloutnv': 'Fallout New Vegas',
|
time_str = f"{mins} minutes, {secs} seconds" if mins else f"{secs} seconds"
|
||||||
'oblivion': 'Oblivion',
|
display_names = {
|
||||||
'starfield': 'Starfield',
|
'skyrim': 'Skyrim',
|
||||||
'oblivion_remastered': 'Oblivion Remastered',
|
'fallout4': 'Fallout 4',
|
||||||
'enderal': 'Enderal'
|
'falloutnv': 'Fallout New Vegas',
|
||||||
}
|
'oblivion': 'Oblivion',
|
||||||
game_name = display_names.get(self._current_game_type, self._current_game_name)
|
'starfield': 'Starfield',
|
||||||
success_dialog = SuccessDialog(
|
'oblivion_remastered': 'Oblivion Remastered',
|
||||||
modlist_name=modlist_name,
|
'enderal': 'Enderal'
|
||||||
workflow_type="install",
|
}
|
||||||
time_taken=time_str,
|
game_name = display_names.get(self._current_game_type, self._current_game_name)
|
||||||
game_name=game_name,
|
success_dialog = SuccessDialog(
|
||||||
parent=self
|
modlist_name=modlist_name,
|
||||||
)
|
workflow_type="install",
|
||||||
success_dialog.show()
|
time_taken=time_str,
|
||||||
elif hasattr(self, '_manual_steps_retry_count') and self._manual_steps_retry_count >= 3:
|
game_name=game_name,
|
||||||
# Max retries reached - show failure message
|
parent=self
|
||||||
MessageService.critical(self, "Manual Steps Failed",
|
)
|
||||||
"Manual steps validation failed after multiple attempts.")
|
success_dialog.show()
|
||||||
else:
|
elif hasattr(self, '_manual_steps_retry_count') and self._manual_steps_retry_count >= 3:
|
||||||
# Configuration failed for other reasons
|
# Max retries reached - show failure message
|
||||||
MessageService.critical(self, "Configuration Failed",
|
MessageService.critical(self, "Manual Steps Failed",
|
||||||
"Post-install configuration failed. Please check the console output.")
|
"Manual steps validation failed after multiple attempts.")
|
||||||
|
else:
|
||||||
|
# Configuration failed for other reasons
|
||||||
|
MessageService.critical(self, "Configuration Failed",
|
||||||
|
"Post-install configuration failed. Please check the console output.")
|
||||||
|
except Exception as e:
|
||||||
|
# Ensure controls are re-enabled even on unexpected errors
|
||||||
|
self._enable_controls_after_operation()
|
||||||
|
raise
|
||||||
# Clean up thread
|
# Clean up thread
|
||||||
if hasattr(self, 'config_thread') and self.config_thread is not None:
|
if hasattr(self, 'config_thread') and self.config_thread is not None:
|
||||||
# Disconnect all signals to prevent "Internal C++ object already deleted" errors
|
# Disconnect all signals to prevent "Internal C++ object already deleted" errors
|
||||||
@@ -1940,8 +2036,8 @@ class InstallModlistScreen(QWidget):
|
|||||||
else:
|
else:
|
||||||
# User clicked Cancel or closed the dialog - cancel the workflow
|
# User clicked Cancel or closed the dialog - cancel the workflow
|
||||||
self._safe_append_text("\n🛑 Manual steps cancelled by user. Workflow stopped.")
|
self._safe_append_text("\n🛑 Manual steps cancelled by user. Workflow stopped.")
|
||||||
# Reset button states
|
# Re-enable all controls when workflow is cancelled
|
||||||
self.start_btn.setEnabled(True)
|
self._enable_controls_after_operation()
|
||||||
self.cancel_btn.setVisible(True)
|
self.cancel_btn.setVisible(True)
|
||||||
self.cancel_install_btn.setVisible(False)
|
self.cancel_install_btn.setVisible(False)
|
||||||
|
|
||||||
@@ -2513,8 +2609,8 @@ class InstallModlistScreen(QWidget):
|
|||||||
# Cleanup any remaining processes
|
# Cleanup any remaining processes
|
||||||
self.cleanup_processes()
|
self.cleanup_processes()
|
||||||
|
|
||||||
# Reset button states
|
# Reset button states and re-enable all controls
|
||||||
self.start_btn.setEnabled(True)
|
self._enable_controls_after_operation()
|
||||||
self.cancel_btn.setVisible(True)
|
self.cancel_btn.setVisible(True)
|
||||||
self.cancel_install_btn.setVisible(False)
|
self.cancel_install_btn.setVisible(False)
|
||||||
|
|
||||||
|
|||||||
@@ -106,8 +106,7 @@ class TuxbornInstallerScreen(QWidget):
|
|||||||
self.modlist_details = {} # {modlist_name: modlist_dict}
|
self.modlist_details = {} # {modlist_name: modlist_dict}
|
||||||
|
|
||||||
# Path for workflow log
|
# Path for workflow log
|
||||||
self.modlist_log_path = os.path.expanduser('~/Jackify/logs/Tuxborn_Installer_workflow.log')
|
self.refresh_paths()
|
||||||
os.makedirs(os.path.dirname(self.modlist_log_path), exist_ok=True)
|
|
||||||
|
|
||||||
# Initialize services early
|
# Initialize services early
|
||||||
from jackify.backend.services.api_key_service import APIKeyService
|
from jackify.backend.services.api_key_service import APIKeyService
|
||||||
@@ -440,6 +439,12 @@ class TuxbornInstallerScreen(QWidget):
|
|||||||
self.start_btn.clicked.connect(self.validate_and_start_install)
|
self.start_btn.clicked.connect(self.validate_and_start_install)
|
||||||
self.steam_restart_finished.connect(self._on_steam_restart_finished)
|
self.steam_restart_finished.connect(self._on_steam_restart_finished)
|
||||||
|
|
||||||
|
def refresh_paths(self):
|
||||||
|
"""Refresh cached paths when config changes."""
|
||||||
|
from jackify.shared.paths import get_jackify_logs_dir
|
||||||
|
self.modlist_log_path = get_jackify_logs_dir() / 'Tuxborn_Installer_workflow.log'
|
||||||
|
os.makedirs(os.path.dirname(self.modlist_log_path), exist_ok=True)
|
||||||
|
|
||||||
def _open_url_safe(self, url):
|
def _open_url_safe(self, url):
|
||||||
"""Safely open URL using subprocess to avoid Qt library conflicts in PyInstaller"""
|
"""Safely open URL using subprocess to avoid Qt library conflicts in PyInstaller"""
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|||||||
@@ -14,15 +14,21 @@ import shutil
|
|||||||
class LoggingHandler:
|
class LoggingHandler:
|
||||||
"""
|
"""
|
||||||
Central logging handler for Jackify.
|
Central logging handler for Jackify.
|
||||||
- Uses ~/Jackify/logs/ as the log directory.
|
- Uses configurable Jackify data directory for logs (default: ~/Jackify/logs/).
|
||||||
- Supports per-function log files (e.g., jackify-install-wabbajack.log).
|
- Supports per-function log files (e.g., jackify-install-wabbajack.log).
|
||||||
- Handles log rotation and log directory creation.
|
- Handles log rotation and log directory creation.
|
||||||
Usage:
|
Usage:
|
||||||
logger = LoggingHandler().setup_logger('install_wabbajack', 'jackify-install-wabbajack.log')
|
logger = LoggingHandler().setup_logger('install_wabbajack', 'jackify-install-wabbajack.log')
|
||||||
"""
|
"""
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.log_dir = Path.home() / "Jackify" / "logs"
|
# Don't cache log_dir - use property to get fresh path each time
|
||||||
self.ensure_log_directory()
|
self.ensure_log_directory()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def log_dir(self):
|
||||||
|
"""Get the current log directory (may change if config updated)."""
|
||||||
|
from jackify.shared.paths import get_jackify_logs_dir
|
||||||
|
return get_jackify_logs_dir()
|
||||||
|
|
||||||
def ensure_log_directory(self) -> None:
|
def ensure_log_directory(self) -> None:
|
||||||
"""Ensure the log directory exists."""
|
"""Ensure the log directory exists."""
|
||||||
|
|||||||
65
jackify/shared/paths.py
Normal file
65
jackify/shared/paths.py
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
"""
|
||||||
|
Path utilities for Jackify.
|
||||||
|
|
||||||
|
This module provides standardized path resolution for Jackify directories,
|
||||||
|
supporting configurable data directory while keeping config in a fixed location.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
|
def get_jackify_data_dir() -> Path:
|
||||||
|
"""
|
||||||
|
Get the configurable Jackify data directory.
|
||||||
|
|
||||||
|
This directory contains:
|
||||||
|
- downloaded_mod_lists/
|
||||||
|
- logs/
|
||||||
|
- temporary proton prefixes during installation
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Path: The Jackify data directory (always set in config)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Import here to avoid circular imports
|
||||||
|
from jackify.backend.handlers.config_handler import ConfigHandler
|
||||||
|
|
||||||
|
config_handler = ConfigHandler()
|
||||||
|
jackify_data_dir = config_handler.get('jackify_data_dir')
|
||||||
|
|
||||||
|
# Config handler now always ensures this is set, but fallback just in case
|
||||||
|
if jackify_data_dir:
|
||||||
|
return Path(jackify_data_dir).expanduser()
|
||||||
|
else:
|
||||||
|
return Path.home() / "Jackify"
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
# Emergency fallback if config system fails
|
||||||
|
return Path.home() / "Jackify"
|
||||||
|
|
||||||
|
|
||||||
|
def get_jackify_logs_dir() -> Path:
|
||||||
|
"""Get the logs directory within the Jackify data directory."""
|
||||||
|
return get_jackify_data_dir() / "logs"
|
||||||
|
|
||||||
|
|
||||||
|
def get_jackify_downloads_dir() -> Path:
|
||||||
|
"""Get the downloaded modlists directory within the Jackify data directory."""
|
||||||
|
return get_jackify_data_dir() / "downloaded_mod_lists"
|
||||||
|
|
||||||
|
|
||||||
|
def get_jackify_config_dir() -> Path:
|
||||||
|
"""
|
||||||
|
Get the Jackify configuration directory (always ~/.config/jackify).
|
||||||
|
|
||||||
|
This directory contains:
|
||||||
|
- config.json (settings)
|
||||||
|
- API keys and credentials
|
||||||
|
- Resource settings
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Path: Always ~/.config/jackify
|
||||||
|
"""
|
||||||
|
return Path.home() / ".config" / "jackify"
|
||||||
@@ -51,9 +51,15 @@ def _clear_screen_fallback():
|
|||||||
|
|
||||||
def print_jackify_banner():
|
def print_jackify_banner():
|
||||||
"""Print the Jackify application banner"""
|
"""Print the Jackify application banner"""
|
||||||
print("""
|
from jackify import __version__
|
||||||
|
version_text = f"Jackify CLI ({__version__})"
|
||||||
|
# Center the version text in the banner (72 chars content width)
|
||||||
|
padding = (72 - len(version_text)) // 2
|
||||||
|
centered_version = " " * padding + version_text + " " * (72 - len(version_text) - padding)
|
||||||
|
|
||||||
|
print(f"""
|
||||||
╔════════════════════════════════════════════════════════════════════════╗
|
╔════════════════════════════════════════════════════════════════════════╗
|
||||||
║ Jackify CLI (pre-alpha) ║
|
║{centered_version}║
|
||||||
║ ║
|
║ ║
|
||||||
║ A tool for installing and configuring modlists ║
|
║ A tool for installing and configuring modlists ║
|
||||||
║ & associated utilities on Linux ║
|
║ & associated utilities on Linux ║
|
||||||
|
|||||||
Reference in New Issue
Block a user