From a9852dbffaf29f8cd99e747a8f8e31db583ed2d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=95=D0=B2=D0=B3=D0=B5=D0=BD=D0=B8=D0=B9=20=28=D0=A5?= =?UTF-8?q?=D1=80=D0=B0=D0=BC=D1=8B=D1=87=D0=AA=29=20=D0=A5=D1=80=D0=B0?= =?UTF-8?q?=D0=BC=D0=BE=D0=B2?= Date: Fri, 7 Mar 2025 00:52:13 +0300 Subject: [PATCH] =?UTF-8?q?*=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B0=20=D0=BD=D0=B0=D1=87=D0=B0=D0=BB=D1=8C=D0=BD=D0=B0?= =?UTF-8?q?=D1=8F=20=D0=B2=D0=BE=D0=B7=D0=BC=D0=BE=D0=B6=D0=BD=D0=BE=D1=81?= =?UTF-8?q?=D1=82=D1=8C=20=D0=BF=D0=B5=D1=80=D0=B5=D0=BA=D0=BB=D1=8E=D1=87?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D1=8F=20=D1=83=D1=80=D0=BE=D0=B2=D0=BD=D1=8F?= =?UTF-8?q?=20=D0=BB=D0=BE=D0=B3=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F=20*=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD?= =?UTF-8?q?=D1=8B=20=D0=B5=D1=89=D1=91=20=D0=BD=D0=B5=D1=81=D0=BA=D0=BE?= =?UTF-8?q?=D0=BB=D1=8C=D0=BA=D0=BE=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8?= =?UTF-8?q?=D0=B9=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D1=8B=20=D1=81=20=D1=84?= =?UTF-8?q?=D0=B0=D0=B9=D0=BB=D0=B0=D0=BC=D0=B8=20*=20=D0=94=D0=BE=D0=B1?= =?UTF-8?q?=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B0=20=D0=BD=D0=B0=D1=87=D0=B0?= =?UTF-8?q?=D0=BB=D1=8C=D0=BD=D0=B0=D1=8F=20=D0=BF=D0=BE=D0=B4=D0=B4=D0=B5?= =?UTF-8?q?=D1=80=D0=B6=D0=BA=D0=B0=20=D1=84=D0=B5=D1=82=D1=87=D0=B8=D0=BD?= =?UTF-8?q?=D0=B3=D0=B0=20=D0=B8=D0=BD=D1=82=D0=B5=D1=80=D0=BD=D0=B5=D1=82?= =?UTF-8?q?=20=D1=80=D0=B5=D1=81=D1=83=D1=80=D1=81=D0=BE=D0=B2=20*=20?= =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D1=8B=20?= =?UTF-8?q?=D0=BC=D0=B5=D0=BB=D0=BA=D0=B8=D0=B5=20=D0=BE=D1=88=D0=B8=D0=B1?= =?UTF-8?q?=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README-RU.md | 41 ++++++++++++++ README.md | 46 +++++++++++++++- modules/clear_db.py | 72 ------------------------- modules/files_worker.py | 41 ++++++++++++++ modules/source_fetcher.py | 109 ++++++++++++++++++++++++++++++++++++++ portproton.py | 3 +- test.py | 0 7 files changed, 238 insertions(+), 74 deletions(-) create mode 100644 README-RU.md delete mode 100644 modules/clear_db.py create mode 100644 modules/source_fetcher.py delete mode 100755 test.py diff --git a/README-RU.md b/README-RU.md new file mode 100644 index 0000000..7d6f0c5 --- /dev/null +++ b/README-RU.md @@ -0,0 +1,41 @@ +
+ +

PortProton 2.0 (В разработке)

+ + GitHub License + + + Telegram + + + YouTube Channel Subscribers + +
+

+ Проект(реализация на python), призванный сделать запуск Windows-игр в Linux простым и удобным как для начинающих, так и для опытных пользователей.
+ Проект стремится сделать запуск игр (и другого программного обеспечения) максимально простым, но в то же время предоставляет гибкие настройки для опытных пользователей. +

+
+ +# **Язык README** + +**Русский** - [English](README.md) + +## Внимание + +**Официальный сайт проекта** с сентября 2022 года: https://linux-gaming.ru. **Любой другой сайт - фальшивка!** + +## Особенности + +- Основан на версии WINE от Valve (Proton) и ее модификациях (Proton GE). + Включает набор скриптов, объединенных с самим wine-proton, контейнер Steam Runtime Sniper с добавлением портированных версий MANGOHUD (вывод полезной информации в окно игры: FPS, FrameTime, CPU, GPU и т.д.) и vkBasalt (улучшение графики в играх, очень хорош в связке с FSR, DLSS) + множество уже настроенных оптимизаций для максимальной производительности. + +- Для любителей консольных игр предлагается множество эмуляторов консолей (на вкладке ЭМУЛЯТОРЫ ): PPSSPP, Citra, Cemu, ePSXe, MAME и многие другие.. + +**ПОЖАЛУЙСТА, НЕ СООБЩАЙТЕ О НАЙДЕННЫХ ОШИБКАХ В WINEHQ ИЛИ ПРОГРАММНОМ ОБЕСПЕЧЕНИИ VALVE!** + +## **Ссылка на исходный код версий wine используемых в PortProton:** + +* WINE-PROTON: https://github.com/ValveSoftware/Proton + +* WINE-PROTON-GE: https://github.com/GloriousEggroll/proton-ge-custom diff --git a/README.md b/README.md index 13246b7..2209968 100644 --- a/README.md +++ b/README.md @@ -1 +1,45 @@ -# PortProton_2.0 \ No newline at end of file + +
+ +

PortProton 2.0 (in progress)

+ + GitHub License + + + Telegram + + + YouTube Channel Subscribers + +
+

+ A project (python realisation) designed to make running Windows games on Linux easy and convenient for both beginners and advanced users.
+ The project aims to simplify the process of launching games (and other software) while also providing flexible settings for experienced users. +

+
+ +# **Readme Language** + +**English** - [Русский](README-RU.md) + +## Attention + +The **official website of the project** since September 2022 is: https://linux-gaming.ru. **Any other site is fake!** + +## Features + +- Based on the version of WINE from Valve (Proton) and its modifications (Proton GE). + Includes a set of scripts combined with wine-proton itself, a Steam Runtime Sniper container with the addition of + ported mangoHud (output useful information over the game window: FPS, frametime, CPU, GPU, etc.), + vkBasalt (improvement of graphics in games, great alongside FSR or DLSS) versions, + and many already configured optimizations for maximum performance. + +- For fans of console games, there are many console emulators to choose from (in the EMULATORS tab): PPSSPP, Citra, Cemu, ePSXe, MAME, and many others. + +**PLEASE DON'T REPORT BUGS ENCOUNTERED WITH THIS AT WINEHQ OR VALVE SOFTWARE!** + +## **Wine sources used in PortWINE:** + +* WINE-PROTON: https://github.com/ValveSoftware/Proton + +* WINE-PROTON-GE: https://github.com/GloriousEggroll/proton-ge-custom diff --git a/modules/clear_db.py b/modules/clear_db.py deleted file mode 100644 index f7d85bd..0000000 --- a/modules/clear_db.py +++ /dev/null @@ -1,72 +0,0 @@ -import os -import glob -import re - -from .log import * - -def main_clear_db(): - base_dir = os.path.dirname(os.path.realpath(__file__)) - portwine_db_path = os.path.join(base_dir, 'portwine_db') - os.makedirs(portwine_db_path, exist_ok=True) - - for filename in glob.glob(os.path.join(portwine_db_path, '*')): # Установка разрешений на файлы - os.chmod(filename, 0o644) - - duplicate_finder = {} #Поиск дубликатов в файлах - for filename in glob.glob(os.path.join(portwine_db_path, '*')): - with open(filename, 'r') as file: - lines = file.readlines() - for line in lines: - if '.exe' in line and '#' in line: - line = line.strip() - if line not in duplicate_finder: - duplicate_finder[line] = [] - duplicate_finder[line].append(filename) - - duplicates = {line: files for line, files in duplicate_finder.items() if len(files) > 1} - if duplicates: - log.warning("Обнаружены дубликаты в файлах:") - for line, files in duplicates.items(): - for file in files: - log.info(f"{file.split('portwine_db/')[1]} содержит дубликат: {line}") - exit(1) - - - for ppdb in glob.glob(os.path.join(portwine_db_path, '*')): # Обработка каждого файла - log.debug(ppdb) - - with open(ppdb, 'r') as file: # Удаление определённых строк - lines = file.readlines() - - lines = [line for line in lines if not line.startswith(('##export', '##add_'))] - lines = [line for line in lines if not ( - re.search(r'MANGOHUD|FPS_LIMIT|VKBASALT|_RAY_TRACING|_DLSS|PW_GUI_DISABLED_CS|PW_USE_GAMEMODE|' - r'PW_USE_SYSTEM_VK_LAYERS|PW_DISABLE_COMPOSITING|PW_USE_EAC_AND_BE|PW_USE_OBS_VKCAPTURE|' - r'GAMESCOPE|PW_GS', line) - )] - - if any(re.search(r'PW_USE_DGVOODOO2="0"|PW_DGVOODOO2="0"', line) for line in lines): - lines = [line for line in lines if not re.search(r'PW_USE_DGVOODOO2|PW_DGV', line)] - - - lines = [re.sub(r'export PW_WINE_USE=.*', 'export PW_WINE_USE="WINE_LG"', line) - if 'PW_WINE_USE="WINE_LG' in line else line for line in lines] - lines = [re.sub(r'export PW_WINE_USE=.*', 'export PW_WINE_USE="PROTON_LG"', line) - if 'PW_WINE_USE="PROTON_LG' in line else line for line in lines] - - with open(ppdb, 'w') as file: # Сохранение изменений - file.writelines(lines) - - ppdb_base = os.path.basename(ppdb) # Переименование файлов - if ppdb_base.endswith('.exe.ppdb'): - new_name = f"{ppdb_base[:-9]}.ppdb" - os.rename(ppdb, os.path.join(portwine_db_path, new_name)) - elif ppdb_base.endswith('.EXE.ppdb'): - new_name = f"{ppdb_base[:-9]}.ppdb" - os.rename(ppdb, os.path.join(portwine_db_path, new_name)) - elif not ppdb_base.endswith('.ppdb'): - new_name = f"{ppdb_base}.ppdb" - os.rename(ppdb, os.path.join(portwine_db_path, new_name)) - - log.info("ГОТОВО!") - exit(0) diff --git a/modules/files_worker.py b/modules/files_worker.py index deef999..794a77f 100755 --- a/modules/files_worker.py +++ b/modules/files_worker.py @@ -62,6 +62,25 @@ def try_force_link_dir(path, link): except Exception as e: log.error(f"failed to create link for file: {e}") +def replace_file(file_path, file_name): # функция замены файла (сначала запись во временный файл, потом замена) + try: + if os.path.exists(file_name) and os.path.getsize(file_name) > 0: + os.replace(file_name, file_path) # Меняем местами файлы, если временный файл не пуст + log.info(f"Данные успешно обновлены в {file_path}.") + else: + log.warning(f"Временный файл {file_name} пуст, замена в {file_path} не выполнена.") + if os.path.exists(file_name): + os.remove(file_name) # Удаляем пустой временный файл + except Exception as e: + log.error(f"Ошибка при замене файла: {e}") + +def try_write_temp_file(file_path, file_name): # функция записи в tmp + try: + with open(file_path, 'w') as file: + file.write("\n".join(file_name)) # Записываем все имена файлов во временный файл + except Exception as e: + log.error(f"Ошибка при записи во временный файл {file_path}: {e}") + def try_remove_dir(path): if os.path.exists(path) and os.path.isdir(path): try: @@ -69,7 +88,22 @@ def try_remove_dir(path): except Exception as e: log.error(f"failed to remove directory: {e}") +def get_last_modified_time(file_path): # функция получения времени последнего изменения файла + try: + return os.path.getmtime(file_path) + except FileNotFoundError: + log.warning(f"Файл не найден: {file_path}") + return None + except Exception as e: + log.error(f"Ошибка при получении времени изменения файла {file_path}: {e}") + return None + + def unpack(archive_path, extract_to=None): + # Проверяем, существует ли архив + if not os.path.isfile(archive_path): + log.error(f"Архив {archive_path} не найден.") + return False try: if extract_to is None: # TODO: перенести распаковку по умолчанию в tmp @@ -83,8 +117,15 @@ def unpack(archive_path, extract_to=None): log.info(f"Архив {archive_path} успешно распакован в {full_path}") except tarfile.TarError as e: log.error(f"Ошибка при распаковке архива {archive_path}: {e}") + return False + except PermissionError: + log.error(f"Ошибка доступа к файлу {archive_path}. Убедитесь, что у вас есть права на чтение.") + return False except Exception as e: log.error(f"Неизвестная ошибка: {e}") + return False + + return True def check_hash_sum(check_file, check_sum): if check_sum and isinstance(check_sum, str): diff --git a/modules/source_fetcher.py b/modules/source_fetcher.py new file mode 100644 index 0000000..8add8f7 --- /dev/null +++ b/modules/source_fetcher.py @@ -0,0 +1,109 @@ +import re +import requests +import time + +from modules.downloader import try_download +from modules.files_worker import * + +count_wines = 25 # Количество элементов для записи в .tmp файл +repos = { # Список репозиториев для обработки и их короткие имена + "GloriousEggroll/proton-ge-custom": "proton-ge-custom", + "Kron4ek/Wine-Builds": "Kron4ek", + "GloriousEggroll/wine-ge-custom": "wine-ge-custom", + "CachyOS/proton-cachyos": "proton-cachyos", + "Castro-Fidel/wine_builds": "LG" +} + +def source_list_checker(tmp_path): # Проверка наличия и обновления файлов со списками исходников + for repo, short_name in repos.items(): + output_file = os.path.join(tmp_path, f"{short_name}.tmp") + + if not os.path.exists(output_file): + log.info(f"Файл {output_file} не существует. Получаем данные из репозитория.") + source_list_downloader(repo, tmp_path, short_name, output_file) + continue # Переходим к следующему репозиторию + + if os.path.getsize(output_file) == 0: # Проверяем, является ли файл пустым + log.info(f"Файл {output_file} пуст. Обновляем данные.") + source_list_downloader(repo, tmp_path, short_name, output_file) + continue # Переходим к следующему репозиторию + + last_modified_time = get_last_modified_time(output_file) # Получаем время последнего изменения файла + if last_modified_time is None: # Если время не удалось получить, пробуем обновить файл + log.info(f"Не удалось получить время последнего изменения для {output_file}. Попытаемся обновить.") + source_list_downloader(repo, tmp_path, short_name, output_file) + continue # Переходим к следующему репозиторию + + current_time = time.time() + + if current_time - last_modified_time >= 10800: # 10800 секунд = 3 часа, проверяем, устарел ли файл + log.info(f"Файл {output_file} устарел. Обновляем данные.") + source_list_downloader(repo, tmp_path, short_name, output_file) + else: + log.info(f"Файл {output_file} существует и был обновлён менее 3 часов назад. Используем кэшированные данные.") + +def source_list_downloader(repo, tmp_path, short_name, output_file): + url = f"https://api.github.com/repos/{repo}/releases?per_page={count_wines}" + temp_file = os.path.join(tmp_path, f"{short_name}.tmp.new") # Временный файл + + try: + response = requests.get(url) + response.raise_for_status() # Возбудим исключение для ошибок HTTP + releases = response.json() + + log.debug(f"Ответ API: {releases}") + tar_files = [] # Проверяем каждый релиз + for release in releases: + assets = release.get('assets', []) + for asset in assets: + asset_name = asset['name'] + if ( + (re.search(r'(wine|proton)', asset_name, re.IGNORECASE) or + re.search(r'^GE-Proton\d+-\d+\.tar\.gz$', asset_name) or + re.search(r'^GE-Proton\d+(-\d+)?\.tar\.xz$', asset_name)) and + (asset_name.endswith('.tar.gz') or asset_name.endswith('.tar.xz')) + ): + tar_files.append(asset_name) # Собираем все подходящие файлы + log.debug(f"Найденный файл: {asset_name}") + + if not tar_files: + log.warning(f"Нет подходящих файлов в репозитории {repo}.") + return # Выходим из функции, если нет файлов + + with open(temp_file, 'w') as file: # Записываем найденные файлы во временный файл + file.write("\n".join(tar_files)) + + log.info(f"Данные успешно записаны в временный файл {temp_file}.") + + if os.path.exists(temp_file): # Если запись прошла успешно, заменяем основной файл + os.replace(temp_file, output_file) + log.info(f"Файл {output_file} успешно обновлен.") + + except requests.exceptions.RequestException as e: + log.error(f"Ошибка при получении данных из {repo}: {str(e)}") + +def get_sources(args, tmp_path, dist_path): + os.makedirs(tmp_path, exist_ok=True) + source_list_checker(tmp_path) + + if args: + for arg in args: + for repo, short_name in repos.items(): # определяем короткое имя репозитория + tmp_file_path = os.path.join(tmp_path, f"{short_name}.tmp") + + if os.path.exists(tmp_file_path): # проверяем наличие файла + with open(tmp_file_path, 'r') as file: + all_tar_gz_files = file.read().splitlines() + + for file_to_download in all_tar_gz_files: # Ищем совпадение в файле + if arg in file_to_download: + log.info(f"Найдено совпадение для '{arg}': {file_to_download} в файле {tmp_file_path}") + url = f"https://github.com/{repo}/releases/latest/download/{file_to_download}" # Получаем URL файла + tmp_file = os.path.join(tmp_path, file_to_download) + if not os.path.exists(tmp_file): + try: + try_download(url, tmp_path) + unpack(tmp_file, dist_path) + try_remove_file(tmp_file) + except Exception as e: + log.error(f"Ошибка при загрузке или распаковке: {str(e)}") diff --git a/portproton.py b/portproton.py index 7890b4e..b8b668a 100755 --- a/portproton.py +++ b/portproton.py @@ -5,6 +5,7 @@ from modules.log import * from modules.env_var import * from modules.files_worker import * from modules.downloader import * +from modules.source_fetcher import * # переменные которые вынесем в отельный файл, аля var plugins_ver = "20" @@ -33,7 +34,7 @@ if __name__ == "__main__": case "--get-wine": # без аргументов сохраняем список доступных в tmp_path/get_wine.tmp и выводим в терминал # если есть аргумент (например WINE_LG_10-1) то обновляем и парсим tmp_path/get_wine.tmp с последующим скачиванием - get_wine(sys.argv[2:]) + get_sources(sys.argv[2:], tmp_path, dist_path) case "--get-dxvk": # без аргументов сохраняем список доступных в tmp_path/get_dxvk.tmp и выводим в терминал # если есть аргумент (например 2.5.3-31) то обновляем и парсим tmp_path/get_dxvk.tmp с последующим скачиванием diff --git a/test.py b/test.py deleted file mode 100755 index e69de29..0000000