import os from subprocess import Popen, PIPE from pathlib import Path import logging import platform import shutil import json class GameLauncher: def __init__(self, settings: dict): self.settings = settings self.logger = logging.getLogger('GameLauncher') self.platform = platform.system().lower() self.account_username = None self.account_id = None # Кэшируем пути к файлам self.game_path = Path(settings.get('game', {}).get('path', '')) self.config_path = self.game_path / 'WTF' / 'Config.wtf' self.realmlist_paths = [ self.game_path / 'Data' / 'ruRU' / 'realmlist.wtf', self.game_path / 'Data' / 'realmlist.wtf' ] def validate_game_path(self, path: str) -> bool: """Проверяет корректность пути к игре""" if not path: return False game_path = Path(path) required_files = [ game_path / 'Wow.exe', game_path / 'Data/common.MPQ', game_path / 'Data/common-2.MPQ' ] try: # Проверяем каждый файл for required_file in required_files: if not required_file.exists(): self.logger.error(f"Missing required file: {required_file}") return False return True except Exception as e: self.logger.error(f"Error validating game path: {e}") return False def update_realmlist(self, path: str, realmlist: str) -> bool: """Обновляет файл realmlist.wtf""" try: # Проверяем оба возможных пути data_paths = [ Path(path) / 'Data' / 'ruRU' / 'realmlist.wtf', # Путь для русской локализации Path(path) / 'Data' / 'realmlist.wtf' # Стандартный путь ] # Обновляем существующие файлы или создаем новые updated = False for data_path in data_paths: try: data_path.parent.mkdir(parents=True, exist_ok=True) with open(data_path, 'w', encoding='utf-8') as f: f.write(f'set realmlist {realmlist}\n') updated = True except Exception as e: self.logger.warning(f"Could not update {data_path}: {e}") return updated except Exception as e: self.logger.error(f"Error updating realmlist: {e}") return False def update_config_wtf(self, path: str) -> bool: """Обновляет файл Config.wtf для автологина""" try: # Получаем realmlist из настроек realmlist = self.settings.get('game', {}).get('realmlist', 'logon.server.com') config_path = Path(path) / 'WTF' / 'Config.wtf' # Создаем директорию WTF если её нет config_path.parent.mkdir(parents=True, exist_ok=True) # Читаем существующие настройки config = {} if config_path.exists(): with open(config_path, 'r', encoding='utf-8') as f: for line in f: if line.startswith('SET '): key, value = line[4:].strip().split(' ', 1) config[key] = value.strip('"') # Обновляем настройки if self.account_username: # Настройки для автологина config['accountName'] = self.account_username.upper() # Имя должно быть в верхнем регистре config['accountList'] = self.account_username.upper() # Список аккаунтов config['lastAccountName'] = self.account_username.upper() # Последний использованный аккаунт # Дополнительные настройки для списка аккаунтов config['portal'] = self.account_username.upper() # Текущий аккаунт в портале config['lastCharacterIndex'] = "0" # Индекс последнего персонажа config['realmName'] = "WoW Server" # Имя реалма config['savedAccountList'] = f"{self.account_username.upper()}|{realmlist}" # Сохраненный список config['lastSelectedAccount'] = self.account_username.upper() # Последний выбранный аккаунт # Добавляем другие важные настройки если их нет defaults = { 'locale': 'ruRU', 'readTOS': '1', 'readEULA': '1', 'readTerminationWithoutNotice': '1', 'accounttype': 'LK', 'lastSelectedRealm': '1', # Индекс последнего выбранного реалма 'realmList': realmlist, # Адрес сервера 'patchlist': f"'{realmlist}'", # Адрес сервера для патчей 'accountListType': "1", # Тип списка аккаунтов 'autoSelect': "1", # Автовыбор аккаунта 'autoConnect': "1" # Автоподключение } for key, value in defaults.items(): if key not in config: config[key] = value # Записываем обновленный конфиг with open(config_path, 'w', encoding='utf-8') as f: for key, value in config.items(): # Если значение похоже на число, записываем без кавычек if value.replace('.', '').isdigit(): f.write(f'SET {key} {value}\n') else: f.write(f'SET {key} "{value}"\n') return True except Exception as e: self.logger.error(f"Error updating Config.wtf: {e}") return False def set_account_info(self, username: str, account_id: int): """Устанавливает данные аккаунта для автологина""" self.account_username = username self.account_id = account_id def launch_game(self) -> bool: """Запускает игру с заданными параметрами""" try: game_path = self.settings.get('game', {}).get('path', '') if not game_path or not self.validate_game_path(game_path): self.logger.error("Invalid game path") return False # Обновляем realmlist realmlist = self.settings.get('game', {}).get('realmlist', 'logon.server.com') if not self.update_realmlist(game_path, realmlist): return False # Обновляем Config.wtf для автологина if self.account_username and not self.update_config_wtf(game_path): return False # Формируем путь к исполняемому файлу exe_path = str(Path(game_path) / 'Wow.exe') # Формируем параметры запуска launch_options = self.settings.get('game', {}).get('launch_options', '').split() # Добавляем параметры графики graphics = self.settings.get('graphics', {}) if graphics.get('windowed', False): launch_options.append('-windowed') resolution = graphics.get('resolution', '1920x1080') if resolution: width, height = resolution.split('x') launch_options.extend(['-width', width, '-height', height]) # Запускаем процесс if self.platform == 'linux': try: runner = self.settings.get('game', {}).get('runner', 'wine') if runner == 'portproton': cmd = ['portproton', exe_path] + launch_options elif runner == 'wine': cmd = ['wine', exe_path] + launch_options elif runner == 'lutris': cmd = ['lutris', 'rungame', exe_path] + launch_options elif runner == 'proton': cmd = ['proton', 'run', exe_path] + launch_options elif runner == 'crossover': cmd = ['crossover', exe_path] + launch_options else: raise RuntimeError(f"Неизвестный эмулятор: {runner}") # Добавляем переменные окружения для Wine env = os.environ.copy() if self.settings.get('game', {}).get('wineprefix'): env['WINEPREFIX'] = self.settings['game']['wineprefix'] env['WINEARCH'] = 'win32' # Запускаем процесс Popen(cmd, env=env) except Exception as e: self.logger.error(f"Error launching with Wine: {e}") return False elif self.platform == 'darwin': Popen(['open', exe_path, '--args'] + launch_options) else: Popen([exe_path] + launch_options) return True except Exception as e: self.logger.error(f"Error launching game: {e}") return False