mirror of
https://github.com/Omni-guides/Jackify.git
synced 2026-06-07 21:37:45 +02:00
185 lines
8.7 KiB
Python
185 lines
8.7 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
"""
|
|
Game path and compatdata mixin for PathHandler.
|
|
Extracted from path_handler for file-size and domain separation.
|
|
"""
|
|
|
|
import os
|
|
import re
|
|
import logging
|
|
from pathlib import Path
|
|
from typing import Optional, List, Dict
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class PathHandlerGameMixin:
|
|
"""Mixin providing game install path and compatdata discovery."""
|
|
|
|
@classmethod
|
|
def find_compat_data(cls, appid: str) -> Optional[Path]:
|
|
"""Find the compatdata directory for a given AppID."""
|
|
if not appid:
|
|
logger.error(f"Invalid AppID provided for compatdata search: {appid}")
|
|
return None
|
|
appid_clean = appid.lstrip('-')
|
|
if not appid_clean.isdigit():
|
|
logger.error(f"Invalid AppID provided for compatdata search: {appid}")
|
|
return None
|
|
logger.debug(f"Searching for compatdata directory for AppID: {appid}")
|
|
library_paths = cls.get_all_steam_library_paths()
|
|
if library_paths:
|
|
logger.debug(f"Checking compatdata in {len(library_paths)} Steam libraries")
|
|
for library_path in library_paths:
|
|
compatdata_base = library_path / "steamapps" / "compatdata"
|
|
if not compatdata_base.is_dir():
|
|
logger.debug(f"Compatdata directory does not exist: {compatdata_base}")
|
|
continue
|
|
potential_path = compatdata_base / appid
|
|
if potential_path.is_dir():
|
|
logger.info(f"Found compatdata directory: {potential_path}")
|
|
return potential_path
|
|
logger.debug(f"Compatdata for AppID {appid} not found in {compatdata_base}")
|
|
is_flatpak_steam = any('.var/app/com.valvesoftware.Steam' in str(lib) for lib in library_paths) if library_paths else False
|
|
if not library_paths or is_flatpak_steam:
|
|
logger.debug("Checking fallback compatdata locations...")
|
|
if is_flatpak_steam:
|
|
fallback_locations = [
|
|
Path.home() / ".var/app/com.valvesoftware.Steam/.local/share/Steam/steamapps/compatdata",
|
|
Path.home() / ".var/app/com.valvesoftware.Steam/data/Steam/steamapps/compatdata",
|
|
]
|
|
else:
|
|
fallback_locations = [
|
|
Path.home() / ".local/share/Steam/steamapps/compatdata",
|
|
Path.home() / ".steam/steam/steamapps/compatdata",
|
|
]
|
|
for compatdata_base in fallback_locations:
|
|
if compatdata_base.is_dir():
|
|
potential_path = compatdata_base / appid
|
|
if potential_path.is_dir():
|
|
logger.warning(f"Found compatdata directory in fallback location: {potential_path}")
|
|
return potential_path
|
|
logger.warning(f"Compatdata directory for AppID {appid} not found in any Steam library or fallback location.")
|
|
return None
|
|
|
|
@staticmethod
|
|
def detect_stock_game_path(game_type: str, steam_library: Path) -> Optional[Path]:
|
|
"""Detect the stock game path for a given game type and Steam library."""
|
|
try:
|
|
game_app_ids = {
|
|
'skyrim': '489830', 'fallout4': '377160', 'fnv': '22380', 'oblivion': '22330'
|
|
}
|
|
if game_type not in game_app_ids:
|
|
return None
|
|
app_id = game_app_ids[game_type]
|
|
game_path = steam_library / 'steamapps' / 'common'
|
|
possible_names = {
|
|
'skyrim': ['Skyrim Special Edition', 'Skyrim'],
|
|
'fallout4': ['Fallout 4'],
|
|
'fnv': ['Fallout New Vegas', 'FalloutNV'],
|
|
'oblivion': ['Oblivion']
|
|
}
|
|
if game_type not in possible_names:
|
|
return None
|
|
for name in possible_names[game_type]:
|
|
potential_path = game_path / name
|
|
if potential_path.exists():
|
|
return potential_path
|
|
return None
|
|
except Exception as e:
|
|
logging.error(f"Error detecting stock game path: {e}")
|
|
return None
|
|
|
|
@classmethod
|
|
def find_game_install_paths(cls, target_appids: Dict[str, str]) -> Dict[str, Path]:
|
|
"""Find installation paths for multiple specified games using Steam app IDs."""
|
|
library_paths = cls.get_all_steam_library_paths()
|
|
if not library_paths:
|
|
logger.warning("Failed to find any Steam library paths")
|
|
return {}
|
|
results = {}
|
|
for library_path in library_paths:
|
|
common_dir = library_path / "steamapps" / "common"
|
|
if not common_dir.is_dir():
|
|
logger.debug(f"No 'steamapps/common' directory in library: {library_path}")
|
|
continue
|
|
try:
|
|
game_dirs = [d for d in common_dir.iterdir() if d.is_dir()]
|
|
except (PermissionError, OSError) as e:
|
|
logger.warning(f"Cannot access directory {common_dir}: {e}")
|
|
continue
|
|
for game_name, app_id in target_appids.items():
|
|
if game_name in results:
|
|
continue
|
|
appmanifest_path = library_path / "steamapps" / f"appmanifest_{app_id}.acf"
|
|
if appmanifest_path.is_file():
|
|
try:
|
|
with open(appmanifest_path, 'r', encoding='utf-8') as f:
|
|
content = f.read()
|
|
match = re.search(r'"installdir"\s+"([^"]+)"', content)
|
|
if match:
|
|
install_dir_name = match.group(1)
|
|
install_path = common_dir / install_dir_name
|
|
if install_path.is_dir():
|
|
results[game_name] = install_path
|
|
logger.info(f"Found {game_name} at {install_path}")
|
|
continue
|
|
except Exception as e:
|
|
logger.warning(f"Error reading appmanifest for {game_name}: {e}")
|
|
return results
|
|
|
|
@classmethod
|
|
def find_vanilla_game_paths(cls, game_names=None) -> Dict[str, Path]:
|
|
"""For each known game, iterate all Steam libraries and look for the canonical game directory in steamapps/common."""
|
|
GAME_DIR_NAMES = {
|
|
"Skyrim Special Edition": ["Skyrim Special Edition"],
|
|
"Fallout 4": ["Fallout 4"],
|
|
"Fallout New Vegas": ["Fallout New Vegas"],
|
|
"Oblivion": ["Oblivion"],
|
|
"Fallout 3": ["Fallout 3", "Fallout 3 goty"]
|
|
}
|
|
if game_names is None:
|
|
game_names = list(GAME_DIR_NAMES.keys())
|
|
all_steam_libraries = cls.get_all_steam_library_paths()
|
|
logger.info(f"[DEBUG] Detected Steam libraries: {all_steam_libraries}")
|
|
found_games = {}
|
|
for game in game_names:
|
|
possible_names = GAME_DIR_NAMES.get(game, [game])
|
|
for lib in all_steam_libraries:
|
|
for name in possible_names:
|
|
candidate = lib / "steamapps" / "common" / name
|
|
logger.info(f"[DEBUG] Checking for vanilla game directory: {candidate}")
|
|
if candidate.is_dir():
|
|
found_games[game] = candidate
|
|
logger.info(f"Found vanilla game directory for {game}: {candidate}")
|
|
break
|
|
if game in found_games:
|
|
break
|
|
return found_games
|
|
|
|
def _detect_stock_game_path(self) -> bool:
|
|
"""Detects common Stock Game or Game Root directories within the modlist path. Expects self.logger, self.modlist_dir, self.stock_game_path."""
|
|
self.logger.info("Step 7a: Detecting Stock Game/Game Root directory...")
|
|
if not self.modlist_dir:
|
|
self.logger.error("Modlist directory not set, cannot detect stock game path.")
|
|
return False
|
|
modlist_path = Path(self.modlist_dir)
|
|
preferred_order = [
|
|
"Stock Game", "StockGame", "STOCK GAME", "Skyrim Stock", "Stock Game Folder",
|
|
"Stock Folder", Path("root/Skyrim Special Edition"), "Game Root"
|
|
]
|
|
found_path = None
|
|
for name in preferred_order:
|
|
potential_path = modlist_path / name
|
|
if potential_path.is_dir():
|
|
found_path = str(potential_path)
|
|
self.logger.info(f"Found potential stock game directory: {found_path}")
|
|
break
|
|
if found_path:
|
|
self.stock_game_path = found_path
|
|
return True
|
|
self.stock_game_path = None
|
|
self.logger.info("No common Stock Game/Game Root directory found. Will assume vanilla game path is needed for some operations.")
|
|
return True
|