mirror of
https://github.com/Omni-guides/Jackify.git
synced 2026-01-17 19:47:00 +01:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f039cf9c24 | ||
|
|
d9ea1be347 | ||
|
|
a8862475d4 |
45
CHANGELOG.md
45
CHANGELOG.md
@@ -1,28 +1,31 @@
|
|||||||
# Jackify Changelog
|
# Jackify Changelog
|
||||||
|
|
||||||
## v0.1.6 - Advanced Proton Management & Lorerim Support
|
## v0.1.6.2 - Minor Bug Fixes
|
||||||
|
**Release Date:** October 23, 2025
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
- **Improved dotnet4.x Compatibility**: Universal registry fixes for better modlist compatibility
|
||||||
|
- **Fixed Proton 9 Override**: A bug meant that modlists with spaces in the name weren't being overridden correctly
|
||||||
|
- **Removed PageFileManager Plugin**: Eliminates Linux PageFile warnings
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## v0.1.6.1 - Fix dotnet40 install and expand Game Proton override
|
||||||
|
**Release Date:** October 21, 2025
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
- **Fixed dotnet40 Installation Failures**: Resolved widespread .NET Framework installation issues affecting multiple modlists
|
||||||
|
- **Added Lost Legacy Proton 9 Override**: Automatic ENB compatibility for Lost Legacy modlist
|
||||||
|
- **Fixed Symlinked Downloads**: Automatically handles symlinked download directories to avoid Wine compatibility issues
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## v0.1.6 - Lorerim Proton Support
|
||||||
**Release Date:** October 16, 2025
|
**Release Date:** October 16, 2025
|
||||||
|
|
||||||
### Major New Features
|
### New Features
|
||||||
- **Dual Proton Configuration**: Separate Install Proton and Game Proton version selection in Settings
|
- **Lorerim Proton Override**: Automatically selects Proton 9 for Lorerim installations (GE-Proton9-27 preferred)
|
||||||
- **Install Proton**: Optimized for modlist installation and texture processing (Experimental/GE-Proton 10+ recommended for performance)
|
- **Engine Update**: jackify-engine v0.3.17
|
||||||
- **Game Proton**: For game shortcuts (supports any Proton 9+ version)
|
|
||||||
- Independent configuration allows users to optimize for both installation speed and game compatibility
|
|
||||||
|
|
||||||
- **Lorerim Proton Override**: Automatic Proton 9 selection for Lorerim modlist installations
|
|
||||||
- Priority system: GE-Proton9-27 → Other GE-Proton 9 versions → Valve Proton 9 → user settings fallback
|
|
||||||
- User notification when override is applied
|
|
||||||
- Case-insensitive detection for Lorerim modlists
|
|
||||||
|
|
||||||
- **Configurable Component Installation Method**: User-selectable toggle in Settings
|
|
||||||
- **Optimized Mode** (default): Protontricks for dotnet40 (reliable), winetricks for other components (fast)
|
|
||||||
- **Legacy Mode**: Protontricks for all components (slower but maximum compatibility)
|
|
||||||
|
|
||||||
### Engine & Technical Improvements
|
|
||||||
- **jackify-engine v0.3.17**: Latest engine version with performance improvements
|
|
||||||
- **Windows 10 Prefix Timing**: Improved timing to match legacy script behavior
|
|
||||||
- **Self-Updater Enhancement**: Fixed auto-restart checkbox functionality
|
|
||||||
- **ProtontricksHandler**: Updated constructor calls across codebase for consistency
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -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.6"
|
__version__ = "0.1.6.2"
|
||||||
|
|||||||
@@ -71,16 +71,19 @@ class ModlistHandler:
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Canonical mapping of modlist-specific Wine components (from omni-guides.sh)
|
# Canonical mapping of modlist-specific Wine components (from omni-guides.sh)
|
||||||
|
# NOTE: dotnet4.x components disabled in v0.1.6.2 - replaced with universal registry fixes
|
||||||
MODLIST_WINE_COMPONENTS = {
|
MODLIST_WINE_COMPONENTS = {
|
||||||
"wildlander": ["dotnet472"],
|
# "wildlander": ["dotnet472"], # DISABLED: Universal registry fixes replace dotnet472 installation
|
||||||
"librum": ["dotnet40", "dotnet8"],
|
# "librum": ["dotnet40", "dotnet8"], # PARTIAL DISABLE: Keep dotnet8, remove dotnet40
|
||||||
"apostasy": ["dotnet40", "dotnet8"],
|
"librum": ["dotnet8"], # dotnet40 replaced with universal registry fixes
|
||||||
"nordicsouls": ["dotnet40"],
|
# "apostasy": ["dotnet40", "dotnet8"], # PARTIAL DISABLE: Keep dotnet8, remove dotnet40
|
||||||
"livingskyrim": ["dotnet40"],
|
"apostasy": ["dotnet8"], # dotnet40 replaced with universal registry fixes
|
||||||
"lsiv": ["dotnet40"],
|
# "nordicsouls": ["dotnet40"], # DISABLED: Universal registry fixes replace dotnet40 installation
|
||||||
"ls4": ["dotnet40"],
|
# "livingskyrim": ["dotnet40"], # DISABLED: Universal registry fixes replace dotnet40 installation
|
||||||
"lorerim": ["dotnet40"],
|
# "lsiv": ["dotnet40"], # DISABLED: Universal registry fixes replace dotnet40 installation
|
||||||
"lostlegacy": ["dotnet48"],
|
# "ls4": ["dotnet40"], # DISABLED: Universal registry fixes replace dotnet40 installation
|
||||||
|
# "lorerim": ["dotnet40"], # DISABLED: Universal registry fixes replace dotnet40 installation
|
||||||
|
# "lostlegacy": ["dotnet40"], # DISABLED: Universal registry fixes replace dotnet40 installation
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, steam_path_or_config: Union[Dict, str, Path, None] = None,
|
def __init__(self, steam_path_or_config: Union[Dict, str, Path, None] = None,
|
||||||
@@ -669,6 +672,12 @@ class ModlistHandler:
|
|||||||
return False
|
return False
|
||||||
self.logger.info("Step 3: Curated user.reg.modlist and system.reg.modlist applied successfully.")
|
self.logger.info("Step 3: Curated user.reg.modlist and system.reg.modlist applied successfully.")
|
||||||
|
|
||||||
|
# Step 3.5: Apply universal dotnet4.x compatibility registry fixes
|
||||||
|
if status_callback:
|
||||||
|
status_callback(f"{self._get_progress_timestamp()} Applying universal dotnet4.x compatibility fixes")
|
||||||
|
self.logger.info("Step 3.5: Applying universal dotnet4.x compatibility registry fixes...")
|
||||||
|
self._apply_universal_dotnet_fixes()
|
||||||
|
|
||||||
# Step 4: Install Wine Components
|
# Step 4: Install Wine Components
|
||||||
if status_callback:
|
if status_callback:
|
||||||
status_callback(f"{self._get_progress_timestamp()} Installing Wine components (this may take a while)")
|
status_callback(f"{self._get_progress_timestamp()} Installing Wine components (this may take a while)")
|
||||||
@@ -737,6 +746,14 @@ class ModlistHandler:
|
|||||||
self.logger.info(f"ModOrganizer.ini backed up to: {backup_path}")
|
self.logger.info(f"ModOrganizer.ini backed up to: {backup_path}")
|
||||||
self.logger.info("Step 6: Backing up ModOrganizer.ini... Done")
|
self.logger.info("Step 6: Backing up ModOrganizer.ini... Done")
|
||||||
|
|
||||||
|
# Step 6.5: Handle symlinked downloads directory
|
||||||
|
if status_callback:
|
||||||
|
status_callback(f"{self._get_progress_timestamp()} Checking for symlinked downloads directory")
|
||||||
|
self.logger.info("Step 6.5: Checking for symlinked downloads directory...")
|
||||||
|
if not self._handle_symlinked_downloads():
|
||||||
|
self.logger.warning("Warning during symlink handling (non-critical)")
|
||||||
|
self.logger.info("Step 6.5: Checking for symlinked downloads directory... Done")
|
||||||
|
|
||||||
# Step 7a: Detect Stock Game/Game Root path
|
# Step 7a: Detect Stock Game/Game Root path
|
||||||
if status_callback:
|
if status_callback:
|
||||||
status_callback(f"{self._get_progress_timestamp()} Detecting stock game path")
|
status_callback(f"{self._get_progress_timestamp()} Detecting stock game path")
|
||||||
@@ -863,21 +880,38 @@ class ModlistHandler:
|
|||||||
print("Warning: Failed to create dxvk.conf file.")
|
print("Warning: Failed to create dxvk.conf file.")
|
||||||
self.logger.info("Step 10: Creating dxvk.conf... Done")
|
self.logger.info("Step 10: Creating dxvk.conf... Done")
|
||||||
|
|
||||||
# Step 11a: Small Tasks - Delete Plugin
|
# Step 11a: Small Tasks - Delete Incompatible Plugins
|
||||||
if status_callback:
|
if status_callback:
|
||||||
status_callback(f"{self._get_progress_timestamp()} Deleting incompatible MO2 plugin")
|
status_callback(f"{self._get_progress_timestamp()} Deleting incompatible MO2 plugins")
|
||||||
self.logger.info("Step 11a: Deleting incompatible MO2 plugin (FixGameRegKey.py)...")
|
self.logger.info("Step 11a: Deleting incompatible MO2 plugins...")
|
||||||
plugin_path = Path(self.modlist_dir) / "plugins" / "FixGameRegKey.py"
|
|
||||||
if plugin_path.exists():
|
# Delete FixGameRegKey.py plugin
|
||||||
|
fixgamereg_path = Path(self.modlist_dir) / "plugins" / "FixGameRegKey.py"
|
||||||
|
if fixgamereg_path.exists():
|
||||||
try:
|
try:
|
||||||
plugin_path.unlink()
|
fixgamereg_path.unlink()
|
||||||
self.logger.info("FixGameRegKey.py plugin deleted successfully.")
|
self.logger.info("FixGameRegKey.py plugin deleted successfully.")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.warning(f"Failed to delete FixGameRegKey.py plugin: {e}")
|
self.logger.warning(f"Failed to delete FixGameRegKey.py plugin: {e}")
|
||||||
print("Warning: Failed to delete incompatible plugin file.")
|
print("Warning: Failed to delete FixGameRegKey.py plugin file.")
|
||||||
else:
|
else:
|
||||||
self.logger.debug("FixGameRegKey.py plugin not found (this is normal).")
|
self.logger.debug("FixGameRegKey.py plugin not found (this is normal).")
|
||||||
self.logger.info("Step 11a: Plugin deletion check complete.")
|
|
||||||
|
# Delete PageFileManager plugin directory (Linux has no PageFile)
|
||||||
|
pagefilemgr_path = Path(self.modlist_dir) / "plugins" / "PageFileManager"
|
||||||
|
if pagefilemgr_path.exists():
|
||||||
|
try:
|
||||||
|
import shutil
|
||||||
|
shutil.rmtree(pagefilemgr_path)
|
||||||
|
self.logger.info("PageFileManager plugin directory deleted successfully.")
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.warning(f"Failed to delete PageFileManager plugin directory: {e}")
|
||||||
|
print("Warning: Failed to delete PageFileManager plugin directory.")
|
||||||
|
else:
|
||||||
|
self.logger.debug("PageFileManager plugin not found (this is normal).")
|
||||||
|
|
||||||
|
self.logger.info("Step 11a: Incompatible plugin deletion check complete.")
|
||||||
|
|
||||||
|
|
||||||
# Step 11b: Download Font
|
# Step 11b: Download Font
|
||||||
if status_callback:
|
if status_callback:
|
||||||
@@ -1346,19 +1380,210 @@ class ModlistHandler:
|
|||||||
self.logger.warning("Cannot re-enforce Windows 10 mode - prefix path not found")
|
self.logger.warning("Cannot re-enforce Windows 10 mode - prefix path not found")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Get wine binary path
|
# Use winetricks handler to set Windows 10 mode
|
||||||
wine_binary = PathHandler.get_wine_binary_for_appid(str(self.appid))
|
winetricks_handler = WinetricksHandler()
|
||||||
|
wine_binary = winetricks_handler._get_wine_binary_for_prefix(str(prefix_path))
|
||||||
if not wine_binary:
|
if not wine_binary:
|
||||||
self.logger.warning("Cannot re-enforce Windows 10 mode - wine binary not found")
|
self.logger.warning("Cannot re-enforce Windows 10 mode - wine binary not found")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Use winetricks handler to set Windows 10 mode
|
|
||||||
winetricks_handler = WinetricksHandler()
|
|
||||||
winetricks_handler._set_windows_10_mode(str(prefix_path), wine_binary)
|
winetricks_handler._set_windows_10_mode(str(prefix_path), wine_binary)
|
||||||
|
|
||||||
self.logger.info("✓ Windows 10 mode re-enforced after modlist-specific configurations")
|
self.logger.info("Windows 10 mode re-enforced after modlist-specific configurations")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.warning(f"Error re-enforcing Windows 10 mode: {e}")
|
self.logger.warning(f"Error re-enforcing Windows 10 mode: {e}")
|
||||||
|
|
||||||
# (Ensure EOF is clean and no extra incorrect methods exist below)
|
def _handle_symlinked_downloads(self) -> bool:
|
||||||
|
"""
|
||||||
|
Check if downloads_directory in ModOrganizer.ini points to a symlink.
|
||||||
|
If it does, comment out the line to force MO2 to use default behavior.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True on success or no action needed, False on error
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
import configparser
|
||||||
|
import os
|
||||||
|
|
||||||
|
if not self.modlist_ini or not os.path.exists(self.modlist_ini):
|
||||||
|
self.logger.warning("ModOrganizer.ini not found for symlink check")
|
||||||
|
return True # Non-critical
|
||||||
|
|
||||||
|
# Read the INI file
|
||||||
|
config = configparser.ConfigParser(allow_no_value=True, delimiters=['='])
|
||||||
|
config.optionxform = str # Preserve case sensitivity
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Read file manually to handle BOM
|
||||||
|
with open(self.modlist_ini, 'r', encoding='utf-8-sig') as f:
|
||||||
|
config.read_file(f)
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
with open(self.modlist_ini, 'r', encoding='latin-1') as f:
|
||||||
|
config.read_file(f)
|
||||||
|
|
||||||
|
# Check if downloads_directory or download_directory exists and is a symlink
|
||||||
|
downloads_key = None
|
||||||
|
downloads_path = None
|
||||||
|
|
||||||
|
if 'General' in config:
|
||||||
|
# Check for both possible key names
|
||||||
|
if 'downloads_directory' in config['General']:
|
||||||
|
downloads_key = 'downloads_directory'
|
||||||
|
downloads_path = config['General']['downloads_directory']
|
||||||
|
elif 'download_directory' in config['General']:
|
||||||
|
downloads_key = 'download_directory'
|
||||||
|
downloads_path = config['General']['download_directory']
|
||||||
|
|
||||||
|
if downloads_path:
|
||||||
|
|
||||||
|
if downloads_path and os.path.exists(downloads_path):
|
||||||
|
# Check if the path or any parent directory contains symlinks
|
||||||
|
def has_symlink_in_path(path):
|
||||||
|
"""Check if path or any parent directory is a symlink"""
|
||||||
|
current_path = Path(path).resolve()
|
||||||
|
check_path = Path(path)
|
||||||
|
|
||||||
|
# Walk up the path checking each component
|
||||||
|
for parent in [check_path] + list(check_path.parents):
|
||||||
|
if parent.is_symlink():
|
||||||
|
return True, str(parent)
|
||||||
|
return False, None
|
||||||
|
|
||||||
|
has_symlink, symlink_path = has_symlink_in_path(downloads_path)
|
||||||
|
if has_symlink:
|
||||||
|
self.logger.info(f"Detected symlink in downloads directory path: {symlink_path} -> {downloads_path}")
|
||||||
|
self.logger.info("Commenting out downloads_directory to avoid Wine symlink issues")
|
||||||
|
|
||||||
|
# Read the file manually to preserve comments and formatting
|
||||||
|
with open(self.modlist_ini, 'r', encoding='utf-8') as f:
|
||||||
|
lines = f.readlines()
|
||||||
|
|
||||||
|
# Find and comment out the downloads directory line
|
||||||
|
modified = False
|
||||||
|
for i, line in enumerate(lines):
|
||||||
|
if line.strip().startswith(f'{downloads_key}='):
|
||||||
|
lines[i] = '#' + line # Comment out the line
|
||||||
|
modified = True
|
||||||
|
break
|
||||||
|
|
||||||
|
if modified:
|
||||||
|
# Write the modified file back
|
||||||
|
with open(self.modlist_ini, 'w', encoding='utf-8') as f:
|
||||||
|
f.writelines(lines)
|
||||||
|
self.logger.info(f"{downloads_key} line commented out successfully")
|
||||||
|
else:
|
||||||
|
self.logger.warning("downloads_directory line not found in file")
|
||||||
|
else:
|
||||||
|
self.logger.debug(f"downloads_directory is not a symlink: {downloads_path}")
|
||||||
|
else:
|
||||||
|
self.logger.debug("downloads_directory path does not exist or is empty")
|
||||||
|
else:
|
||||||
|
self.logger.debug("No downloads_directory found in ModOrganizer.ini")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Error handling symlinked downloads: {e}", exc_info=True)
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _apply_universal_dotnet_fixes(self):
|
||||||
|
"""Apply universal dotnet4.x compatibility registry fixes to ALL modlists"""
|
||||||
|
try:
|
||||||
|
prefix_path = os.path.join(str(self.compat_data_path), "pfx")
|
||||||
|
if not os.path.exists(prefix_path):
|
||||||
|
self.logger.warning(f"Prefix path not found: {prefix_path}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
self.logger.info("Applying universal dotnet4.x compatibility registry fixes...")
|
||||||
|
|
||||||
|
# Find the appropriate Wine binary to use for registry operations
|
||||||
|
wine_binary = self._find_wine_binary_for_registry()
|
||||||
|
if not wine_binary:
|
||||||
|
self.logger.error("Could not find Wine binary for registry operations")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Set environment for Wine registry operations
|
||||||
|
env = os.environ.copy()
|
||||||
|
env['WINEPREFIX'] = prefix_path
|
||||||
|
env['WINEDEBUG'] = '-all' # Suppress Wine debug output
|
||||||
|
|
||||||
|
# Registry fix 1: Set mscoree=native DLL override
|
||||||
|
# This tells Wine to use native .NET runtime instead of Wine's implementation
|
||||||
|
self.logger.debug("Setting mscoree=native DLL override...")
|
||||||
|
cmd1 = [
|
||||||
|
wine_binary, 'reg', 'add',
|
||||||
|
'HKEY_CURRENT_USER\\Software\\Wine\\DllOverrides',
|
||||||
|
'/v', 'mscoree', '/t', 'REG_SZ', '/d', 'native', '/f'
|
||||||
|
]
|
||||||
|
|
||||||
|
result1 = subprocess.run(cmd1, env=env, capture_output=True, text=True)
|
||||||
|
if result1.returncode == 0:
|
||||||
|
self.logger.info("Successfully applied mscoree=native DLL override")
|
||||||
|
else:
|
||||||
|
self.logger.warning(f"Failed to set mscoree DLL override: {result1.stderr}")
|
||||||
|
|
||||||
|
# Registry fix 2: Set OnlyUseLatestCLR=1
|
||||||
|
# This prevents .NET version conflicts by using the latest CLR
|
||||||
|
self.logger.debug("Setting OnlyUseLatestCLR=1 registry entry...")
|
||||||
|
cmd2 = [
|
||||||
|
wine_binary, 'reg', 'add',
|
||||||
|
'HKEY_LOCAL_MACHINE\\Software\\Microsoft\\.NETFramework',
|
||||||
|
'/v', 'OnlyUseLatestCLR', '/t', 'REG_DWORD', '/d', '1', '/f'
|
||||||
|
]
|
||||||
|
|
||||||
|
result2 = subprocess.run(cmd2, env=env, capture_output=True, text=True)
|
||||||
|
if result2.returncode == 0:
|
||||||
|
self.logger.info("Successfully applied OnlyUseLatestCLR=1 registry entry")
|
||||||
|
else:
|
||||||
|
self.logger.warning(f"Failed to set OnlyUseLatestCLR: {result2.stderr}")
|
||||||
|
|
||||||
|
# Both fixes applied - this should eliminate dotnet4.x installation requirements
|
||||||
|
if result1.returncode == 0 and result2.returncode == 0:
|
||||||
|
self.logger.info("Universal dotnet4.x compatibility fixes applied successfully")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
self.logger.warning("Some dotnet4.x registry fixes failed, but continuing...")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Failed to apply universal dotnet4.x fixes: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _find_wine_binary_for_registry(self) -> Optional[str]:
|
||||||
|
"""Find the appropriate Wine binary for registry operations"""
|
||||||
|
try:
|
||||||
|
# Method 1: Try to detect from Steam's config or use Proton from compat data
|
||||||
|
# Look for wine binary in common Proton locations
|
||||||
|
proton_paths = [
|
||||||
|
os.path.expanduser("~/.local/share/Steam/compatibilitytools.d"),
|
||||||
|
os.path.expanduser("~/.steam/steam/steamapps/common")
|
||||||
|
]
|
||||||
|
|
||||||
|
for base_path in proton_paths:
|
||||||
|
if os.path.exists(base_path):
|
||||||
|
for item in os.listdir(base_path):
|
||||||
|
if 'proton' in item.lower():
|
||||||
|
wine_path = os.path.join(base_path, item, 'files', 'bin', 'wine')
|
||||||
|
if os.path.exists(wine_path):
|
||||||
|
self.logger.debug(f"Found Wine binary: {wine_path}")
|
||||||
|
return wine_path
|
||||||
|
|
||||||
|
# Method 2: Fallback to system wine if available
|
||||||
|
try:
|
||||||
|
result = subprocess.run(['which', 'wine'], capture_output=True, text=True)
|
||||||
|
if result.returncode == 0:
|
||||||
|
wine_path = result.stdout.strip()
|
||||||
|
self.logger.debug(f"Using system Wine binary: {wine_path}")
|
||||||
|
return wine_path
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.logger.error("No suitable Wine binary found for registry operations")
|
||||||
|
return None
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Error finding Wine binary: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
@@ -488,7 +488,7 @@ class ProtontricksHandler:
|
|||||||
if "ShowDotFiles" not in content:
|
if "ShowDotFiles" not in content:
|
||||||
logger.debug(f"Adding ShowDotFiles entry to {user_reg_path}")
|
logger.debug(f"Adding ShowDotFiles entry to {user_reg_path}")
|
||||||
with open(user_reg_path, 'a', encoding='utf-8') as f:
|
with open(user_reg_path, 'a', encoding='utf-8') as f:
|
||||||
f.write('\n[Software\\Wine] 1603891765\n')
|
f.write('\n[Software\\Wine] 1603891765\n')
|
||||||
f.write('"ShowDotFiles"="Y"\n')
|
f.write('"ShowDotFiles"="Y"\n')
|
||||||
dotfiles_set_success = True # Count file write as success too
|
dotfiles_set_success = True # Count file write as success too
|
||||||
else:
|
else:
|
||||||
@@ -497,7 +497,7 @@ class ProtontricksHandler:
|
|||||||
else:
|
else:
|
||||||
logger.warning(f"user.reg not found at {user_reg_path}, creating it.")
|
logger.warning(f"user.reg not found at {user_reg_path}, creating it.")
|
||||||
with open(user_reg_path, 'w', encoding='utf-8') as f:
|
with open(user_reg_path, 'w', encoding='utf-8') as f:
|
||||||
f.write('[Software\\Wine] 1603891765\n')
|
f.write('[Software\\Wine] 1603891765\n')
|
||||||
f.write('"ShowDotFiles"="Y"\n')
|
f.write('"ShowDotFiles"="Y"\n')
|
||||||
dotfiles_set_success = True # Creating file counts as success
|
dotfiles_set_success = True # Creating file counts as success
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -262,10 +262,20 @@ class WinetricksHandler:
|
|||||||
config_handler = ConfigHandler()
|
config_handler = ConfigHandler()
|
||||||
use_winetricks = config_handler.get('use_winetricks_for_components', True)
|
use_winetricks = config_handler.get('use_winetricks_for_components', True)
|
||||||
|
|
||||||
|
# Legacy .NET Framework versions that are problematic in Wine/Proton
|
||||||
|
# DISABLED in v0.1.6.2: Universal registry fixes replace dotnet4.x installation
|
||||||
|
# legacy_dotnet_versions = ['dotnet40', 'dotnet472', 'dotnet48']
|
||||||
|
legacy_dotnet_versions = [] # ALL dotnet4.x versions disabled - universal registry fixes handle compatibility
|
||||||
|
|
||||||
|
# Check if any legacy .NET Framework versions are present
|
||||||
|
has_legacy_dotnet = any(comp in components_to_install for comp in legacy_dotnet_versions)
|
||||||
|
|
||||||
# Choose installation method based on user preference and components
|
# Choose installation method based on user preference and components
|
||||||
if use_winetricks and "dotnet40" in components_to_install:
|
# HYBRID APPROACH MOSTLY DISABLED: dotnet40/dotnet472 replaced with universal registry fixes
|
||||||
self.logger.info("Using optimized approach: protontricks for dotnet40 (reliable), winetricks for other components (fast)")
|
if has_legacy_dotnet:
|
||||||
return self._install_components_hybrid_approach(components_to_install, wineprefix, game_var)
|
legacy_found = [comp for comp in legacy_dotnet_versions if comp in components_to_install]
|
||||||
|
self.logger.info(f"Using hybrid approach: protontricks for legacy .NET versions {legacy_found} (reliable), {'winetricks' if use_winetricks else 'protontricks'} for other components")
|
||||||
|
return self._install_components_hybrid_approach(components_to_install, wineprefix, game_var, use_winetricks)
|
||||||
elif not use_winetricks:
|
elif not use_winetricks:
|
||||||
self.logger.info("Using legacy approach: protontricks for all components")
|
self.logger.info("Using legacy approach: protontricks for all components")
|
||||||
return self._install_components_protontricks_only(components_to_install, wineprefix, game_var)
|
return self._install_components_protontricks_only(components_to_install, wineprefix, game_var)
|
||||||
@@ -453,7 +463,7 @@ class WinetricksHandler:
|
|||||||
)
|
)
|
||||||
|
|
||||||
if result.returncode == 0:
|
if result.returncode == 0:
|
||||||
self.logger.info(f"✓ {component} installed successfully")
|
self.logger.info(f"{component} installed successfully")
|
||||||
component_success = True
|
component_success = True
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
@@ -467,13 +477,13 @@ class WinetricksHandler:
|
|||||||
try:
|
try:
|
||||||
with open(log_path, 'r') as f:
|
with open(log_path, 'r') as f:
|
||||||
if 'dotnet40' in f.read():
|
if 'dotnet40' in f.read():
|
||||||
self.logger.info("✓ dotnet40 confirmed in winetricks.log")
|
self.logger.info("dotnet40 confirmed in winetricks.log")
|
||||||
component_success = True
|
component_success = True
|
||||||
break
|
break
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.warning(f"Could not read winetricks.log: {e}")
|
self.logger.warning(f"Could not read winetricks.log: {e}")
|
||||||
|
|
||||||
self.logger.error(f"✗ {component} failed (attempt {attempt}): {result.stderr.strip()}")
|
self.logger.error(f"{component} failed (attempt {attempt}): {result.stderr.strip()}")
|
||||||
self.logger.debug(f"Full stdout for {component}: {result.stdout.strip()}")
|
self.logger.debug(f"Full stdout for {component}: {result.stdout.strip()}")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -483,63 +493,70 @@ class WinetricksHandler:
|
|||||||
self.logger.error(f"Failed to install {component} after {max_attempts} attempts")
|
self.logger.error(f"Failed to install {component} after {max_attempts} attempts")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self.logger.info("✓ All components installed successfully using separate sessions")
|
self.logger.info("All components installed successfully using separate sessions")
|
||||||
# Set Windows 10 mode after all component installation (matches legacy script timing)
|
# Set Windows 10 mode after all component installation (matches legacy script timing)
|
||||||
self._set_windows_10_mode(wineprefix, env.get('WINE', ''))
|
self._set_windows_10_mode(wineprefix, env.get('WINE', ''))
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _install_components_hybrid_approach(self, components: list, wineprefix: str, game_var: str) -> bool:
|
def _install_components_hybrid_approach(self, components: list, wineprefix: str, game_var: str, use_winetricks: bool = True) -> bool:
|
||||||
"""
|
"""
|
||||||
Hybrid approach: Install dotnet40 with protontricks (known to work),
|
Hybrid approach: Install legacy .NET Framework versions with protontricks (reliable),
|
||||||
then install remaining components with winetricks (faster for other components).
|
then install remaining components with winetricks OR protontricks based on user preference.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
components: List of all components to install
|
components: List of all components to install
|
||||||
wineprefix: Wine prefix path
|
wineprefix: Wine prefix path
|
||||||
game_var: Game variable for AppID detection
|
game_var: Game variable for AppID detection
|
||||||
|
use_winetricks: Whether to use winetricks for non-legacy components
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
bool: True if all installations succeeded, False otherwise
|
bool: True if all installations succeeded, False otherwise
|
||||||
"""
|
"""
|
||||||
self.logger.info("Starting hybrid installation approach")
|
self.logger.info("Starting hybrid installation approach")
|
||||||
|
|
||||||
# Separate dotnet40 (protontricks) from other components (winetricks)
|
# Legacy .NET Framework versions that need protontricks
|
||||||
protontricks_components = [comp for comp in components if comp == "dotnet40"]
|
legacy_dotnet_versions = ['dotnet40', 'dotnet472', 'dotnet48']
|
||||||
other_components = [comp for comp in components if comp != "dotnet40"]
|
|
||||||
|
# Separate legacy .NET (protontricks) from other components (winetricks)
|
||||||
|
protontricks_components = [comp for comp in components if comp in legacy_dotnet_versions]
|
||||||
|
other_components = [comp for comp in components if comp not in legacy_dotnet_versions]
|
||||||
|
|
||||||
self.logger.info(f"Protontricks components: {protontricks_components}")
|
self.logger.info(f"Protontricks components: {protontricks_components}")
|
||||||
self.logger.info(f"Other components: {other_components}")
|
self.logger.info(f"Other components: {other_components}")
|
||||||
|
|
||||||
# Step 1: Install dotnet40 with protontricks if present
|
# Step 1: Install legacy .NET Framework versions with protontricks if present
|
||||||
if protontricks_components:
|
if protontricks_components:
|
||||||
self.logger.info(f"Installing {protontricks_components} using protontricks...")
|
self.logger.info(f"Installing legacy .NET versions {protontricks_components} using protontricks...")
|
||||||
if not self._install_dotnet40_with_protontricks(wineprefix, game_var):
|
if not self._install_legacy_dotnet_with_protontricks(protontricks_components, wineprefix, game_var):
|
||||||
self.logger.error(f"Failed to install {protontricks_components} with protontricks")
|
self.logger.error(f"Failed to install {protontricks_components} with protontricks")
|
||||||
return False
|
return False
|
||||||
self.logger.info(f"✓ {protontricks_components} installation completed successfully with protontricks")
|
self.logger.info(f"{protontricks_components} installation completed successfully with protontricks")
|
||||||
|
|
||||||
# Step 2: Install remaining components with winetricks if any
|
# Step 2: Install remaining components if any
|
||||||
if other_components:
|
if other_components:
|
||||||
self.logger.info(f"Installing remaining components with winetricks: {other_components}")
|
if use_winetricks:
|
||||||
|
self.logger.info(f"Installing remaining components with winetricks: {other_components}")
|
||||||
|
# Use existing winetricks logic for other components
|
||||||
|
env = self._prepare_winetricks_environment(wineprefix)
|
||||||
|
if not env:
|
||||||
|
return False
|
||||||
|
return self._install_components_with_winetricks(other_components, wineprefix, env)
|
||||||
|
else:
|
||||||
|
self.logger.info(f"Installing remaining components with protontricks: {other_components}")
|
||||||
|
return self._install_components_protontricks_only(other_components, wineprefix, game_var)
|
||||||
|
|
||||||
# Use existing winetricks logic for other components
|
self.logger.info("Hybrid component installation completed successfully")
|
||||||
env = self._prepare_winetricks_environment(wineprefix)
|
|
||||||
if not env:
|
|
||||||
return False
|
|
||||||
|
|
||||||
return self._install_components_with_winetricks(other_components, wineprefix, env)
|
|
||||||
|
|
||||||
self.logger.info("✓ Hybrid component installation completed successfully")
|
|
||||||
# Set Windows 10 mode after all component installation (matches legacy script timing)
|
# Set Windows 10 mode after all component installation (matches legacy script timing)
|
||||||
wine_binary = self._get_wine_binary_for_prefix(wineprefix)
|
wine_binary = self._get_wine_binary_for_prefix(wineprefix)
|
||||||
self._set_windows_10_mode(wineprefix, wine_binary)
|
self._set_windows_10_mode(wineprefix, wine_binary)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _install_dotnet40_with_protontricks(self, wineprefix: str, game_var: str) -> bool:
|
def _install_legacy_dotnet_with_protontricks(self, legacy_components: list, wineprefix: str, game_var: str) -> bool:
|
||||||
"""
|
"""
|
||||||
Install dotnet40 using protontricks (known to work reliably).
|
Install legacy .NET Framework versions using protontricks (known to work more reliably).
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
legacy_components: List of legacy .NET components to install (dotnet40, dotnet472, dotnet48)
|
||||||
wineprefix: Wine prefix path
|
wineprefix: Wine prefix path
|
||||||
game_var: Game variable for AppID detection
|
game_var: Game variable for AppID detection
|
||||||
|
|
||||||
@@ -575,21 +592,28 @@ class WinetricksHandler:
|
|||||||
|
|
||||||
# Detect protontricks availability
|
# Detect protontricks availability
|
||||||
if not protontricks_handler.detect_protontricks():
|
if not protontricks_handler.detect_protontricks():
|
||||||
self.logger.error("Protontricks not available for dotnet40 installation")
|
self.logger.error(f"Protontricks not available for legacy .NET installation: {legacy_components}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Install dotnet40 using protontricks
|
# Install legacy .NET components using protontricks
|
||||||
success = protontricks_handler.install_wine_components(appid, game_var, ["dotnet40"])
|
success = protontricks_handler.install_wine_components(appid, game_var, legacy_components)
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
self.logger.info("✓ dotnet40 installed successfully with protontricks")
|
self.logger.info(f"Legacy .NET components {legacy_components} installed successfully with protontricks")
|
||||||
|
|
||||||
|
# Enable dotfiles and symlinks for the prefix
|
||||||
|
if protontricks_handler.enable_dotfiles(appid):
|
||||||
|
self.logger.info("Enabled dotfiles and symlinks support")
|
||||||
|
else:
|
||||||
|
self.logger.warning("Failed to enable dotfiles/symlinks (non-critical)")
|
||||||
|
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
self.logger.error("✗ dotnet40 installation failed with protontricks")
|
self.logger.error(f"Legacy .NET components {legacy_components} installation failed with protontricks")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f"Error installing dotnet40 with protontricks: {e}", exc_info=True)
|
self.logger.error(f"Error installing legacy .NET components {legacy_components} with protontricks: {e}", exc_info=True)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _prepare_winetricks_environment(self, wineprefix: str) -> Optional[dict]:
|
def _prepare_winetricks_environment(self, wineprefix: str) -> Optional[dict]:
|
||||||
@@ -699,13 +723,13 @@ class WinetricksHandler:
|
|||||||
)
|
)
|
||||||
|
|
||||||
if result.returncode == 0:
|
if result.returncode == 0:
|
||||||
self.logger.info(f"✓ Winetricks components installed successfully: {components}")
|
self.logger.info(f"Winetricks components installed successfully: {components}")
|
||||||
# Set Windows 10 mode after component installation (matches legacy script timing)
|
# Set Windows 10 mode after component installation (matches legacy script timing)
|
||||||
wine_binary = env.get('WINE', '')
|
wine_binary = env.get('WINE', '')
|
||||||
self._set_windows_10_mode(env.get('WINEPREFIX', ''), wine_binary)
|
self._set_windows_10_mode(env.get('WINEPREFIX', ''), wine_binary)
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
self.logger.error(f"✗ Winetricks failed (attempt {attempt}): {result.stderr.strip()}")
|
self.logger.error(f"Winetricks failed (attempt {attempt}): {result.stderr.strip()}")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f"Error during winetricks run (attempt {attempt}): {e}")
|
self.logger.error(f"Error during winetricks run (attempt {attempt}): {e}")
|
||||||
@@ -729,7 +753,7 @@ class WinetricksHandler:
|
|||||||
], env=env, capture_output=True, text=True, timeout=300)
|
], env=env, capture_output=True, text=True, timeout=300)
|
||||||
|
|
||||||
if result.returncode == 0:
|
if result.returncode == 0:
|
||||||
self.logger.info("✓ Windows 10 mode set successfully")
|
self.logger.info("Windows 10 mode set successfully")
|
||||||
else:
|
else:
|
||||||
self.logger.warning(f"Could not set Windows 10 mode: {result.stderr}")
|
self.logger.warning(f"Could not set Windows 10 mode: {result.stderr}")
|
||||||
|
|
||||||
@@ -768,13 +792,13 @@ class WinetricksHandler:
|
|||||||
success = protontricks_handler.install_wine_components(appid, game_var, components)
|
success = protontricks_handler.install_wine_components(appid, game_var, components)
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
self.logger.info("✓ All components installed successfully with protontricks")
|
self.logger.info("All components installed successfully with protontricks")
|
||||||
# Set Windows 10 mode after component installation
|
# Set Windows 10 mode after component installation
|
||||||
wine_binary = self._get_wine_binary_for_prefix(wineprefix)
|
wine_binary = self._get_wine_binary_for_prefix(wineprefix)
|
||||||
self._set_windows_10_mode(wineprefix, wine_binary)
|
self._set_windows_10_mode(wineprefix, wine_binary)
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
self.logger.error("✗ Component installation failed with protontricks")
|
self.logger.error("Component installation failed with protontricks")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -50,13 +50,22 @@ class AutomatedPrefixService:
|
|||||||
from jackify.backend.handlers.wine_utils import WineUtils
|
from jackify.backend.handlers.wine_utils import WineUtils
|
||||||
|
|
||||||
# Check for Lorerim-specific Proton override first
|
# Check for Lorerim-specific Proton override first
|
||||||
if modlist_name and modlist_name.lower() == 'lorerim':
|
modlist_normalized = modlist_name.lower().replace(" ", "") if modlist_name else ""
|
||||||
|
if modlist_normalized == 'lorerim':
|
||||||
lorerim_proton = self._get_lorerim_preferred_proton()
|
lorerim_proton = self._get_lorerim_preferred_proton()
|
||||||
if lorerim_proton:
|
if lorerim_proton:
|
||||||
logger.info(f"Lorerim detected: Using {lorerim_proton} instead of user settings")
|
logger.info(f"Lorerim detected: Using {lorerim_proton} instead of user settings")
|
||||||
self._store_proton_override_notification("Lorerim", lorerim_proton)
|
self._store_proton_override_notification("Lorerim", lorerim_proton)
|
||||||
return lorerim_proton
|
return lorerim_proton
|
||||||
|
|
||||||
|
# Check for Lost Legacy-specific Proton override (needs Proton 9 for ENB compatibility)
|
||||||
|
if modlist_normalized == 'lostlegacy':
|
||||||
|
lostlegacy_proton = self._get_lorerim_preferred_proton() # Use same logic as Lorerim
|
||||||
|
if lostlegacy_proton:
|
||||||
|
logger.info(f"Lost Legacy detected: Using {lostlegacy_proton} instead of user settings (ENB compatibility)")
|
||||||
|
self._store_proton_override_notification("Lost Legacy", lostlegacy_proton)
|
||||||
|
return lostlegacy_proton
|
||||||
|
|
||||||
config_handler = ConfigHandler()
|
config_handler = ConfigHandler()
|
||||||
user_proton_path = config_handler.get_game_proton_path()
|
user_proton_path = config_handler.get_game_proton_path()
|
||||||
|
|
||||||
@@ -2972,14 +2981,115 @@ echo Prefix creation complete.
|
|||||||
logger.error(f"Failed to update registry path: {e}")
|
logger.error(f"Failed to update registry path: {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def _apply_universal_dotnet_fixes(self, modlist_compatdata_path: str):
|
||||||
|
"""Apply universal dotnet4.x compatibility registry fixes to ALL modlists"""
|
||||||
|
try:
|
||||||
|
prefix_path = os.path.join(modlist_compatdata_path, "pfx")
|
||||||
|
if not os.path.exists(prefix_path):
|
||||||
|
logger.warning(f"Prefix path not found: {prefix_path}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
logger.info("Applying universal dotnet4.x compatibility registry fixes...")
|
||||||
|
|
||||||
|
# Find the appropriate Wine binary to use for registry operations
|
||||||
|
wine_binary = self._find_wine_binary_for_registry(modlist_compatdata_path)
|
||||||
|
if not wine_binary:
|
||||||
|
logger.error("Could not find Wine binary for registry operations")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Set environment for Wine registry operations
|
||||||
|
env = os.environ.copy()
|
||||||
|
env['WINEPREFIX'] = prefix_path
|
||||||
|
env['WINEDEBUG'] = '-all' # Suppress Wine debug output
|
||||||
|
|
||||||
|
# Registry fix 1: Set mscoree=native DLL override
|
||||||
|
# This tells Wine to use native .NET runtime instead of Wine's implementation
|
||||||
|
logger.debug("Setting mscoree=native DLL override...")
|
||||||
|
cmd1 = [
|
||||||
|
wine_binary, 'reg', 'add',
|
||||||
|
'HKEY_CURRENT_USER\\Software\\Wine\\DllOverrides',
|
||||||
|
'/v', 'mscoree', '/t', 'REG_SZ', '/d', 'native', '/f'
|
||||||
|
]
|
||||||
|
|
||||||
|
result1 = subprocess.run(cmd1, env=env, capture_output=True, text=True)
|
||||||
|
if result1.returncode == 0:
|
||||||
|
logger.info("Successfully applied mscoree=native DLL override")
|
||||||
|
else:
|
||||||
|
logger.warning(f"Failed to set mscoree DLL override: {result1.stderr}")
|
||||||
|
|
||||||
|
# Registry fix 2: Set OnlyUseLatestCLR=1
|
||||||
|
# This prevents .NET version conflicts by using the latest CLR
|
||||||
|
logger.debug("Setting OnlyUseLatestCLR=1 registry entry...")
|
||||||
|
cmd2 = [
|
||||||
|
wine_binary, 'reg', 'add',
|
||||||
|
'HKEY_LOCAL_MACHINE\\Software\\Microsoft\\.NETFramework',
|
||||||
|
'/v', 'OnlyUseLatestCLR', '/t', 'REG_DWORD', '/d', '1', '/f'
|
||||||
|
]
|
||||||
|
|
||||||
|
result2 = subprocess.run(cmd2, env=env, capture_output=True, text=True)
|
||||||
|
if result2.returncode == 0:
|
||||||
|
logger.info("Successfully applied OnlyUseLatestCLR=1 registry entry")
|
||||||
|
else:
|
||||||
|
logger.warning(f"Failed to set OnlyUseLatestCLR: {result2.stderr}")
|
||||||
|
|
||||||
|
# Both fixes applied - this should eliminate dotnet4.x installation requirements
|
||||||
|
if result1.returncode == 0 and result2.returncode == 0:
|
||||||
|
logger.info("Universal dotnet4.x compatibility fixes applied successfully")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
logger.warning("Some dotnet4.x registry fixes failed, but continuing...")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to apply universal dotnet4.x fixes: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _find_wine_binary_for_registry(self, modlist_compatdata_path: str) -> Optional[str]:
|
||||||
|
"""Find the appropriate Wine binary for registry operations"""
|
||||||
|
try:
|
||||||
|
# Method 1: Try to detect from Steam's config or use Proton from compat data
|
||||||
|
# Look for wine binary in common Proton locations
|
||||||
|
proton_paths = [
|
||||||
|
os.path.expanduser("~/.local/share/Steam/compatibilitytools.d"),
|
||||||
|
os.path.expanduser("~/.steam/steam/steamapps/common")
|
||||||
|
]
|
||||||
|
|
||||||
|
for base_path in proton_paths:
|
||||||
|
if os.path.exists(base_path):
|
||||||
|
for item in os.listdir(base_path):
|
||||||
|
if 'proton' in item.lower():
|
||||||
|
wine_path = os.path.join(base_path, item, 'files', 'bin', 'wine')
|
||||||
|
if os.path.exists(wine_path):
|
||||||
|
logger.debug(f"Found Wine binary: {wine_path}")
|
||||||
|
return wine_path
|
||||||
|
|
||||||
|
# Method 2: Fallback to system wine if available
|
||||||
|
try:
|
||||||
|
result = subprocess.run(['which', 'wine'], capture_output=True, text=True)
|
||||||
|
if result.returncode == 0:
|
||||||
|
wine_path = result.stdout.strip()
|
||||||
|
logger.debug(f"Using system Wine binary: {wine_path}")
|
||||||
|
return wine_path
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
logger.error("No suitable Wine binary found for registry operations")
|
||||||
|
return None
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error finding Wine binary: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
def _inject_game_registry_entries(self, modlist_compatdata_path: str):
|
def _inject_game_registry_entries(self, modlist_compatdata_path: str):
|
||||||
"""Detect and inject FNV/Enderal game paths into modlist's system.reg"""
|
"""Detect and inject FNV/Enderal game paths and apply universal dotnet4.x compatibility fixes"""
|
||||||
system_reg_path = os.path.join(modlist_compatdata_path, "pfx", "system.reg")
|
system_reg_path = os.path.join(modlist_compatdata_path, "pfx", "system.reg")
|
||||||
if not os.path.exists(system_reg_path):
|
if not os.path.exists(system_reg_path):
|
||||||
logger.warning("system.reg not found, skipping game path injection")
|
logger.warning("system.reg not found, skipping game path injection")
|
||||||
return
|
return
|
||||||
|
|
||||||
logger.info("Detecting and injecting game registry entries...")
|
logger.info("Detecting game registry entries...")
|
||||||
|
|
||||||
|
# NOTE: Universal dotnet4.x registry fixes now applied in modlist_handler.py after .reg downloads
|
||||||
|
|
||||||
# Game configurations
|
# Game configurations
|
||||||
games_config = {
|
games_config = {
|
||||||
|
|||||||
@@ -629,7 +629,8 @@ class ModlistService:
|
|||||||
'mo2_exe_path': str(context.install_dir / 'ModOrganizer.exe'),
|
'mo2_exe_path': str(context.install_dir / 'ModOrganizer.exe'),
|
||||||
'resolution': getattr(context, 'resolution', None),
|
'resolution': getattr(context, 'resolution', None),
|
||||||
'skip_confirmation': True, # Service layer should be non-interactive
|
'skip_confirmation': True, # Service layer should be non-interactive
|
||||||
'manual_steps_completed': False
|
'manual_steps_completed': False,
|
||||||
|
'appid': getattr(context, 'app_id', None) # Fix: Include appid like other configuration paths
|
||||||
}
|
}
|
||||||
|
|
||||||
# DEBUG: Log what resolution we're passing
|
# DEBUG: Log what resolution we're passing
|
||||||
|
|||||||
@@ -382,8 +382,27 @@ class NativeSteamService:
|
|||||||
# Find the CompatToolMapping section
|
# Find the CompatToolMapping section
|
||||||
compat_start = config_text.find('"CompatToolMapping"')
|
compat_start = config_text.find('"CompatToolMapping"')
|
||||||
if compat_start == -1:
|
if compat_start == -1:
|
||||||
logger.error("CompatToolMapping section not found in config.vdf")
|
logger.warning("CompatToolMapping section not found in config.vdf, creating it")
|
||||||
return False
|
# Find the Steam section to add CompatToolMapping to
|
||||||
|
steam_section = config_text.find('"Steam"')
|
||||||
|
if steam_section == -1:
|
||||||
|
logger.error("Steam section not found in config.vdf")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Find the opening brace for Steam section
|
||||||
|
steam_brace = config_text.find('{', steam_section)
|
||||||
|
if steam_brace == -1:
|
||||||
|
logger.error("Steam section opening brace not found")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Insert CompatToolMapping section right after Steam opening brace
|
||||||
|
insert_pos = steam_brace + 1
|
||||||
|
compat_section = '\n\t\t"CompatToolMapping"\n\t\t{\n\t\t}\n'
|
||||||
|
config_text = config_text[:insert_pos] + compat_section + config_text[insert_pos:]
|
||||||
|
|
||||||
|
# Update compat_start position after insertion
|
||||||
|
compat_start = config_text.find('"CompatToolMapping"')
|
||||||
|
logger.info("Created CompatToolMapping section in config.vdf")
|
||||||
|
|
||||||
# Find the closing brace for CompatToolMapping
|
# Find the closing brace for CompatToolMapping
|
||||||
# Look for the opening brace after CompatToolMapping
|
# Look for the opening brace after CompatToolMapping
|
||||||
|
|||||||
@@ -447,7 +447,7 @@ class SettingsDialog(QDialog):
|
|||||||
self.use_winetricks_checkbox = QCheckBox("Use winetricks for component installation (faster)")
|
self.use_winetricks_checkbox = QCheckBox("Use winetricks for component installation (faster)")
|
||||||
self.use_winetricks_checkbox.setChecked(self.config_handler.get('use_winetricks_for_components', True))
|
self.use_winetricks_checkbox.setChecked(self.config_handler.get('use_winetricks_for_components', True))
|
||||||
self.use_winetricks_checkbox.setToolTip(
|
self.use_winetricks_checkbox.setToolTip(
|
||||||
"When enabled: Uses winetricks for most components (faster) and protontricks only for dotnet40 (more reliable).\n"
|
"When enabled: Uses winetricks for most components (faster) and protontricks for legacy .NET versions (dotnet40, dotnet472, dotnet48) which are more reliable.\n"
|
||||||
"When disabled: Uses protontricks for all components (legacy behavior, slower but more compatible)."
|
"When disabled: Uses protontricks for all components (legacy behavior, slower but more compatible)."
|
||||||
)
|
)
|
||||||
component_layout.addWidget(self.use_winetricks_checkbox)
|
component_layout.addWidget(self.use_winetricks_checkbox)
|
||||||
|
|||||||
Reference in New Issue
Block a user