Sync from development - prepare for v0.1.6.2

This commit is contained in:
Omni
2025-10-23 21:50:28 +01:00
parent d9ea1be347
commit f039cf9c24
5 changed files with 265 additions and 26 deletions

View File

@@ -1,5 +1,15 @@
# Jackify Changelog # Jackify Changelog
## 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

View File

@@ -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.2"

View File

@@ -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,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)")
@@ -871,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:
@@ -1461,4 +1487,103 @@ 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"""
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

View File

@@ -263,13 +263,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")

View File

@@ -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 = {