mirror of
https://github.com/Omni-guides/Jackify.git
synced 2026-06-17 12:37:45 +02:00
Sync from development - prepare for v0.2.2.2
This commit is contained in:
@@ -892,6 +892,7 @@ class PathHandler:
|
||||
|
||||
# Extract existing gamePath to use as source of truth for vanilla game location
|
||||
existing_game_path = None
|
||||
gamepath_drive_letter = None
|
||||
gamepath_line_index = -1
|
||||
for i, line in enumerate(lines):
|
||||
if re.match(r'^\s*gamepath\s*=.*@ByteArray\(([^)]+)\)', line, re.IGNORECASE):
|
||||
@@ -899,11 +900,16 @@ class PathHandler:
|
||||
if match:
|
||||
raw_path = match.group(1)
|
||||
gamepath_line_index = i
|
||||
# Extract drive letter from gamePath (Z: or D:)
|
||||
if raw_path.startswith('Z:'):
|
||||
gamepath_drive_letter = 'Z:'
|
||||
elif raw_path.startswith('D:'):
|
||||
gamepath_drive_letter = 'D:'
|
||||
# Convert Windows path back to Linux path
|
||||
if raw_path.startswith(('Z:', 'D:')):
|
||||
linux_path = raw_path[2:].replace('\\\\', '/').replace('\\', '/')
|
||||
existing_game_path = linux_path
|
||||
logger.debug(f"Extracted existing gamePath: {existing_game_path}")
|
||||
logger.debug(f"Extracted existing gamePath: {existing_game_path}, drive letter: {gamepath_drive_letter}")
|
||||
break
|
||||
|
||||
# Special handling for gamePath in three-true scenario (engine_installed + steamdeck + sdcard)
|
||||
@@ -957,19 +963,33 @@ class PathHandler:
|
||||
logger.error(f"Malformed binary line: {line}")
|
||||
continue
|
||||
key_part, value_part = parts
|
||||
exe_name = os.path.basename(value_part).lower()
|
||||
|
||||
# Clean up malformed paths (quotes in wrong places, etc.)
|
||||
cleaned_value = PathHandler._clean_malformed_binary_path(value_part)
|
||||
exe_name = os.path.basename(cleaned_value).lower()
|
||||
|
||||
# SELECTIVE FILTERING: Only process target executables (script extenders, etc.)
|
||||
if exe_name not in TARGET_EXECUTABLES_LOWER:
|
||||
logger.debug(f"Skipping non-target executable: {exe_name}")
|
||||
continue
|
||||
|
||||
drive_prefix = "D:" if modlist_sdcard else "Z:"
|
||||
rel_path = None
|
||||
# --- BEGIN: FULL PARITY LOGIC ---
|
||||
if 'steamapps' in value_part:
|
||||
idx = value_part.index('steamapps')
|
||||
subpath = value_part[idx:].lstrip('/')
|
||||
if 'steamapps' in cleaned_value:
|
||||
# Vanilla game path detected - always rebuild to ensure correct format
|
||||
if not gamepath_drive_letter:
|
||||
logger.warning(f"Vanilla game path detected but gamePath drive letter not found. Skipping binary path update for: {exe_name}")
|
||||
logger.warning("This may indicate jackify-engine already configured paths correctly, or gamePath is malformed.")
|
||||
continue
|
||||
|
||||
# Check if path is malformed (has quotes or wrong structure)
|
||||
is_malformed = '"' in cleaned_value or cleaned_value != value_part.strip().strip('"')
|
||||
|
||||
# Extract subpath from cleaned value (includes exe name)
|
||||
idx = cleaned_value.index('steamapps')
|
||||
subpath = cleaned_value[idx:].lstrip('/')
|
||||
|
||||
# Find correct Steam library
|
||||
correct_steam_lib = None
|
||||
for lib in steam_libraries:
|
||||
# Check if the actual game folder exists in this library
|
||||
@@ -979,39 +999,62 @@ class PathHandler:
|
||||
if not correct_steam_lib and steam_libraries:
|
||||
correct_steam_lib = steam_libraries[0]
|
||||
if correct_steam_lib:
|
||||
# Always rebuild path using gamePath drive letter to ensure correct format
|
||||
drive_prefix = gamepath_drive_letter
|
||||
if is_malformed:
|
||||
logger.info(f"Fixing malformed binary path for {exe_name}: {value_part.strip()}")
|
||||
logger.debug(f"Vanilla game path detected: Using drive letter from gamePath: {drive_prefix}")
|
||||
new_binary_path = f"{drive_prefix}/{correct_steam_lib}/{subpath}".replace('\\', '/').replace('//', '/')
|
||||
else:
|
||||
logger.error("Could not determine correct Steam library for vanilla game path.")
|
||||
continue
|
||||
else:
|
||||
# For modlist-relative paths (Stock Game, mods, etc.), use modlist location
|
||||
drive_prefix = "D:" if modlist_sdcard else "Z:"
|
||||
found_stock = None
|
||||
for folder in STOCK_GAME_FOLDERS:
|
||||
folder_pattern = f"/{folder}"
|
||||
if folder_pattern in value_part:
|
||||
idx = value_part.index(folder_pattern)
|
||||
rel_path = value_part[idx:].lstrip('/')
|
||||
if folder_pattern in cleaned_value:
|
||||
idx = cleaned_value.index(folder_pattern)
|
||||
rel_path = cleaned_value[idx:].lstrip('/')
|
||||
found_stock = folder
|
||||
break
|
||||
if not rel_path:
|
||||
mods_pattern = "/mods/"
|
||||
if mods_pattern in value_part:
|
||||
idx = value_part.index(mods_pattern)
|
||||
rel_path = value_part[idx:].lstrip('/')
|
||||
if mods_pattern in cleaned_value:
|
||||
idx = cleaned_value.index(mods_pattern)
|
||||
rel_path = cleaned_value[idx:].lstrip('/')
|
||||
else:
|
||||
rel_path = exe_name
|
||||
processed_modlist_path = PathHandler._strip_sdcard_path_prefix(modlist_dir_path) if modlist_sdcard else str(modlist_dir_path)
|
||||
new_binary_path = f"{drive_prefix}/{processed_modlist_path}/{rel_path}".replace('\\', '/').replace('//', '/')
|
||||
formatted_binary_path = PathHandler._format_binary_for_mo2(new_binary_path)
|
||||
# Ensure no quotes in formatted path (binary paths should never have quotes)
|
||||
if '"' in formatted_binary_path:
|
||||
logger.warning(f"Formatted binary path still contains quotes, removing: {formatted_binary_path}")
|
||||
formatted_binary_path = formatted_binary_path.replace('"', '')
|
||||
new_binary_line = f"{index}{backslash_style}binary = {formatted_binary_path}"
|
||||
logger.debug(f"Updating binary path: {line.strip()} -> {new_binary_line}")
|
||||
lines[i] = new_binary_line + "\n"
|
||||
logger.info(f"Updating binary path: {line.strip()} -> {new_binary_line}")
|
||||
# Preserve original line ending - lines from readlines() should have newline, but ensure it
|
||||
original_line = lines[i]
|
||||
if original_line.endswith('\n'):
|
||||
lines[i] = new_binary_line + '\n'
|
||||
else:
|
||||
lines[i] = new_binary_line + '\n'
|
||||
binary_paths_updated += 1
|
||||
binary_paths_by_index[index] = formatted_binary_path
|
||||
for j, wd_line, index, backslash_style in working_dir_lines:
|
||||
if index in binary_paths_by_index:
|
||||
binary_path = binary_paths_by_index[index]
|
||||
wd_path = os.path.dirname(binary_path)
|
||||
drive_prefix = "D:" if modlist_sdcard else "Z:"
|
||||
# Derive drive letter from binary path, not modlist location
|
||||
if binary_path.startswith("D:"):
|
||||
drive_prefix = "D:"
|
||||
elif binary_path.startswith("Z:"):
|
||||
drive_prefix = "Z:"
|
||||
else:
|
||||
# Fallback: use modlist location if binary path doesn't have drive letter
|
||||
drive_prefix = "D:" if modlist_sdcard else "Z:"
|
||||
if wd_path.startswith("D:") or wd_path.startswith("Z:"):
|
||||
wd_path = wd_path[2:]
|
||||
wd_path = drive_prefix + wd_path
|
||||
@@ -1019,7 +1062,12 @@ class PathHandler:
|
||||
key_part = f"{index}{backslash_style}workingDirectory"
|
||||
new_wd_line = f"{key_part} = {formatted_wd_path}"
|
||||
logger.debug(f"Updating working directory: {wd_line.strip()} -> {new_wd_line}")
|
||||
lines[j] = new_wd_line + "\n"
|
||||
# Preserve original line ending - ensure newline is present
|
||||
original_wd_line = lines[j]
|
||||
if original_wd_line.endswith('\n'):
|
||||
lines[j] = new_wd_line + '\n'
|
||||
else:
|
||||
lines[j] = new_wd_line + '\n'
|
||||
working_dirs_updated += 1
|
||||
with open(modlist_ini_path, 'w', encoding='utf-8') as f:
|
||||
f.writelines(lines)
|
||||
@@ -1141,6 +1189,33 @@ class PathHandler:
|
||||
path = re.sub(r'^([A-Z]:)\\+', r'\1\\', path)
|
||||
return path
|
||||
|
||||
@staticmethod
|
||||
def _clean_malformed_binary_path(value_part: str) -> str:
|
||||
"""
|
||||
Clean up malformed binary paths from engine (e.g., quotes in wrong places).
|
||||
Example: "Z:/path/to/game"/exe.exe -> Z:/path/to/game/exe.exe
|
||||
"""
|
||||
cleaned = value_part.strip()
|
||||
# Remove quotes if they wrap only part of the path (malformed)
|
||||
if cleaned.startswith('"') and '"' in cleaned[1:]:
|
||||
# Find the closing quote
|
||||
quote_end = cleaned.find('"', 1)
|
||||
if quote_end > 0:
|
||||
# Check if there's content after the quote (malformed)
|
||||
after_quote = cleaned[quote_end + 1:].strip()
|
||||
if after_quote.startswith('/') or after_quote:
|
||||
# Malformed: quotes wrap only part of path
|
||||
# Remove quotes and join
|
||||
path_part = cleaned[1:quote_end]
|
||||
remaining = after_quote.lstrip('/')
|
||||
cleaned = f"{path_part}/{remaining}" if remaining else path_part
|
||||
logger.info(f"Cleaned malformed binary path: {value_part} -> {cleaned}")
|
||||
# Remove any remaining quotes (handles fully quoted paths too)
|
||||
cleaned = cleaned.strip('"')
|
||||
# Normalize slashes
|
||||
cleaned = cleaned.replace('\\', '/')
|
||||
return cleaned
|
||||
|
||||
@staticmethod
|
||||
def _format_binary_for_mo2(path: str) -> str:
|
||||
import re
|
||||
|
||||
@@ -367,6 +367,13 @@ class ProtontricksHandler:
|
||||
**kwargs # Allow overriding defaults (like stderr=DEVNULL)
|
||||
}
|
||||
|
||||
# Log full command for advanced users to reproduce manually (debug mode only)
|
||||
cmd_str = ' '.join(map(str, cmd))
|
||||
logger.debug("=" * 80)
|
||||
logger.debug("PROTONTRICKS COMMAND (for manual reproduction):")
|
||||
logger.debug(f" {cmd_str}")
|
||||
logger.debug("=" * 80)
|
||||
|
||||
# Handle environment: if env was passed in kwargs, merge it with our clean env
|
||||
# Otherwise create a clean env from scratch
|
||||
if 'env' in kwargs and kwargs['env']:
|
||||
|
||||
@@ -19,6 +19,15 @@ from .subprocess_utils import get_clean_subprocess_env
|
||||
# Initialize logger
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Known Valve Proton App ID -> config.vdf internal name mapping
|
||||
VALVE_PROTON_APPID_MAP = {
|
||||
'2805730': 'proton_9',
|
||||
'3658110': 'proton_10',
|
||||
'1493710': 'proton_experimental',
|
||||
'2180100': 'proton_hotfix',
|
||||
'1887720': 'proton_8',
|
||||
}
|
||||
|
||||
|
||||
class WineUtils:
|
||||
"""
|
||||
@@ -849,6 +858,155 @@ class WineUtils:
|
||||
# Return only existing paths
|
||||
return [path for path in compat_paths if path.exists()]
|
||||
|
||||
@staticmethod
|
||||
def _parse_compat_tool_name(proton_dir: Path) -> Optional[str]:
|
||||
"""Parse the Steam internal name from a compatibilitytool.vdf file.
|
||||
The key under compat_tools is what Steam uses in config.vdf CompatToolMapping."""
|
||||
vdf_path = proton_dir / "compatibilitytool.vdf"
|
||||
if not vdf_path.exists():
|
||||
return None
|
||||
try:
|
||||
with open(vdf_path, 'r', encoding='utf-8', errors='ignore') as f:
|
||||
content = f.read()
|
||||
match = re.search(r'"compat_tools"\s*\{[^{]*"([^"]+)"\s*(?://[^\n]*)?\s*\{', content, re.DOTALL)
|
||||
if match:
|
||||
return match.group(1)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to parse {vdf_path}: {e}")
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _find_valve_proton_appid(proton_dir_name: str) -> Optional[str]:
|
||||
"""Find the Steam App ID for a Valve Proton by matching appmanifest installdir."""
|
||||
steam_libs = WineUtils.get_steam_library_paths()
|
||||
for lib_path in steam_libs:
|
||||
steamapps_dir = lib_path.parent
|
||||
for manifest in steamapps_dir.glob("appmanifest_*.acf"):
|
||||
try:
|
||||
with open(manifest, 'r', encoding='utf-8', errors='ignore') as f:
|
||||
content = f.read()
|
||||
installdir_match = re.search(r'"installdir"\s+"([^"]+)"', content)
|
||||
appid_match = re.search(r'"appid"\s+"(\d+)"', content)
|
||||
if installdir_match and appid_match:
|
||||
if installdir_match.group(1) == proton_dir_name:
|
||||
return appid_match.group(1)
|
||||
except Exception:
|
||||
continue
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def resolve_steam_compat_name(proton_path) -> Optional[str]:
|
||||
"""Resolve the correct Steam config.vdf internal name for a Proton installation.
|
||||
|
||||
For third-party Protons (GE, CachyOS, etc.): parses compatibilitytool.vdf
|
||||
For Valve Protons: maps via App ID from appmanifest files.
|
||||
|
||||
Args:
|
||||
proton_path: Path to the Proton directory (str or Path)
|
||||
|
||||
Returns:
|
||||
Internal name for config.vdf CompatToolMapping, or None if unresolvable
|
||||
"""
|
||||
proton_path = Path(proton_path)
|
||||
|
||||
if not proton_path.is_dir():
|
||||
logger.warning(f"Proton path not found: {proton_path}")
|
||||
return None
|
||||
|
||||
# Third-party Proton: check for compatibilitytool.vdf
|
||||
compat_name = WineUtils._parse_compat_tool_name(proton_path)
|
||||
if compat_name:
|
||||
logger.debug(f"Resolved compat name from vdf: {proton_path.name} -> {compat_name}")
|
||||
return compat_name
|
||||
|
||||
# Valve Proton: look up App ID from appmanifest, then map
|
||||
dir_name = proton_path.name
|
||||
appid = WineUtils._find_valve_proton_appid(dir_name)
|
||||
if appid and appid in VALVE_PROTON_APPID_MAP:
|
||||
name = VALVE_PROTON_APPID_MAP[appid]
|
||||
logger.debug(f"Resolved Valve Proton: {dir_name} (AppID {appid}) -> {name}")
|
||||
return name
|
||||
|
||||
# Fallback for GE-Proton dirs without a vdf (shouldn't happen, but safe)
|
||||
if dir_name.startswith('GE-Proton'):
|
||||
return dir_name
|
||||
|
||||
logger.warning(f"Could not resolve Steam compat name for: {proton_path}")
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def scan_thirdparty_proton_versions() -> List[Dict[str, any]]:
|
||||
"""Scan for non-GE third-party Proton versions in compatibilitytools.d directories.
|
||||
Discovers CachyOS, TKG, and other community builds by parsing compatibilitytool.vdf.
|
||||
|
||||
Returns:
|
||||
List of dicts with version info, sorted by name
|
||||
"""
|
||||
logger.info("Scanning for third-party Proton versions...")
|
||||
|
||||
found_versions = []
|
||||
seen_names = set()
|
||||
compat_paths = WineUtils.get_compatibility_tool_paths()
|
||||
|
||||
if not compat_paths:
|
||||
return []
|
||||
|
||||
for compat_path in compat_paths:
|
||||
try:
|
||||
for proton_dir in compat_path.iterdir():
|
||||
if not proton_dir.is_dir():
|
||||
continue
|
||||
|
||||
dir_name = proton_dir.name
|
||||
|
||||
# Skip GE-Proton (handled by scan_ge_proton_versions)
|
||||
if dir_name.startswith("GE-Proton"):
|
||||
continue
|
||||
|
||||
# Must have a wine binary to be a usable Proton
|
||||
wine_bin = proton_dir / "files" / "bin" / "wine"
|
||||
if not wine_bin.exists():
|
||||
continue
|
||||
|
||||
# Must have a compatibilitytool.vdf (proves it's a Proton compat tool)
|
||||
compat_name = WineUtils._parse_compat_tool_name(proton_dir)
|
||||
if not compat_name:
|
||||
continue
|
||||
|
||||
# Skip non-Proton tools (e.g., LegacyRuntime)
|
||||
vdf_path = proton_dir / "compatibilitytool.vdf"
|
||||
try:
|
||||
with open(vdf_path, 'r', encoding='utf-8', errors='ignore') as f:
|
||||
vdf_content = f.read()
|
||||
if '"from_oslist" "linux"' in vdf_content:
|
||||
continue
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Skip Proton Hotfix
|
||||
if 'hotfix' in compat_name.lower():
|
||||
continue
|
||||
|
||||
if compat_name in seen_names:
|
||||
continue
|
||||
seen_names.add(compat_name)
|
||||
|
||||
found_versions.append({
|
||||
'name': dir_name,
|
||||
'path': proton_dir,
|
||||
'wine_bin': wine_bin,
|
||||
'priority': 175,
|
||||
'type': 'ThirdParty-Proton',
|
||||
'steam_compat_name': compat_name,
|
||||
})
|
||||
logger.debug(f"Found third-party Proton: {dir_name} (compat name: {compat_name})")
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Error scanning {compat_path}: {e}")
|
||||
|
||||
logger.info(f"Found {len(found_versions)} third-party Proton version(s)")
|
||||
return found_versions
|
||||
|
||||
@staticmethod
|
||||
def scan_ge_proton_versions() -> List[Dict[str, any]]:
|
||||
"""
|
||||
@@ -895,6 +1053,7 @@ class WineUtils:
|
||||
# Priority format: 200 (base) + major*10 + minor (e.g., 200 + 100 + 16 = 316)
|
||||
priority = 200 + (major_ver * 10) + minor_ver
|
||||
|
||||
compat_name = WineUtils._parse_compat_tool_name(proton_dir) or dir_name
|
||||
found_versions.append({
|
||||
'name': dir_name,
|
||||
'path': proton_dir,
|
||||
@@ -902,7 +1061,8 @@ class WineUtils:
|
||||
'priority': priority,
|
||||
'major_version': major_ver,
|
||||
'minor_version': minor_ver,
|
||||
'type': 'GE-Proton'
|
||||
'type': 'GE-Proton',
|
||||
'steam_compat_name': compat_name,
|
||||
})
|
||||
logger.debug(f"Found {dir_name} at {proton_dir} (priority: {priority})")
|
||||
else:
|
||||
@@ -951,12 +1111,14 @@ class WineUtils:
|
||||
wine_bin = proton_path / "files" / "bin" / "wine"
|
||||
|
||||
if wine_bin.exists() and wine_bin.is_file():
|
||||
compat_name = WineUtils.resolve_steam_compat_name(proton_path)
|
||||
found_versions.append({
|
||||
'name': version_name,
|
||||
'path': proton_path,
|
||||
'wine_bin': wine_bin,
|
||||
'priority': priority,
|
||||
'type': 'Valve-Proton'
|
||||
'type': 'Valve-Proton',
|
||||
'steam_compat_name': compat_name,
|
||||
})
|
||||
logger.debug(f"Found {version_name} at {proton_path}")
|
||||
|
||||
@@ -998,6 +1160,10 @@ class WineUtils:
|
||||
ge_versions = WineUtils.scan_ge_proton_versions()
|
||||
all_versions.extend(ge_versions)
|
||||
|
||||
# Scan third-party Proton versions (CachyOS, TKG, etc.)
|
||||
thirdparty_versions = WineUtils.scan_thirdparty_proton_versions()
|
||||
all_versions.extend(thirdparty_versions)
|
||||
|
||||
# Scan Valve Proton versions
|
||||
valve_versions = WineUtils.scan_valve_proton_versions()
|
||||
all_versions.extend(valve_versions)
|
||||
@@ -1025,6 +1191,7 @@ class WineUtils:
|
||||
def select_best_proton() -> Optional[Dict[str, any]]:
|
||||
"""
|
||||
Select the best available Proton version (GE-Proton or Valve Proton) using unified precedence.
|
||||
Excludes third-party builds (CachyOS, etc.) which may have compatibility issues.
|
||||
|
||||
Returns:
|
||||
Dict with version info for the best Proton, or None if none found
|
||||
@@ -1035,8 +1202,16 @@ class WineUtils:
|
||||
logger.warning("No compatible Proton versions found")
|
||||
return None
|
||||
|
||||
# Filter out third-party Protons - they may have compatibility issues with component installation
|
||||
# Only include GE-Proton and Valve-Proton types
|
||||
compatible_versions = [v for v in available_versions if v.get('type') in ('GE-Proton', 'Valve-Proton')]
|
||||
|
||||
if not compatible_versions:
|
||||
logger.warning("No compatible Proton versions found (only third-party builds available)")
|
||||
return None
|
||||
|
||||
# Return the highest priority version (first in sorted list)
|
||||
best_version = available_versions[0]
|
||||
best_version = compatible_versions[0]
|
||||
logger.info(f"Selected best Proton version: {best_version['name']} ({best_version['type']})")
|
||||
return best_version
|
||||
|
||||
@@ -1079,11 +1254,11 @@ class WineUtils:
|
||||
if best_proton:
|
||||
# Compatible Proton found
|
||||
proton_type = best_proton.get('type', 'Unknown')
|
||||
status_msg = f"✓ Using {best_proton['name']} ({proton_type}) for this workflow"
|
||||
status_msg = f"[OK] Using {best_proton['name']} ({proton_type}) for this workflow"
|
||||
logger.info(f"Proton requirements satisfied: {best_proton['name']} ({proton_type})")
|
||||
return True, status_msg, best_proton
|
||||
else:
|
||||
# No compatible Proton found
|
||||
status_msg = "✗ No compatible Proton version found (GE-Proton 10+, Proton 9+, 10, or Experimental required)"
|
||||
status_msg = "[FAIL] No compatible Proton version found (GE-Proton 10+, Proton 9+, 10, or Experimental required)"
|
||||
logger.warning("Proton requirements not met - no compatible version found")
|
||||
return False, status_msg, None
|
||||
@@ -484,7 +484,16 @@ class WinetricksHandler:
|
||||
# Build winetricks command - using --unattended for silent installation
|
||||
cmd = [self.winetricks_path, '--unattended'] + components_to_install
|
||||
|
||||
self.logger.debug(f"Running: {' '.join(cmd)}")
|
||||
# Log full command for advanced users to reproduce manually (debug mode only)
|
||||
cmd_str = ' '.join(cmd)
|
||||
self.logger.debug("=" * 80)
|
||||
self.logger.debug("WINETRICKS COMMAND (for manual reproduction):")
|
||||
self.logger.debug(f" {cmd_str}")
|
||||
self.logger.debug("")
|
||||
self.logger.debug("Environment variables required:")
|
||||
self.logger.debug(f" WINEPREFIX={env.get('WINEPREFIX', 'NOT SET')}")
|
||||
self.logger.debug(f" WINE={env.get('WINE', 'NOT SET')}")
|
||||
self.logger.debug("=" * 80)
|
||||
|
||||
# Enhanced diagnostics for bundled winetricks
|
||||
self.logger.debug("=== Winetricks Environment Diagnostics ===")
|
||||
@@ -567,8 +576,31 @@ class WinetricksHandler:
|
||||
self.logger.error("")
|
||||
self.logger.error("STDERR:")
|
||||
if result.stderr.strip():
|
||||
# Filter out verbose winetricks "Executing..." messages - these are informational, not errors
|
||||
error_lines = []
|
||||
verbose_lines = []
|
||||
for line in result.stderr.strip().split('\n'):
|
||||
self.logger.error(f" {line}")
|
||||
line_lower = line.lower().strip()
|
||||
# Skip verbose informational messages
|
||||
if (line_lower.startswith('executing ') or
|
||||
(line_lower.startswith('grep: warning:') and 'stray' in line_lower) or
|
||||
('warning; possible' in line_lower and 'extra bytes' in line_lower)):
|
||||
# These are verbose info messages, log at debug level instead
|
||||
verbose_lines.append(line)
|
||||
else:
|
||||
# Actual error/warning messages (including "returned status", "aborting", dbus errors, etc.)
|
||||
error_lines.append(line)
|
||||
|
||||
if error_lines:
|
||||
self.logger.error(" Actual errors/warnings:")
|
||||
for line in error_lines:
|
||||
self.logger.error(f" {line}")
|
||||
if verbose_lines:
|
||||
self.logger.debug(f" ({len(verbose_lines)} verbose 'Executing...' lines suppressed - see debug log for details)")
|
||||
else:
|
||||
self.logger.error(" (only verbose output, no actual errors)")
|
||||
if verbose_lines:
|
||||
self.logger.debug(f" ({len(verbose_lines)} verbose lines suppressed)")
|
||||
else:
|
||||
self.logger.error(" (empty)")
|
||||
self.logger.error("=" * 80)
|
||||
|
||||
Reference in New Issue
Block a user