mirror of
https://github.com/Omni-guides/Jackify.git
synced 2026-01-17 19:47:00 +01:00
Jackify provides native Linux support for Wabbajack modlist installation and management with automated Steam integration and Proton configuration. Key Features: - Almost Native Linux implementation (texconv.exe run via proton) - Automated Steam shortcut creation and Proton prefix management - Both CLI and GUI interfaces, with Steam Deck optimization Supported Games: - Skyrim Special Edition - Fallout 4 - Fallout New Vegas - Oblivion, Starfield, Enderal, and diverse other games Technical Architecture: - Clean separation between frontend and backend services - Powered by jackify-engine 0.3.x for Wabbajack-matching modlist installation
260 lines
11 KiB
Python
260 lines
11 KiB
Python
"""
|
|
GameDetector module for detecting and managing game-related information.
|
|
This module handles game type detection, version detection, and game-specific requirements.
|
|
"""
|
|
|
|
import os
|
|
import logging
|
|
from pathlib import Path
|
|
from typing import Optional, Dict, List, Tuple
|
|
|
|
class GameDetector:
|
|
def __init__(self):
|
|
self.logger = logging.getLogger(__name__)
|
|
self.supported_games = {
|
|
'skyrim': ['Skyrim Special Edition', 'Skyrim'],
|
|
'fallout4': ['Fallout 4'],
|
|
'falloutnv': ['Fallout New Vegas'],
|
|
'oblivion': ['Oblivion'],
|
|
'starfield': ['Starfield'],
|
|
'oblivion_remastered': ['Oblivion Remastered']
|
|
}
|
|
|
|
def detect_game_type(self, modlist_name: str) -> Optional[str]:
|
|
"""Detect the game type from a modlist name."""
|
|
modlist_lower = modlist_name.lower()
|
|
|
|
# Check for game-specific keywords in modlist name
|
|
# Check for Oblivion Remastered first since "oblivion" is a substring
|
|
if any(keyword in modlist_lower for keyword in ['oblivion remastered', 'oblivionremastered', 'oblivion_remastered']):
|
|
return 'oblivion_remastered'
|
|
elif any(keyword in modlist_lower for keyword in ['skyrim', 'sse', 'skse', 'dragonborn', 'dawnguard']):
|
|
return 'skyrim'
|
|
elif any(keyword in modlist_lower for keyword in ['fallout 4', 'fo4', 'f4se', 'commonwealth']):
|
|
return 'fallout4'
|
|
elif any(keyword in modlist_lower for keyword in ['fallout new vegas', 'fonv', 'fnv', 'new vegas', 'nvse']):
|
|
return 'falloutnv'
|
|
elif any(keyword in modlist_lower for keyword in ['oblivion', 'obse', 'shivering isles']):
|
|
return 'oblivion'
|
|
elif any(keyword in modlist_lower for keyword in ['starfield', 'sf', 'starfieldse']):
|
|
return 'starfield'
|
|
|
|
self.logger.debug(f"Could not detect game type from modlist name: {modlist_name}")
|
|
return None
|
|
|
|
def detect_game_version(self, game_type: str, modlist_path: Path) -> Optional[str]:
|
|
"""Detect the game version from the modlist path."""
|
|
try:
|
|
# Look for ModOrganizer.ini to get game info
|
|
mo_ini = modlist_path / "ModOrganizer.ini"
|
|
if mo_ini.exists():
|
|
with open(mo_ini, 'r', encoding='utf-8') as f:
|
|
content = f.read()
|
|
|
|
# Extract game version info from MO2 config
|
|
if 'gameName=' in content:
|
|
for line in content.splitlines():
|
|
if line.startswith('gameName='):
|
|
game_name = line.split('=', 1)[1].strip()
|
|
return game_name
|
|
|
|
self.logger.debug(f"Could not detect game version for {game_type} at {modlist_path}")
|
|
return None
|
|
|
|
except Exception as e:
|
|
self.logger.error(f"Error detecting game version: {e}")
|
|
return None
|
|
|
|
def detect_game_path(self, game_type: str, modlist_path: Path) -> Optional[Path]:
|
|
"""Detect the game installation path."""
|
|
try:
|
|
# Look for ModOrganizer.ini to get game path
|
|
mo_ini = modlist_path / "ModOrganizer.ini"
|
|
if mo_ini.exists():
|
|
with open(mo_ini, 'r', encoding='utf-8') as f:
|
|
content = f.read()
|
|
|
|
# Extract game path from MO2 config
|
|
for line in content.splitlines():
|
|
if line.startswith('gamePath='):
|
|
game_path = line.split('=', 1)[1].strip()
|
|
return Path(game_path) if game_path else None
|
|
|
|
self.logger.debug(f"Could not detect game path for {game_type} at {modlist_path}")
|
|
return None
|
|
|
|
except Exception as e:
|
|
self.logger.error(f"Error detecting game path: {e}")
|
|
return None
|
|
|
|
def get_game_requirements(self, game_type: str) -> Dict:
|
|
"""Get the requirements for a specific game type."""
|
|
requirements = {
|
|
'skyrim': {
|
|
'launcher': 'SKSE',
|
|
'min_proton_version': '6.0',
|
|
'required_dlc': ['Dawnguard', 'Hearthfire', 'Dragonborn'],
|
|
'compatibility_tools': ['protontricks', 'winetricks']
|
|
},
|
|
'fallout4': {
|
|
'launcher': 'F4SE',
|
|
'min_proton_version': '6.0',
|
|
'required_dlc': [],
|
|
'compatibility_tools': ['protontricks', 'winetricks']
|
|
},
|
|
'falloutnv': {
|
|
'launcher': 'NVSE',
|
|
'min_proton_version': '5.0',
|
|
'required_dlc': [],
|
|
'compatibility_tools': ['protontricks', 'winetricks']
|
|
},
|
|
'oblivion': {
|
|
'launcher': 'OBSE',
|
|
'min_proton_version': '5.0',
|
|
'required_dlc': [],
|
|
'compatibility_tools': ['protontricks', 'winetricks']
|
|
},
|
|
'starfield': {
|
|
'launcher': 'SFSE',
|
|
'min_proton_version': '8.0',
|
|
'required_dlc': [],
|
|
'compatibility_tools': ['protontricks', 'winetricks']
|
|
},
|
|
'oblivion_remastered': {
|
|
'launcher': 'OBSE',
|
|
'min_proton_version': '8.0',
|
|
'required_dlc': [],
|
|
'compatibility_tools': ['protontricks', 'winetricks']
|
|
}
|
|
}
|
|
|
|
return requirements.get(game_type, {})
|
|
|
|
def detect_mods(self, modlist_path: Path) -> List[Dict]:
|
|
"""Detect installed mods in a modlist."""
|
|
mods = []
|
|
try:
|
|
# Look for mods directory in MO2 structure
|
|
mods_dir = modlist_path / "mods"
|
|
if mods_dir.exists() and mods_dir.is_dir():
|
|
for mod_dir in mods_dir.iterdir():
|
|
if mod_dir.is_dir():
|
|
mod_info = {
|
|
'name': mod_dir.name,
|
|
'path': str(mod_dir),
|
|
'enabled': True # Assume enabled by default
|
|
}
|
|
|
|
# Check for meta.ini for more details
|
|
meta_ini = mod_dir / "meta.ini"
|
|
if meta_ini.exists():
|
|
try:
|
|
with open(meta_ini, 'r', encoding='utf-8') as f:
|
|
meta_content = f.read()
|
|
# Parse basic mod info from meta.ini
|
|
for line in meta_content.splitlines():
|
|
if line.startswith('modid='):
|
|
mod_info['nexus_id'] = line.split('=', 1)[1].strip()
|
|
elif line.startswith('version='):
|
|
mod_info['version'] = line.split('=', 1)[1].strip()
|
|
except Exception:
|
|
pass # Continue without meta info
|
|
|
|
mods.append(mod_info)
|
|
|
|
except Exception as e:
|
|
self.logger.error(f"Error detecting mods: {e}")
|
|
|
|
return mods
|
|
|
|
def detect_launcher(self, game_type: str, modlist_path: Path) -> Optional[str]:
|
|
"""Detect the game launcher type (SKSE, F4SE, etc)."""
|
|
launcher_map = {
|
|
'skyrim': 'SKSE',
|
|
'fallout4': 'F4SE',
|
|
'falloutnv': 'NVSE',
|
|
'oblivion': 'OBSE',
|
|
'starfield': 'SFSE',
|
|
'oblivion_remastered': 'OBSE'
|
|
}
|
|
|
|
expected_launcher = launcher_map.get(game_type)
|
|
if not expected_launcher:
|
|
return None
|
|
|
|
# Check if launcher executable exists
|
|
launcher_exe = f"{expected_launcher.lower()}_loader.exe"
|
|
if (modlist_path / launcher_exe).exists():
|
|
return expected_launcher
|
|
|
|
return expected_launcher # Return expected even if not found
|
|
|
|
def get_launcher_path(self, launcher_type: str, modlist_path: Path) -> Optional[Path]:
|
|
"""Get the path to the game launcher."""
|
|
launcher_exe = f"{launcher_type.lower()}_loader.exe"
|
|
launcher_path = modlist_path / launcher_exe
|
|
|
|
if launcher_path.exists():
|
|
return launcher_path
|
|
|
|
return None
|
|
|
|
def detect_compatibility_requirements(self, game_type: str) -> List[str]:
|
|
"""Detect compatibility requirements for a game type."""
|
|
requirements = {
|
|
'skyrim': ['vcrun2019', 'dotnet48', 'dxvk'],
|
|
'fallout4': ['vcrun2019', 'dotnet48', 'dxvk'],
|
|
'falloutnv': ['vcrun2019', 'dotnet48'],
|
|
'oblivion': ['vcrun2019', 'dotnet48'],
|
|
'starfield': ['vcrun2022', 'dotnet6', 'dotnet7', 'dxvk'],
|
|
'oblivion_remastered': ['vcrun2022', 'dotnet6', 'dotnet7', 'dxvk']
|
|
}
|
|
|
|
return requirements.get(game_type, [])
|
|
|
|
def validate_game_installation(self, game_type: str, game_path: Path) -> bool:
|
|
"""Validate a game installation."""
|
|
if not game_path or not game_path.exists():
|
|
return False
|
|
|
|
# Check for game-specific executables
|
|
game_executables = {
|
|
'skyrim': ['SkyrimSE.exe', 'Skyrim.exe'],
|
|
'fallout4': ['Fallout4.exe'],
|
|
'falloutnv': ['FalloutNV.exe'],
|
|
'oblivion': ['Oblivion.exe']
|
|
}
|
|
|
|
executables = game_executables.get(game_type, [])
|
|
for exe in executables:
|
|
if (game_path / exe).exists():
|
|
return True
|
|
|
|
return False
|
|
|
|
def get_game_specific_config(self, game_type: str) -> Dict:
|
|
"""Get game-specific configuration requirements."""
|
|
configs = {
|
|
'skyrim': {
|
|
'ini_files': ['Skyrim.ini', 'SkyrimPrefs.ini', 'SkyrimCustom.ini'],
|
|
'config_dirs': ['Data', 'Saves'],
|
|
'registry_keys': ['HKEY_LOCAL_MACHINE\\SOFTWARE\\Bethesda Softworks\\Skyrim Special Edition']
|
|
},
|
|
'fallout4': {
|
|
'ini_files': ['Fallout4.ini', 'Fallout4Prefs.ini', 'Fallout4Custom.ini'],
|
|
'config_dirs': ['Data', 'Saves'],
|
|
'registry_keys': ['HKEY_LOCAL_MACHINE\\SOFTWARE\\Bethesda Softworks\\Fallout 4']
|
|
},
|
|
'falloutnv': {
|
|
'ini_files': ['Fallout.ini', 'FalloutPrefs.ini'],
|
|
'config_dirs': ['Data', 'Saves'],
|
|
'registry_keys': ['HKEY_LOCAL_MACHINE\\SOFTWARE\\Bethesda Softworks\\FalloutNV']
|
|
},
|
|
'oblivion': {
|
|
'ini_files': ['Oblivion.ini'],
|
|
'config_dirs': ['Data', 'Saves'],
|
|
'registry_keys': ['HKEY_LOCAL_MACHINE\\SOFTWARE\\Bethesda Softworks\\Oblivion']
|
|
}
|
|
}
|
|
|
|
return configs.get(game_type, {}) |