mirror of
https://github.com/Omni-guides/Jackify.git
synced 2026-01-17 19:47:00 +01:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
956ea24465 | ||
|
|
f039cf9c24 |
20
CHANGELOG.md
20
CHANGELOG.md
@@ -1,5 +1,25 @@
|
|||||||
# Jackify Changelog
|
# Jackify Changelog
|
||||||
|
|
||||||
|
## v0.1.6.3 - Emergency Hotfix
|
||||||
|
**Release Date:** October 23, 2025
|
||||||
|
|
||||||
|
### Critical Bug Fixes
|
||||||
|
- **FIXED: Proton Detection for Custom Steam Libraries**: Now properly reads all Steam libraries from libraryfolders.vdf
|
||||||
|
- **IMPROVED: Registry Wine Binary Detection**: Uses user's configured Proton for better compatibility
|
||||||
|
- **IMPROVED: Error Handling**: Registry fixes now provide clear warnings if they fail instead of breaking entire workflow
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 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
|
## v0.1.6.1 - Fix dotnet40 install and expand Game Proton override
|
||||||
**Release Date:** October 21, 2025
|
**Release Date:** October 21, 2025
|
||||||
|
|
||||||
|
|||||||
@@ -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.1"
|
__version__ = "0.1.6.3"
|
||||||
|
|||||||
@@ -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": ["dotnet40"],
|
# "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,25 @@ 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...")
|
||||||
|
registry_success = False
|
||||||
|
try:
|
||||||
|
registry_success = self._apply_universal_dotnet_fixes()
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"CRITICAL: Registry fixes failed - modlist may have .NET compatibility issues: {e}")
|
||||||
|
registry_success = False
|
||||||
|
|
||||||
|
if not registry_success:
|
||||||
|
self.logger.error("=" * 80)
|
||||||
|
self.logger.error("WARNING: Universal dotnet4.x registry fixes FAILED!")
|
||||||
|
self.logger.error("This modlist may experience .NET Framework compatibility issues.")
|
||||||
|
self.logger.error("Consider manually setting mscoree=native in winecfg if problems occur.")
|
||||||
|
self.logger.error("=" * 80)
|
||||||
|
# Continue but user should be aware of potential issues
|
||||||
|
|
||||||
# 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)")
|
||||||
@@ -871,21 +893,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:
|
||||||
@@ -1461,4 +1500,109 @@ class ModlistHandler:
|
|||||||
self.logger.error(f"Error handling symlinked downloads: {e}", exc_info=True)
|
self.logger.error(f"Error handling symlinked downloads: {e}", exc_info=True)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# (Ensure EOF is clean and no extra incorrect methods exist below)
|
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 using user's configured Proton"""
|
||||||
|
try:
|
||||||
|
# Use the user's configured Proton version from settings
|
||||||
|
from ..handlers.config_handler import ConfigHandler
|
||||||
|
config_handler = ConfigHandler()
|
||||||
|
user_proton_path = config_handler.get_game_proton_path()
|
||||||
|
|
||||||
|
if user_proton_path and user_proton_path != 'auto':
|
||||||
|
# User has selected a specific Proton version
|
||||||
|
proton_path = Path(user_proton_path).expanduser()
|
||||||
|
|
||||||
|
# Check for wine binary in both GE-Proton and Valve Proton structures
|
||||||
|
wine_candidates = [
|
||||||
|
proton_path / "files" / "bin" / "wine", # GE-Proton structure
|
||||||
|
proton_path / "dist" / "bin" / "wine" # Valve Proton structure
|
||||||
|
]
|
||||||
|
|
||||||
|
for wine_path in wine_candidates:
|
||||||
|
if wine_path.exists():
|
||||||
|
self.logger.info(f"Using Wine binary from user's configured Proton: {wine_path}")
|
||||||
|
return str(wine_path)
|
||||||
|
|
||||||
|
self.logger.warning(f"User's configured Proton path has no wine binary: {user_proton_path}")
|
||||||
|
|
||||||
|
# Fallback: Try to use same Steam library detection as main Proton detection
|
||||||
|
from ..handlers.wine_utils import WineUtils
|
||||||
|
best_proton = WineUtils.select_best_proton()
|
||||||
|
if best_proton:
|
||||||
|
wine_binary = WineUtils.find_proton_binary(best_proton['name'])
|
||||||
|
if wine_binary:
|
||||||
|
self.logger.info(f"Using Wine binary from detected Proton: {wine_binary}")
|
||||||
|
return wine_binary
|
||||||
|
|
||||||
|
# NEVER fall back to system wine - it will break Proton prefixes with architecture mismatches
|
||||||
|
self.logger.error("No suitable Proton Wine binary found for registry operations")
|
||||||
|
return None
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Error finding Wine binary: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
@@ -777,27 +777,40 @@ class WineUtils:
|
|||||||
Returns:
|
Returns:
|
||||||
List of Path objects for Steam library directories
|
List of Path objects for Steam library directories
|
||||||
"""
|
"""
|
||||||
|
steam_common_paths = []
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from .path_handler import PathHandler
|
from .path_handler import PathHandler
|
||||||
# Use existing PathHandler that reads libraryfolders.vdf
|
# Use existing PathHandler that reads libraryfolders.vdf
|
||||||
library_paths = PathHandler.get_all_steam_library_paths()
|
library_paths = PathHandler.get_all_steam_library_paths()
|
||||||
|
logger.info(f"PathHandler found Steam libraries: {library_paths}")
|
||||||
|
|
||||||
# Convert to steamapps/common paths for Proton scanning
|
# Convert to steamapps/common paths for Proton scanning
|
||||||
steam_common_paths = []
|
|
||||||
for lib_path in library_paths:
|
for lib_path in library_paths:
|
||||||
common_path = lib_path / "steamapps" / "common"
|
common_path = lib_path / "steamapps" / "common"
|
||||||
if common_path.exists():
|
if common_path.exists():
|
||||||
steam_common_paths.append(common_path)
|
steam_common_paths.append(common_path)
|
||||||
logger.debug(f"Found Steam library paths: {steam_common_paths}")
|
logger.debug(f"Added Steam library: {common_path}")
|
||||||
return steam_common_paths
|
else:
|
||||||
|
logger.debug(f"Steam library path doesn't exist: {common_path}")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"Failed to get Steam library paths from libraryfolders.vdf: {e}")
|
logger.error(f"PathHandler failed to read libraryfolders.vdf: {e}")
|
||||||
# Fallback to hardcoded paths if PathHandler fails
|
|
||||||
fallback_paths = [
|
# Always add fallback paths in case PathHandler missed something
|
||||||
Path.home() / ".steam/steam/steamapps/common",
|
fallback_paths = [
|
||||||
Path.home() / ".local/share/Steam/steamapps/common",
|
Path.home() / ".steam/steam/steamapps/common",
|
||||||
Path.home() / ".steam/root/steamapps/common"
|
Path.home() / ".local/share/Steam/steamapps/common",
|
||||||
]
|
Path.home() / ".steam/root/steamapps/common"
|
||||||
return [path for path in fallback_paths if path.exists()]
|
]
|
||||||
|
|
||||||
|
for fallback_path in fallback_paths:
|
||||||
|
if fallback_path.exists() and fallback_path not in steam_common_paths:
|
||||||
|
steam_common_paths.append(fallback_path)
|
||||||
|
logger.debug(f"Added fallback Steam library: {fallback_path}")
|
||||||
|
|
||||||
|
logger.info(f"Final Steam library paths for Proton scanning: {steam_common_paths}")
|
||||||
|
return steam_common_paths
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_compatibility_tool_paths() -> List[Path]:
|
def get_compatibility_tool_paths() -> List[Path]:
|
||||||
|
|||||||
@@ -169,9 +169,18 @@ class WinetricksHandler:
|
|||||||
if best_proton:
|
if best_proton:
|
||||||
wine_binary = WineUtils.find_proton_binary(best_proton['name'])
|
wine_binary = WineUtils.find_proton_binary(best_proton['name'])
|
||||||
self.logger.info(f"Auto-selected Proton: {best_proton['name']} at {best_proton['path']}")
|
self.logger.info(f"Auto-selected Proton: {best_proton['name']} at {best_proton['path']}")
|
||||||
|
else:
|
||||||
|
# Enhanced debugging for Proton detection failure
|
||||||
|
self.logger.error("Auto-detection failed - no Proton versions found")
|
||||||
|
available_versions = WineUtils.scan_all_proton_versions()
|
||||||
|
if available_versions:
|
||||||
|
self.logger.error(f"Available Proton versions: {[v['name'] for v in available_versions]}")
|
||||||
|
else:
|
||||||
|
self.logger.error("No Proton versions detected in standard Steam locations")
|
||||||
|
|
||||||
if not wine_binary:
|
if not wine_binary:
|
||||||
self.logger.error("Cannot run winetricks: No compatible Proton version found")
|
self.logger.error("Cannot run winetricks: No compatible Proton version found")
|
||||||
|
self.logger.error("Please ensure you have Proton 9+ or GE-Proton installed through Steam")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if not (os.path.exists(wine_binary) and os.access(wine_binary, os.X_OK)):
|
if not (os.path.exists(wine_binary) and os.access(wine_binary, os.X_OK)):
|
||||||
@@ -263,13 +272,15 @@ class WinetricksHandler:
|
|||||||
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
|
# Legacy .NET Framework versions that are problematic in Wine/Proton
|
||||||
legacy_dotnet_versions = ['dotnet40', 'dotnet472', 'dotnet48']
|
# 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
|
# Check if any legacy .NET Framework versions are present
|
||||||
has_legacy_dotnet = any(comp in components_to_install for comp in legacy_dotnet_versions)
|
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
|
||||||
# ALWAYS use hybrid approach when legacy .NET Framework versions are present
|
# HYBRID APPROACH MOSTLY DISABLED: dotnet40/dotnet472 replaced with universal registry fixes
|
||||||
if has_legacy_dotnet:
|
if has_legacy_dotnet:
|
||||||
legacy_found = [comp for comp in legacy_dotnet_versions if comp in components_to_install]
|
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")
|
self.logger.info(f"Using hybrid approach: protontricks for legacy .NET versions {legacy_found} (reliable), {'winetricks' if use_winetricks else 'protontricks'} for other components")
|
||||||
|
|||||||
@@ -50,7 +50,8 @@ 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")
|
||||||
@@ -58,7 +59,7 @@ class AutomatedPrefixService:
|
|||||||
return lorerim_proton
|
return lorerim_proton
|
||||||
|
|
||||||
# Check for Lost Legacy-specific Proton override (needs Proton 9 for ENB compatibility)
|
# Check for Lost Legacy-specific Proton override (needs Proton 9 for ENB compatibility)
|
||||||
if modlist_name and modlist_name.lower() == 'lostlegacy':
|
if modlist_normalized == 'lostlegacy':
|
||||||
lostlegacy_proton = self._get_lorerim_preferred_proton() # Use same logic as Lorerim
|
lostlegacy_proton = self._get_lorerim_preferred_proton() # Use same logic as Lorerim
|
||||||
if lostlegacy_proton:
|
if lostlegacy_proton:
|
||||||
logger.info(f"Lost Legacy detected: Using {lostlegacy_proton} instead of user settings (ENB compatibility)")
|
logger.info(f"Lost Legacy detected: Using {lostlegacy_proton} instead of user settings (ENB compatibility)")
|
||||||
@@ -2980,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 = {
|
||||||
|
|||||||
Reference in New Issue
Block a user