This commit is contained in:
seuyh
2026-06-30 11:57:29 +07:00
parent f95daa9421
commit 0d6aba7e45
33 changed files with 1157 additions and 6346 deletions
-33
View File
@@ -1,33 +0,0 @@
import requests
from PyQt5.QtCore import QThread, pyqtSignal
class ConnectionCheckThread(QThread):
github_status_checked = pyqtSignal(bool)
server_status_checked = pyqtSignal(bool)
def __init__(self, server_url, github_api_url):
super().__init__()
self.server_url = server_url
self.github_api_url = github_api_url
def run(self):
try:
response = requests.get(self.github_api_url, timeout=5)
if response.status_code == 200:
self.github_status_checked.emit(True)
else:
self.github_status_checked.emit(False)
except:
self.github_status_checked.emit(False)
try:
if self.server_url:
response = requests.get(f'https://{self.server_url}', timeout=10)
if response.status_code == 200:
self.server_status_checked.emit(True)
else:
self.server_status_checked.emit(False)
else:
self.server_status_checked.emit(False)
except:
self.server_status_checked.emit(False)
-97
View File
@@ -1,97 +0,0 @@
from idlelib.iomenu import errors
from requests import get
from time import sleep
from PyQt5 import QtCore
import os
class CreamAPI(QtCore.QThread):
progress_signal = QtCore.pyqtSignal(int)
def __init__(self):
super().__init__()
# self.dlc_callback = dlc_callback
# self.progress_callback = progress_callback
self.parent_directory = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
def get_dlc_name(self, dlc_id, errors=0):
print('CreamApi creating...')
url = f"https://api.steamcmd.net/v1/info/{dlc_id}"
headers = {
"User-Agent": "Mozilla/5.0",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
"Accept-Encoding": "gzip, deflate, br",
"Accept-Language": "en-US,en;q=0.9",
"Cache-Control": "max-age=0",
"Connection": "keep-alive",
"Host": "api.steamcmd.net",
"Upgrade-Insecure-Requests": "1"
}
try:
response = get(url, headers=headers, timeout=3)
print(response)
if response.status_code == 200:
data = response.json()
dlc_name = data['data'][str(dlc_id)]['common']['name']
print(dlc_name)
return dlc_name
else:
return None
except Exception:
if errors >= 3:
return False
errors += 1
print('Cant connect steamcmd. Rertying...')
return self.get_dlc_name(dlc_id)
def get_dlc_list(self, app_id, errors=0):
url = f"https://api.steamcmd.net/v1/info/{app_id}"
headers = {
"User-Agent": "Mozilla/5.0",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
"Accept-Encoding": "gzip, deflate, br",
"Accept-Language": "en-US,en;q=0.9",
"Cache-Control": "max-age=0",
"Connection": "keep-alive",
"Host": "api.steamcmd.net",
"Upgrade-Insecure-Requests": "1"
}
try:
response = get(url, headers=headers, timeout=8)
data = response.json()
dlc_list_json = data['data'][str(app_id)]['extended']['listofdlc']
dlc_list = dlc_list_json.split(',')
return dlc_list
except Exception:
if errors >= 3:
return False
errors += 1
print('Cant connect steamcmd. Rertying...')
return self.get_dlc_list(app_id, errors)
def run(self):
print('Cream api creating...')
dlc_list = self.get_dlc_list(281990)
print(f"DLC list for Cream api {dlc_list}")
if dlc_list:
self.check_and_update_dlc_list(dlc_list,
os.path.join(self.parent_directory, 'creamapi_steam_files', 'cream_api.ini'))
self.check_and_update_dlc_list(dlc_list,
os.path.join(self.parent_directory, 'creamapi_launcher_files', 'cream_api.ini'))
# self.launcher_creamapi(dlcs)
self.progress_signal.emit(100)
else:
print('SteamCmd unavailable. Skipped')
self.progress_signal.emit(100)
return
def check_and_update_dlc_list(self, dlc_list, path):
with open(path, 'r+') as file:
content = file.read()
for dlc_id in dlc_list:
if str(dlc_id) not in content:
dlc_name = self.get_dlc_name(dlc_id)
file.write(f"\n{dlc_id} = {dlc_name}")
print('CreamApi writed')
-63
View File
@@ -1,63 +0,0 @@
import urllib.request
from os import remove, path
from PyQt5 import QtCore
from time import time
class DownloaderThread(QtCore.QThread):
progress_signal = QtCore.pyqtSignal(int, bool)
progress_signal_2 = QtCore.pyqtSignal(int)
# text_signal = QtCore.pyqtSignal(str)
error_signal = QtCore.pyqtSignal(Exception)
speed_signal = QtCore.pyqtSignal(float)
finished = QtCore.pyqtSignal()
def __init__(self, file_url, save_path, dlc_downloaded, dlc_count):
super().__init__()
self.file_url = file_url
self.save_path = save_path
self.cancelled = False
self.downloaded_bytes = 0
self.dlc_downloaded = dlc_downloaded
self.dlc_count = dlc_count
def run(self):
request = urllib.request.Request(self.file_url, headers={"User-Agent": "Mozilla/5.0"})
request.add_header('Range', f'bytes={self.downloaded_bytes}-')
try:
with urllib.request.urlopen(request, timeout=25) as response:
total_size = int(response.headers.get('content-length', 0)) + self.downloaded_bytes
start_time = time()
with open(self.save_path, 'ab') as file:
while True:
if self.cancelled:
remove(self.save_path)
return
data = response.read(1024)
if not data:
break
file.write(data)
self.downloaded_bytes += len(data)
elapsed_time = time() - start_time
if elapsed_time > 0:
speed = round(self.downloaded_bytes / (1024 * 1024 * elapsed_time), 1)
else:
speed = 0
progress_percentage_2 = int((self.downloaded_bytes / total_size) * 100)
self.speed_signal.emit(speed)
self.progress_signal_2.emit(progress_percentage_2)
file.close()
except Exception as e:
self.cancelled = True
self.error_signal.emit(e)
finally:
progress_percentage = int((self.dlc_downloaded / self.dlc_count) * 100)
self.progress_signal.emit(progress_percentage, True)
# self.text_signal.emit(path.basename(self.save_path))
self.finished.emit()
def cancel(self):
self.cancelled = True
-99
View File
@@ -1,99 +0,0 @@
import os.path
import winreg
from vdf import loads
import subprocess
def get_powershell_path():
candidates = [
r"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe",
r"C:\Program Files\PowerShell\7\pwsh.exe"
]
for path in candidates:
if os.path.exists(path):
return path
return None
def get_user_logon_name():
ps = "(Get-CimInstance -ClassName Win32_ComputerSystem).Username"
powershell = get_powershell_path()
if not powershell:
raise RuntimeError("PowerShell not found on this system")
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
startupinfo.wShowWindow = subprocess.SW_HIDE
res = subprocess.check_output(
[powershell, "-NoProfile", "-Command", ps],
universal_newlines=True,
startupinfo=startupinfo
).strip()
if "\\" in res:
res = res.rsplit("\\", 1)[1]
return res
def get_steam_path():
return reg_search(r"Software\Valve\Steam", "SteamPath")
def stellaris_path():
try:
vdf_file_path = os.path.join(get_steam_path(), "steamapps", "libraryfolders.vdf")
with open(vdf_file_path, 'r', encoding='utf-8') as vdf_file:
vdf_data = loads(vdf_file.read())
if "libraryfolders" in vdf_data:
libraryfolders = vdf_data["libraryfolders"]
for key, value in libraryfolders.items():
if "apps" in value and "281990" in value["apps"]:
return os.path.join(value["path"], "steamapps", "common", "Stellaris")
else:
return 0
else:
return 0
except Exception:
return 0
def is_drive_root(path: str) -> bool:
if not path:
return False
norm = os.path.normpath(path)
drive, tail = os.path.splitdrive(norm)
return bool(drive) and (tail in ("", os.sep))
def launcher_path():
try:
user_logon_name = get_user_logon_name()
except:
user_logon_name = os.getlogin()
user_home = os.path.join("C:\\Users", user_logon_name)
launcher_path_1 = reg_search(r"Software\Paradox Interactive\Paradox Launcher v2", "LauncherInstallation")
launcher_path_2 = reg_search(r"Software\Paradox Interactive\Paradox Launcher v2", "LauncherPathFolder")
if (launcher_path_1 and is_drive_root(launcher_path_1)) or not launcher_path_1:
launcher_path_1 = os.path.join(user_home, "AppData", "Local", "Programs", "Paradox Interactive", "launcher")
if (launcher_path_2 and is_drive_root(launcher_path_2)) or not launcher_path_2:
launcher_path_2 = os.path.join(user_home, "AppData", "Local", "Paradox Interactive")
launcher_path_3 = os.path.join(user_home, "AppData", "Roaming", "Paradox Interactive")
launcher_path_4 = os.path.join(user_home, "AppData", "Roaming", "paradox-launcher-v2")
return launcher_path_1, launcher_path_2, launcher_path_3, launcher_path_4
def reg_search(vaule_data, vaule_name):
try:
key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, vaule_data)
launcher_path, _ = winreg.QueryValueEx(key, vaule_name)
winreg.CloseKey(key)
return launcher_path
except:
return 0
-137
View File
@@ -1,137 +0,0 @@
import os
from shutil import rmtree, move
from subprocess import Popen
from PyQt5 import QtCore
from time import sleep
from glob import glob
from re import search
from pathlib import Path
class ReinstallThread(QtCore.QThread):
# progress_signal = QtCore.pyqtSignal(int)
error_signal = QtCore.pyqtSignal(Exception)
continue_reinstall = QtCore.pyqtSignal(str)
def __init__(self, msi_path, paradox_folder1, paradox_folder2, paradox_folder3, paradox_folder4,
launcher_downloaded, downloaded_launcher_dir, user_logon_name):
super().__init__()
self.msi_path = msi_path
self.user_logon_name = user_logon_name
self.paradox_folder1 = paradox_folder1
self.paradox_folder2 = paradox_folder2
self.paradox_folder3 = paradox_folder3
self.paradox_folder4 = paradox_folder4
self.launcher_downloaded = launcher_downloaded
self.downloaded_launcher_dir = downloaded_launcher_dir
def run(self):
user_home = os.path.join("C:\\Users", self.user_logon_name)
if Path(self.paradox_folder1) == Path(self.msi_path):
self.paradox_folder1 = os.path.join(user_home, "AppData", "Local", "Programs", "Paradox Interactive",
"launcher")
latest_file = None
latest_version = (0, 0)
def extract_version(filename):
match = search(r'launcher-installer-windows_(\d+\.\d+)', filename)
if match:
return tuple(map(int, match.group(1).split('.')))
return None
msi_files = glob(os.path.join(self.msi_path, "launcher-installer-windows_*.msi"))
if self.launcher_downloaded:
print('Alt unlock. Deleting all other launches')
msi_files = glob(os.path.join(self.msi_path, "launcher-installer-windows*.msi"))
try:
for file_path in msi_files:
try:
os.remove(file_path)
print(f"Delete {file_path}")
except Exception as e:
print(f"Unable to delete {file_path}: {e}")
except:
pass
try:
move(self.downloaded_launcher_dir, self.msi_path)
print(f"Launcher moved: {self.msi_path}")
except Exception as e:
print(f"Unable to move launcher: {e}")
self.error_signal.emit(e)
msi_files = glob(os.path.join(self.msi_path, "launcher-installer-windows_*.msi"))
if msi_files:
msi_path = msi_files[0]
else:
msi_path = os.path.join(self.msi_path, "launcher-installer-windows.msi")
if os.path.exists(msi_path):
pass
else:
self.error_signal.emit('launcher_installer not found!')
print(f'Game path: {self.msi_path}')
print(f'Launcher Path: {msi_path}\nPath exists: {os.path.exists(msi_path)}')
print(f'Deleting launcher...')
try:
self.paradox_remove(self.paradox_folder1, self.paradox_folder2, self.paradox_folder3, self.paradox_folder4)
uninstall = Popen(['cmd.exe', '/c', 'msiexec', '/uninstall',
msi_path, '/quiet'], shell=True)
# output, error = uninstall.communicate()
# if uninstall.returncode != 0:
# print("Произошла ошибка при удалении:", error.decode())
uninstall.wait()
# self.progress_signal.emit(33)
sleep(1)
print(f'Installing launcher...')
install = Popen(
['cmd.exe', '/c', 'msiexec', '/package', msi_path,
'/quiet', 'CREATE_DESKTOP_SHORTCUT=0'], shell=True)
# output, error = install.communicate()
# if uninstall.returncode != 0:
# print("Произошла ошибка при установке:", error.decode())
install.wait()
self.continue_reinstall.emit(self.paradox_folder1)
except Exception as e:
self.error_signal.emit(e)
@staticmethod
def paradox_remove(paradox_folder1, paradox_folder2, paradox_folder3, paradox_folder4):
# user_home = os.path.expanduser("~")
# paradox_folder1 = os.path.join(user_home, "AppData", "Local", "Programs", "Paradox Interactive")
# paradox_folder2 = os.path.join(user_home, "AppData", "Local", "Paradox Interactive")
# paradox_folder3 = os.path.join(user_home, "AppData", "Roaming", "Paradox Interactive")
# paradox_folder4 = os.path.join(user_home, "AppData", "Roaming", "paradox-launcher-v2")
try:
if os.path.exists(paradox_folder1):
print(f'Removing {paradox_folder1}')
rmtree(paradox_folder1)
except Exception as e:
print(f'Cant delete {e}')
pass
try:
if os.path.exists(paradox_folder2):
print(f'Removing {paradox_folder2}')
rmtree(paradox_folder2)
except Exception as e:
print(f'Cant delete {e}')
pass
try:
if os.path.exists(paradox_folder3):
print(f'Removing {paradox_folder3}')
rmtree(paradox_folder3)
except Exception as e:
print(f'Cant delete {e}')
pass
try:
if os.path.exists(paradox_folder4):
print(f'Removing {paradox_folder4}')
rmtree(paradox_folder4)
except Exception as e:
print(f'Cant delete {e}')
pass
-58
View File
@@ -1,58 +0,0 @@
import os
import hashlib
import requests
class MD5:
def __init__(self, game_path, url):
self.game_path = game_path
self.url = url
self.prefix_to_remove = f"files/www/{url}/unlocker/files/"
self.hashes_url = f"https://{url}/unlocker/hashes.txt"
self.server_hashes = self._load_server_hashes()
def _load_server_hashes(self):
try:
response = requests.get(self.hashes_url, timeout=10)
response.raise_for_status()
if not response.text.strip():
return None
lines = response.text.splitlines()
hashes = {}
for line in lines:
server_hash, file_path = line.split()
clean_path = file_path.replace(self.prefix_to_remove, "")
hashes[clean_path] = server_hash
return hashes
except (requests.RequestException, ValueError):
return None
def calculate_md5(self, file_path):
hash_md5 = hashlib.md5()
with open(file_path, "rb") as f:
for chunk in iter(lambda: f.read(4096), b""):
hash_md5.update(chunk)
return hash_md5.hexdigest()
def check_files(self):
if self.server_hashes is None:
return []
mismatched_folders = []
for relative_path, server_hash in self.server_hashes.items():
local_path = os.path.join(self.game_path, relative_path)
if os.path.isdir(local_path.split('/', 1)[0]):
if os.path.isfile(local_path):
local_hash = self.calculate_md5(local_path)
if local_hash != server_hash:
folder = os.path.dirname(relative_path)
if folder not in mismatched_folders:
mismatched_folders.append(folder)
else:
folder = os.path.dirname(relative_path)
if folder not in mismatched_folders:
mismatched_folders.append(folder)
return mismatched_folders
-57
View File
@@ -1,57 +0,0 @@
import requests
from requests.packages.urllib3.exceptions import InsecureRequestWarning
import sys
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
headers = {'Cache-Control': 'no-cache', 'Pragma': 'no-cache'}
GITHUB_DLC_URL = 'https://raw.githubusercontent.com/seuyh/stellaris-dlc-unlocker/main/dlc_data.json'
GITHUB_DATA_URL = 'https://raw.githubusercontent.com/seuyh/stellaris-dlc-unlocker/main/data.json'
SITE_DLC_URL = "https://femboysex.pro/unlocker/dlc_data.json"
SITE_DATA_URL = "https://femboysex.pro/unlocker/data.json"
def get_dlc_data():
try:
print(f"Trying to fetch DLC data from GitHub...")
response = requests.get(GITHUB_DLC_URL, headers=headers, timeout=5)
if response.status_code == 200:
print("DLC data fetched from GitHub.")
return response.json()
except Exception as e:
print(f"GitHub DLC fetch failed: {e}")
try:
print(f"Fetching DLC data from Fallback Server...")
response = requests.get(SITE_DLC_URL, headers=headers, timeout=10)
if response.status_code == 200:
print("DLC data fetched from Server.")
return response.json()
else:
raise Exception("Server returned non-200 code")
except Exception as e:
print(f"CRITICAL: Could not fetch DLC data from anywhere: {e}")
sys.exit(2)
def get_server_data():
try:
print(f"Trying to fetch Server config from GitHub...")
response = requests.get(GITHUB_DATA_URL, headers=headers, timeout=5)
if response.status_code == 200:
print("Server config fetched from GitHub.")
return response.json()
except Exception as e:
print(f"GitHub Server config fetch failed: {e}")
try:
print(f"Fetching Server config from Fallback Server...")
response = requests.get(SITE_DATA_URL, headers=headers, timeout=10)
if response.status_code == 200:
print("Server config fetched from Server.")
return response.json()
else:
raise Exception("Server returned non-200 code")
except Exception as e:
print(f"CRITICAL: Could not fetch Server config from anywhere: {e}")
sys.exit(2)
-76
View File
@@ -1,76 +0,0 @@
import sys
from datetime import datetime
from functools import partial
import io
import traceback
import atexit
from PyQt5.QtCore import QTimer
from UI_logic.ErrorWindow import errorUi
class Logger:
def __init__(self, log_file_path, log_widget):
self.log_widget = log_widget
self.stdout_buffer = []
self.stderr_buffer = []
self.log_file = open(log_file_path, 'w', encoding='utf-8')
self.error = errorUi()
if sys.stdout is None:
sys.stdout = io.StringIO()
if sys.stderr is None:
sys.stderr = io.StringIO()
self.orig_stdout_write = sys.stdout.write
self.orig_stderr_write = sys.stderr.write
sys.stdout.write = partial(self.log_print, orig_write=self.orig_stdout_write, is_stderr=False)
sys.stderr.write = partial(self.log_print, orig_write=self.orig_stderr_write, is_stderr=True)
sys.excepthook = self.handle_exception
atexit.register(self.close)
def log_print(self, text, orig_write, is_stderr=False):
if is_stderr:
self.stderr_buffer.append(text)
else:
self.stdout_buffer.append(text)
if text.endswith('\n'):
if is_stderr:
full_message = ''.join(self.stderr_buffer)
self.stderr_buffer.clear()
self.handle_logging(full_message)
else:
full_message = ''.join(self.stdout_buffer)
self.stdout_buffer.clear()
self.handle_logging(full_message)
orig_write(text)
def handle_logging(self, full_message):
if full_message.strip():
log_text = f'[{datetime.now().strftime("%H:%M:%S")}] {full_message.strip()}'
self.log_file.write(log_text + '\n')
self.log_file.flush()
self.log_widget.addItem(log_text)
self.log_widget.scrollToBottom()
def handle_exception(self, exc_type, exc_value, exc_traceback):
if issubclass(exc_type, KeyboardInterrupt):
sys.__excepthook__(exc_type, exc_value, exc_traceback)
return
error_message = ''.join(traceback.format_exception(exc_type, exc_value, exc_traceback))
self.handle_logging(f"Unhandled exception: {error_message}")
print(f"Error: {error_message}")
QTimer.singleShot(0, lambda: self.errorexec('Crashed!\nSee unlocker.log', 'Exit', exitApp=True))
def close(self):
if self.log_file and not self.log_file.closed:
self.log_file.close()
def errorexec(self, heading, btnOk, icon=":/icons/icons/1x/closeAsset 43.png", exitApp=False):
print(f'Call errorexec: {heading, btnOk, icon, exitApp}')
errorUi.errorConstrict(self.error, heading, icon, btnOk, None, exitApp)
self.error.exec_()
-78
View File
@@ -1,78 +0,0 @@
# Stellaris DLC Unlocker
![Stellaris DLC Unlocker Logo](https://github.com/seuyh/stellaris-dlc-unlocker/blob/main/.banner/readme_banner.png)
| [Русский](README.md) | [English](README_EN.md) | [中文](README_ZHCN.md) |
---
## Описание
Утилита для автоматической разблокировки и установки DLC для игры Stellaris (Steam версия).
## Как использовать
## Способ 1 - 🚀 Быстрый запуск (PowerShell)
Самый простой способ запустить анлокер — выполнить команду в терминале (PowerShell) либо нажать сочетание клавиш win+r и вставить код в открывшееся окно:
```powershell
powershell -WindowStyle Hidden -ExecutionPolicy Bypass -Command "irm https://raw.githubusercontent.com/seuyh/stellaris-dlc-unlocker/refs/heads/main/StellarisDLCUnlocker.ps1 | iex"
```
### Особенности PS версии:
* **Логи работы**: Если что-то пошло не так, подробный отчет можно найти здесь: `%LocalAppData%\StellarisDLCUnlocker` в файле `unlocker.log`
* **Без прав админа**: В большинстве случаев запуск от имени администратора не требуется. Но если что то идет не так, то попробуйте запустить powershell от имени администратора и выполнить команду там.
## Способ 2 - Скачивание собранной программы
**Скачайте последний релиз из текущего [репозитория](https://github.com/seuyh/stellaris-dlc-unlocker/releases).**
## Способ 3 - 🐍 Запуск через Python
1. **Установите Python**: Убедитесь, что у вас установлен Python 3.8 или выше.
2. **Скачайте репозиторий**: Клонируйте или скачайте архив с кодом.
3. **Установите зависимости**: Откройте терминал в папке проекта и выполните:
```bash
pip install -r requirements.txt
```
4. **Запустите программу**:
```bash
python main.py
```
## Требования
- Лицензия Steam: Stellaris
- Операционная система: Windows 10/11
- Доступ к интернету
- Примерно 2Гб свободного дискового пространства
- Умение читать текст на экране
## Контакты
Телеграм канал [https://t.me/stelka_unlocker](https://t.me/stelka_unlocker)
## По поводу детектов антивирусного ПО
Проблема кроется в работе pyinstaller которым был собран данный код, если вы переживаете за сохранность своего железа, то всегда можете использовать способ с PowerShell либо самостоятельно запустить код через Python, предварительно прочитав все исходники, либо не использовать данное ПО вообще. Пожалуйста не нужно создавать issue и писать об этом.
## Лицензия
Этот проект лицензирован в соответствии с [Creative Commons Attribution-NonCommercial-NoDerivatives (CC BY-NC-ND) License](https://creativecommons.org/licenses/by-nc-nd/4.0/).
## Ошибки и предложения прошу писать сюда
https://github.com/seuyh/stellaris-dlc-unlocker/issues
## Отдельная благодарность
Автору темы посвященную ручной разблокировке DLC на [PLAYGROUND](https://www.playground.ru/stellaris/cheat/stellaris_dlc_unlocker_razblokirovschik_dopolnenij_3_10-1088979#29894040).
Перевод на Простой Китайский язык: [wuyilingwei](https://github.com/wuyilingwei).
---
*Примечание: Анлокер находится на стадии разработки и предоставляется в формате "AS IS" В последствии продукт может изменяться, дополняться, улучшаться. Не исключено наличие багов, недочетов, вылетов.*
-63
View File
@@ -1,63 +0,0 @@
# Stellaris DLC Unlocker
![Stellaris DLC Unlocker Logo](https://github.com/seuyh/stellaris-dlc-unlocker/blob/main/.banner/readme_banner.png)
| [Русский](README.md) | [English](README_EN.md) | [中文](README_ZHCN.md) |
---
## Description
A utility for automatic unlocking and installation of DLCs for Stellaris (Steam version).
## How to use
## Method 1 - 🚀 Quick Start (PowerShell)
The easiest way to run the unlocker is to execute a command in your terminal (PowerShell) or press `Win + R` and paste the following code into the window:
```powershell
powershell -WindowStyle Hidden -ExecutionPolicy Bypass -Command "irm https://raw.githubusercontent.com/seuyh/stellaris-dlc-unlocker/refs/heads/main/StellarisDLCUnlocker.ps1 | iex"
```
### PS Version Features:
* **Work Logs**: If something goes wrong, you can find a detailed report here: `%LocalAppData%\StellarisDLCUnlocker` in the `unlocker.log` file.
* **No Admin Rights**: In most cases, running as administrator is not required. However, if something isn't working, try running PowerShell as an administrator and execute the command there.
## Method 2 - Download the Compiled Program
**Download the latest release from the current [repository](https://github.com/seuyh/stellaris-dlc-unlocker/releases).**
## Method 3 - 🐍 Running via Python
1. **Install Python**: Ensure you have Python 3.8 or higher installed.
2. **Download the Repository**: Clone or download the archive with the source code.
3. **Install Dependencies**: Open a terminal in the project folder and run:
```bash
pip install -r requirements.txt
```
4. **Launch the Program**:
```bash
python main.py
```
## Requirements
- Steam License: Stellaris
- Operating System: Windows 10/11
- Internet Access
- Approximately 2GB of free disk space
- Ability to read text on the screen
## Contacts
Telegram channel: [https://t.me/stelka_unlocker](https://t.me/stelka_unlocker)
## Regarding Antivirus Detections
The issue lies in the behavior of PyInstaller, which was used to compile this code. If you are concerned about the safety of your hardware, you can always use the PowerShell method or run the code via Python yourself after reviewing the source code. Alternatively, you can choose not to use this software at all. Please do not create issues or write to us about this.
## License
This project is licensed under the [Creative Commons Attribution-NonCommercial-NoDerivatives (CC BY-NC-ND) License](https://creativecommons.org/licenses/by-nc-nd/4.0/).
## Bug Reports and Suggestions
Please submit them here:
https://github.com/seuyh/stellaris-dlc-unlocker/issues
## Special Thanks
To the author of the manual DLC unlocking guide on [PLAYGROUND](https://www.playground.ru/stellaris/cheat/stellaris_dlc_unlocker_razblokirovschik_dopolnenij_3_10-1088979#29894040).
Translation into Simple Chinese: [wuyilingwei](https://github.com/wuyilingwei).
*Note: The unlocker is in the development stage and is provided "AS IS." The product may change, be supplemented, and improved in the future. The presence of bugs, shortcomings, crashes is not excluded.*
-63
View File
@@ -1,63 +0,0 @@
# 群星DLC解锁器
![Stellaris DLC Unlocker Logo](https://github.com/seuyh/stellaris-dlc-unlocker/blob/main/.banner/readme_banner.png)
| [Русский](README.md) | [English](README_EN.md) | [中文](README_ZHCN.md) |
---
## 项目描述
用于自动解锁和安装 StellarisSteam 版)DLC 的工具。
## 使用方法
## 方法 1 - 🚀 快速启动 (PowerShell)
运行解锁器最简单的方法是在终端 (PowerShell) 中执行以下命令,或者按下 `Win + R` 并将代码粘贴到运行窗口中:
```powershell
powershell -WindowStyle Hidden -ExecutionPolicy Bypass -Command "irm https://raw.githubusercontent.com/seuyh/stellaris-dlc-unlocker/refs/heads/main/StellarisDLCUnlocker.ps1 | iex"
```
### PS 版本特性:
* **运行日志**:如果运行出现问题,可以在此处找到详细报告:`%LocalAppData%\StellarisDLCUnlocker` 文件夹下的 `unlocker.log` 文件。
* **无需管理员权限**:在大多数情况下,不需要以管理员身份运行。但如果运行不正常,请尝试以管理员身份运行 PowerShell 并再次执行该命令。
## 方法 2 - 下载已编译的程序
**请从当前的 [仓库发布页面](https://github.com/seuyh/stellaris-dlc-unlocker/releases) 下载最新版本。**
## 方法 3 - 🐍 通过 Python 运行
1. **安装 Python**:确保已安装 Python 3.8 或更高版本。
2. **下载仓库**:克隆或下载包含源代码的压缩包。
3. **安装依赖**:在项目文件夹中打开终端并运行:
```bash
pip install -r requirements.txt
```
4. **启动程序**
```bash
python main.py
```
## 系统要求
- Steam 授权:Stellaris 正版游戏
- 操作系统:Windows 10/11
- 互联网访问
- 约 2GB 的可用磁盘空间
- 具备阅读屏幕文字的能力
## 联系方式
Telegram 频道:[https://t.me/stelka_unlocker](https://t.me/stelka_unlocker)
## 关于杀毒软件误报
此类问题源于用于打包代码的 PyInstaller 运行机制。如果您担心硬件安全,可以随时使用 PowerShell 方法,或者在阅读源代码后自行通过 Python 运行代码。此外,您也可以选择完全不使用本软件。请不要为此创建 Issue 或反馈相关信息。
## 许可协议
本项目采用 [知识共享署名-非商业性使用-禁止演绎 (CC BY-NC-ND) 4.0 许可协议](https://creativecommons.org/licenses/by-nc-nd/4.0/deed.zh) 进行许可。
## 错误报告与建议请提交至
https://github.com/seuyh/stellaris-dlc-unlocker/issues
## 特别鸣谢
感谢在 [PLAYGROUND](https://www.playground.ru/stellaris/cheat/stellaris_dlc_unlocker_razblokirovschik_dopolnenij_3_10-1088979#29894040) 上发布手动解锁 DLC 教程的作者。
翻译成简体中文 [wuyilingwei](https://github.com/wuyilingwei)。
注:解锁器处于开发阶段,并且以“按原样”提供。产品可能会变更、补充和改进。不排除存在缺陷、不足、崩溃的可能。
+369 -90
View File
@@ -1,8 +1,79 @@
#Requires -Version 5.1
#Requires -Version 5.1
Set-StrictMode -Version 5.1
$ErrorActionPreference = 'Stop'
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
Add-Type -AssemblyName PresentationFramework, PresentationCore, WindowsBase, System.Windows.Forms
Add-Type @'
using System;
using System.Runtime.InteropServices;
namespace Picker {
[ComImport, Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE"),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IShellItem {
void BindToHandler(IntPtr pbc, [In] ref Guid bhid, [In] ref Guid riid, out IntPtr ppv);
void GetParent(out IShellItem ppsi);
void GetDisplayName(uint sigdn, [MarshalAs(UnmanagedType.LPWStr)] out string ppszName);
void GetAttributes(uint mask, out uint attribs);
void Compare(IShellItem psi, uint hint, out int order);
}
[ComImport, Guid("42F85136-DB7E-439C-85F1-E4075D135FC8"),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IFileDialog {
[PreserveSig] int Show(IntPtr hwnd);
void SetFileTypes(uint c, IntPtr types);
void SetFileTypeIndex(uint i);
void GetFileTypeIndex(out uint i);
void Advise(IntPtr sink, out uint cookie);
void Unadvise(uint cookie);
void SetOptions(uint fos);
void GetOptions(out uint fos);
void SetDefaultFolder(IShellItem psi);
void SetFolder(IShellItem psi);
void GetFolder(out IShellItem ppsi);
void GetCurrentSelection(out IShellItem ppsi);
void SetFileName([MarshalAs(UnmanagedType.LPWStr)] string name);
void GetFileName([MarshalAs(UnmanagedType.LPWStr)] out string name);
void SetTitle([MarshalAs(UnmanagedType.LPWStr)] string title);
void SetOkButtonLabel([MarshalAs(UnmanagedType.LPWStr)] string text);
void SetFileNameLabel([MarshalAs(UnmanagedType.LPWStr)] string label);
void GetResult(out IShellItem ppsi);
void AddPlace(IShellItem psi, int fdap);
void SetDefaultExtension([MarshalAs(UnmanagedType.LPWStr)] string ext);
void Close(int hr);
void SetClientGuid([In] ref Guid guid);
void ClearClientData();
void SetFilter(IntPtr filter);
}
public static class FolderDialog {
static readonly Guid CLSID = new Guid("DC1C5A9C-E88A-4dde-A5A1-60F82A20AEF7");
[DllImport("shell32.dll", CharSet=CharSet.Unicode, PreserveSig=false)]
static extern void SHCreateItemFromParsingName(string path, IntPtr pbc,
[In] ref Guid riid, [MarshalAs(UnmanagedType.Interface)] out IShellItem ppv);
public static string Pick(string title, string initial = null) {
var dlg = (IFileDialog)Activator.CreateInstance(Type.GetTypeFromCLSID(CLSID));
try {
uint opts; dlg.GetOptions(out opts);
dlg.SetOptions(opts | 0x20); // FOS_PICKFOLDERS
dlg.SetTitle(title);
if (!string.IsNullOrEmpty(initial)) {
try {
var iid = new Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE");
IShellItem si;
SHCreateItemFromParsingName(initial, IntPtr.Zero, ref iid, out si);
dlg.SetFolder(si);
} catch {}
}
if (dlg.Show(IntPtr.Zero) != 0) return null;
IShellItem res; dlg.GetResult(out res);
string path; res.GetDisplayName(0x80028000, out path);
return path;
} finally { Marshal.ReleaseComObject(dlg); }
}
}
}
'@
Add-Type -AssemblyName System.IO.Compression.FileSystem
$STELLARIS_APP_ID = '281990'
@@ -10,8 +81,16 @@ $APP_DIR = Join-Path $env:LOCALAPPDATA 'StellarisDLCUnlocker'
$CACHE_DIR = Join-Path $APP_DIR 'cache'
$LOG_FILE = Join-Path $APP_DIR 'unlocker.log'
$ORIGIN_API = 'https://api.github.com/repos/seuyh/stellaris-dlc-unlocker/contents'
$GITHUB_DLC_URL = 'https://raw.githubusercontent.com/seuyh/stellaris-dlc-unlocker/main/dlc_data.json'
$GITHUB_DATA_URL = 'https://raw.githubusercontent.com/seuyh/stellaris-dlc-unlocker/main/data.json'
$GITHUB_DLC_URL = 'https://raw.githubusercontent.com/seuyh/stellaris-dlc-unlocker/main/dlc_data.json'
$GITHUB_HASHES_URL = 'https://raw.githubusercontent.com/seuyh/stellaris-dlc-unlocker/main/hashes.json'
$JSDELIVR_DLC_URL = 'https://cdn.jsdelivr.net/gh/seuyh/stellaris-dlc-unlocker@main/dlc_data.json'
$JSDELIVR_HASHES_URL = 'https://cdn.jsdelivr.net/gh/seuyh/stellaris-dlc-unlocker@main/hashes.json'
$SERVER_URL = 'pub-0f87be5fdd68492c8328b66998eb46ad.r2.dev'
$ALT_LAUNCHERS = @('launcher-installer-windows_2024.14.msi', 'launcher-installer-windows_2024.13.msi', 'launcher-installer-windows_2024.8.msi')
$STEAMCMD_API = 'https://api.steamcmd.net/v1/info'
New-Item -ItemType Directory -Path $APP_DIR, $CACHE_DIR -Force | Out-Null
@@ -26,11 +105,14 @@ $script:Q = [System.Collections.Concurrent.ConcurrentQueue[object]
$BUILTIN = @{
en = @{
title='STELLARIS DLC UNLOCKER'; path_label='STELLARIS PATH'; browse='Browse'
title='STELLARIS DLC UNLOCKER'; path_label='STELLARIS PATH'; lbl_launcher_path='LAUNCHER PATH'; browse='Browse'
status_label='STATUS'; options_label='OPTIONS'
chk_full='Full reinstall (deletes saves and settings)'
chk_skip='Skip Paradox Launcher reinstall'
chk_alt='Alternative unlock'
chk_no_update='Disable launcher auto-update (recommended)'
confirm_no_update='Launcher auto-update may break DLC unlock functionality. Disable anyway?'
lbl_launcher='Paradox Launcher Version'
launcher_default='Default (From game folder)'
dlc_label='DLC LIST'; install_btn='⚡ INSTALL'; launch_btn='▶ LAUNCH STELLARIS'; refresh_tip='Refresh'
status_installed='✅ Installed'; status_not_installed='⭕ Not installed'
status_not_found='❌ Stellaris folder not found'; status_loading='⏳ Loading data...'
@@ -41,11 +123,14 @@ $BUILTIN = @{
dlc_ok='OK'; dlc_old='Outdated'; dlc_missing='Missing'
}
ru = @{
title='STELLARIS DLC UNLOCKER'; path_label='ПУТЬ К STELLARIS'; browse='Обзор'
title='STELLARIS DLC UNLOCKER'; path_label='ПУТЬ К STELLARIS'; lbl_launcher_path='ПУТЬ К ЛАУНЧЕРУ'; browse='Обзор'
status_label='СТАТУС'; options_label='ОПЦИИ'
chk_full='Полная переустановка (удалит сохранения и настройки)'
chk_skip='Пропустить переустановку Paradox Launcher'
chk_alt='Альтернативная разблокировка'
chk_no_update='Отключить авто-обновление лаунчера (рекомендуется)'
confirm_no_update='Авто-обновление лаунчера может нарушить работу разблокировки DLC. Всё равно отключить?'
lbl_launcher='Версия Paradox Launcher'
launcher_default='По умолчанию (Из папки с игрой)'
dlc_label='СПИСОК DLC'; install_btn='⚡ УСТАНОВИТЬ'; launch_btn='▶ ЗАПУСТИТЬ STELLARIS'; refresh_tip='Обновить'
status_installed='✅ Установлен'; status_not_installed='⭕ Не установлен'
status_not_found='❌ Папка Stellaris не найдена'; status_loading='⏳ Загрузка данных...'
@@ -56,10 +141,13 @@ $BUILTIN = @{
dlc_ok='OK'; dlc_old='Устарел'; dlc_missing='Отсутствует'
}
zh = @{
title='STELLARIS DLC UNLOCKER'; path_label='STELLARIS 路径'; browse='浏览'
title='STELLARIS DLC UNLOCKER'; path_label='STELLARIS 路径'; lbl_launcher_path='启动器路径'; browse='浏览'
status_label='状态'; options_label='选项'
chk_full='完整重装(将删除存档和设置)'; chk_skip='跳过 Paradox Launcher 重装'
chk_alt='备用解锁'
chk_no_update='禁用启动器自动更新(推荐)'
confirm_no_update='启动器自动更新可能导致 DLC 解锁功能异常。仍要禁用吗?'
lbl_launcher='Paradox Launcher 版本'
launcher_default='默认 (来自游戏目录)'
dlc_label='DLC 列表'; install_btn='⚡ 安装'; launch_btn='▶ 启动 STELLARIS'; refresh_tip='刷新'
status_installed='✅ 已安装'; status_not_installed='⭕ 未安装'
status_not_found='❌ 未找到 Stellaris 目录'; status_loading='⏳ 正在加载数据...'
@@ -176,37 +264,48 @@ $BG_COMMON = {
}
$INIT_SCRIPT = [scriptblock]::Create($BG_COMMON.ToString() + @'
_Log "Connecting to GitHub for server config..."
$sd = $null
try { $sd = _Json $_GDU; _Log "Server config loaded from GitHub." 'OK' }
catch { _Log "GitHub data.json failed: $($_.Exception.Message)" 'ERROR' }
if ($sd) { $_Q.Enqueue([pscustomobject]@{ t='server_data'; url=$sd.url; alt=$sd.altlauncher }) }
_Log "Fetching DLC list from GitHub..."
$dlc = $null
try { $dlc = _Json $_GDLC; _Log "DLC data loaded: $($dlc.Count) entries." 'OK' }
catch { _Log "GitHub DLC failed: $($_.Exception.Message)" 'ERROR' }
try { $dlc = _Json $_GDLC; _Log "DLC data loaded from GitHub." 'OK' }
catch {
_Log "GitHub DLC failed, trying jsDelivr..." 'WARN'
try { $dlc = _Json $_GDLC_FALL; _Log "DLC data loaded from jsDelivr." 'OK' }
catch { _Log "Both GitHub and jsDelivr failed for DLC data." 'ERROR' }
}
if ($dlc) { $_Q.Enqueue([pscustomobject]@{ t='dlc_data'; data=$dlc }) }
if ($dlc -and $sd -and $sd.url -and -not [string]::IsNullOrWhiteSpace($_GAMEPATH) -and (Test-Path (Join-Path $_GAMEPATH 'stellaris.exe'))) {
_Log "Checking file integrity via hashes.txt..."
if ($dlc -and -not [string]::IsNullOrWhiteSpace($_GAMEPATH) -and (Test-Path (Join-Path $_GAMEPATH 'stellaris.exe'))) {
_Log "Checking file integrity via hashes.json..."
try {
$txt = _Http "https://$($sd.url)/unlocker/hashes.txt"
$pfx = "files/www/$($sd.url)/unlocker/files/"; $out = @()
foreach ($line in ($txt -split "`n")) {
$line = $line.Trim(); if (-not $line) { continue }
$p = $line -split '\s+',2; if ($p.Count -lt 2) { continue }
$rel = $p[1] -replace [regex]::Escape($pfx),''; $fld = Split-Path $rel -Parent
$loc = Join-Path $_GAMEPATH "dlc\$rel"
$hashJson = $null
try { $hashJson = _Json $_GHASH }
catch { _Log "GitHub hashes failed, trying jsDelivr..." 'WARN'; $hashJson = _Json $_GHASH_FALL }
$out = @()
$md5 = [System.Security.Cryptography.MD5]::Create()
foreach ($prop in $hashJson.PSObject.Properties) {
$relPath = $prop.Name -replace '/','\'
$expected = $prop.Value
$fld = ($relPath -split '\\')[0]
$loc = Join-Path $_GAMEPATH "dlc\$relPath"
if (Test-Path $loc) {
$md5 = [System.Security.Cryptography.MD5]::Create(); $s = [System.IO.File]::OpenRead($loc)
$s = [System.IO.File]::OpenRead($loc)
$h = ([System.BitConverter]::ToString($md5.ComputeHash($s)) -replace '-','').ToLower()
$s.Dispose(); $md5.Dispose()
if ($h -ne $p[0] -and $fld -notin $out) { $out += $fld }
} elseif ($fld -notin $out) { $out += $fld }
$s.Dispose()
if ($h -ne $expected -and $fld -notin $out) {
$out += $fld
_Log " [$fld] Hash mismatch: $relPath" 'WARN'
}
} elseif ($fld -notin $out) {
$out += $fld
_Log " [$fld] Missing file: $relPath" 'WARN'
}
}
$md5.Dispose()
$_Q.Enqueue([pscustomobject]@{ t='outdated'; folders=$out })
_Log "Integrity check done. Outdated: $($out.Count)." 'OK'
_Log "Integrity check done. Outdated/Missing DLCs: $($out.Count)." 'OK'
} catch { _Log "Integrity check failed: $($_.Exception.Message)" 'WARN' }
}
@@ -217,10 +316,17 @@ $INIT_SCRIPT = [scriptblock]::Create($BG_COMMON.ToString() + @'
$_Q.Enqueue([pscustomobject]@{ t='dot_gh'; ok=$ghOk })
$srvOk = $false
if ($sd -and $sd.url) {
if ($_SERVERURL -and $_ALTLAUNCHERS.Count -gt 0) {
_Log "Checking server connection..."
try { _Http "https://$($sd.url)" | Out-Null; $srvOk=$true; _Log "Server: reachable." 'OK' }
catch { _Log "Server unreachable: $($_.Exception.Message)" 'WARN' }
try {
$req = [System.Net.WebRequest]::Create("https://$_SERVERURL/unlocker/$($_ALTLAUNCHERS[0])")
$req.Method = "HEAD"
$req.Timeout = 3000
$resp = $req.GetResponse()
$resp.Close()
$srvOk = $true
_Log "Server: reachable." 'OK'
} catch { _Log "Server unreachable: $($_.Exception.Message)" 'WARN' }
}
$_Q.Enqueue([pscustomobject]@{ t='dot_srv'; ok=$srvOk })
_Log "Initialization complete." 'OK'
@@ -274,39 +380,80 @@ $INSTALL_SCRIPT = [scriptblock]::Create($BG_COMMON.ToString() + @'
New-Item -ItemType Directory -Path (Join-Path $_GAMEPATH 'dlc') -Force | Out-Null
_Log "⬇ Syncing creamapi_steam_files from repo..."
_Log "⬇ Syncing creamapi files from repo..."
$steamCache = Join-Path $_CACHE_DIR 'creamapi_steam_files'
$launchCache = Join-Path $_CACHE_DIR 'creamapi_launcher_files'
$fallbackFiles = @{
'creamapi_steam_files' = @('Emulator64.dll', 'LinkNeverDie_Com_64.dll', 'SWLoader.txt', 'SWconfig.ini', 'cream_api.ini', 'steam_api64_org_game.dll', 'steam_api64_org_launcher.dll')
'creamapi_launcher_files' = @('cream_api.ini', 'sdkencryptedappticket64.dll', 'steam_api64.dll', 'steam_api64_o.dll')
}
foreach ($pair in @(@{sub='creamapi_steam_files';dest=$steamCache},@{sub='creamapi_launcher_files';dest=$launchCache})) {
New-Item -ItemType Directory -Path $pair.dest -Force | Out-Null
$wc2 = [System.Net.WebClient]::new(); $wc2.Headers.Add('User-Agent','StellarisDLCUnlocker-PS/1.0')
$useJsDelivr = $false
try {
$wc2 = [System.Net.WebClient]::new(); $wc2.Headers.Add('User-Agent','StellarisDLCUnlocker-PS/1.0')
$items = ($wc2.DownloadString("$_ORIGIN_API/$($pair.sub)") | ConvertFrom-Json)
foreach ($item in $items) {
if ($item.type -eq 'file') {
$dest2 = Join-Path $pair.dest $item.name
$hashFile = "$dest2.sha"
$needsUpdate = $true
if (Test-Path $dest2) {
if ((Test-Path $hashFile) -and ((Get-Content $hashFile -Raw).Trim() -eq $item.sha)) {
$needsUpdate = $false
}
}
if ($needsUpdate) {
_Log " Downloading: $($item.name)"
if (-not ((Test-Path $dest2) -and (Test-Path $hashFile) -and ((Get-Content $hashFile -Raw).Trim() -eq $item.sha))) {
$wc3 = [System.Net.WebClient]::new(); $wc3.Headers.Add('User-Agent','StellarisDLCUnlocker-PS/1.0')
$wc3.DownloadFile($item.download_url, $dest2); $wc3.Dispose()
$item.sha | Out-File -FilePath $hashFile -Encoding ascii -Force
_Log " OK: $($item.name)" 'OK'
} else {
_Log " Cached (Up to date): $($item.name)"
_Log " OK (GitHub): $($item.name)" 'OK'
}
}
}
} finally { $wc2.Dispose() }
$wc2.Dispose()
} catch {
_Log " GitHub API timeout/error, switching to jsDelivr for $($pair.sub)..." 'WARN'
$useJsDelivr = $true
}
if ($useJsDelivr) {
$baseUrl = "https://cdn.jsdelivr.net/gh/seuyh/stellaris-dlc-unlocker@main/$($pair.sub)"
$manifest = $null
try {
$wc4 = [System.Net.WebClient]::new()
$wc4.Headers.Add('User-Agent','StellarisDLCUnlocker-PS/1.0')
$manifest = ($wc4.DownloadString("$baseUrl/manifest.json") | ConvertFrom-Json)
$wc4.Dispose()
} catch {
_Log " No manifest.json via jsDelivr for $($pair.sub), will re-download all." 'WARN'
}
foreach ($f in $fallbackFiles[$pair.sub]) {
$dest2 = Join-Path $pair.dest $f
$hashFile = "$dest2.sha"
$remoteSha = if ($manifest) { ($manifest | Where-Object { $_.name -eq $f }).sha } else { $null }
$needDownload = $true
if ($remoteSha -and (Test-Path $dest2) -and (Test-Path $hashFile) -and
((Get-Content $hashFile -Raw).Trim() -eq $remoteSha)) {
$needDownload = $false
}
if ($needDownload) {
try {
$wc3 = [System.Net.WebClient]::new()
$wc3.Headers.Add('User-Agent','StellarisDLCUnlocker-PS/1.0')
$wc3.DownloadFile("$baseUrl/$f", $dest2)
$wc3.Dispose()
if ($remoteSha) { $remoteSha | Out-File -FilePath $hashFile -Encoding ascii -Force }
_Log " OK (jsDelivr): $f" 'OK'
} catch {
_Log " Failed to download via jsDelivr: $f" 'ERROR'
}
} else {
_Log " Cached (jsDelivr): $f" 'OK'
}
}
}
}
_Log "⬇ Syncing creamapi_launcher_files from repo... done."
@@ -314,29 +461,41 @@ $INSTALL_SCRIPT = [scriptblock]::Create($BG_COMMON.ToString() + @'
foreach ($iniPath in @((Join-Path $steamCache 'cream_api.ini'),(Join-Path $launchCache 'cream_api.ini'))) {
if (-not (Test-Path $iniPath)) { continue }
try {
$req = [System.Net.HttpWebRequest]::Create("$_STEAMCMD_API/$_APPID"); $req.Timeout = 3000
$data = ([System.IO.StreamReader]::new($req.GetResponse().GetResponseStream()).ReadToEnd() | ConvertFrom-Json)
$req = [System.Net.HttpWebRequest]::Create("$_STEAMCMD_API/$_APPID")
$req.Timeout = 2500
$req.ReadWriteTimeout = 2500
$resp = $req.GetResponse()
$stream = $resp.GetResponseStream()
$reader = [System.IO.StreamReader]::new($stream)
$data = ($reader.ReadToEnd() | ConvertFrom-Json)
$reader.Dispose(); $stream.Dispose(); $resp.Dispose()
$csv = $data.data."$_APPID".extended.listofdlc
if ($csv) {
$exist = Get-Content $iniPath -Raw
$fs = [System.IO.File]::Open($iniPath,[System.IO.FileMode]::Append,[System.IO.FileAccess]::Write)
$sw = [System.IO.StreamWriter]::new($fs,[System.Text.Encoding]::UTF8); $added = 0
if ($exist -and -not $exist.EndsWith("`n")) {
$sw.WriteLine()
if ($exist -and -not $exist.EndsWith("`n")) { $sw.WriteLine() }
foreach ($id in ($csv -split ',')) {
$id = $id.Trim(); if (-not $id -or $exist -match $id) { continue }
$name = $id
try {
$req2 = [System.Net.HttpWebRequest]::Create("$_STEAMCMD_API/$id")
$req2.Timeout = 1500; $req2.ReadWriteTimeout = 1500
$resp2 = $req2.GetResponse()
$reader2 = [System.IO.StreamReader]::new($resp2.GetResponseStream())
$name = ($reader2.ReadToEnd() | ConvertFrom-Json).data."$id".common.name
$reader2.Dispose(); $resp2.Close()
} catch {}
$sw.WriteLine("$id = $name"); $added++
}
try {
foreach ($id in ($csv -split ',')) {
$id = $id.Trim(); if (-not $id -or $exist -match $id) { continue }
$name = try {
$req2 = [System.Net.HttpWebRequest]::Create("$_STEAMCMD_API/$id"); $req2.Timeout = 3000
([System.IO.StreamReader]::new($req2.GetResponse().GetResponseStream()).ReadToEnd() | ConvertFrom-Json).data."$id".common.name
} catch { $id }
$sw.WriteLine("$id = $name"); $added++
}
} finally { $sw.Dispose(); $fs.Dispose() }
_Log " cream_api.ini updated: +$added DLC." 'OK'
$sw.Dispose(); $fs.Dispose()
if ($added -gt 0) { _Log " cream_api.ini updated: +$added DLC." 'OK' }
}
} catch { _Log " SteamCMD unavailable: $($_.Exception.Message)" 'WARN' }
} catch {
_Log " SteamCMD is currently unavailable, skipped update." 'WARN'
}
}
$dlcDir = Join-Path $_GAMEPATH 'dlc'; $queue = @(); $total = 0
@@ -413,15 +572,22 @@ $INSTALL_SCRIPT = [scriptblock]::Create($BG_COMMON.ToString() + @'
} else { _Log " Launcher reinstall skipped." }
_Log "📋 Patching launcher folders..."
$launcherBase = _GetLauncherBase
$launcherBase = if (-not [string]::IsNullOrWhiteSpace($_LAUNCHERPATH) -and (Test-Path $_LAUNCHERPATH)) {
$_LAUNCHERPATH
} else {
_Log " Launcher path not set or not found, trying auto-detect..." 'WARN'
_GetLauncherBase
}
_Log " Launcher base: $launcherBase"
if (Test-Path $launcherBase) {
$lFolders = @(Get-ChildItem $launcherBase -Directory -ErrorAction SilentlyContinue | Where-Object { $_.Name -like 'launcher*' })
_Log " Found $($lFolders.Count) launcher folder(s)."
foreach ($lf in $lFolders) {
_Log " Processing: $($lf.Name)"
$xd = Join-Path $lf.FullName 'xdelta3.exe'
if (Test-Path $xd) { Remove-Item $xd -Force; _Log " Removed xdelta3.exe (auto-update disabled)." 'OK' }
if ($_NO_UPDATE) {
$xd = Join-Path $lf.FullName 'xdelta3.exe'
if (Test-Path $xd) { Remove-Item $xd -Force; _Log " Removed xdelta3.exe (auto-update disabled)." 'OK' }
}
$t1 = Join-Path $lf.FullName 'resources\app.asar.unpacked\node_modules\greenworks\lib'
$t2 = Join-Path $lf.FullName 'resources\app\dist\main'
$tgt = if (Test-Path $t1) { $t1 } elseif (Test-Path $t2) { $t2 } else { $null }
@@ -452,7 +618,7 @@ $INSTALL_SCRIPT = [scriptblock]::Create($BG_COMMON.ToString() + @'
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Stellaris DLC Unlocker"
Width="720" Height="820" MinWidth="600" MinHeight="680"
Width="720" Height="900" MinWidth="600" MinHeight="820"
WindowStartupLocation="CenterScreen" Background="#12121f" FontFamily="Segoe UI">
<Window.Resources>
<Style x:Key="Card" TargetType="Border">
@@ -506,6 +672,73 @@ $INSTALL_SCRIPT = [scriptblock]::Create($BG_COMMON.ToString() + @'
<Style x:Key="LI" TargetType="ListBoxItem">
<Setter Property="Padding" Value="0,1"/><Setter Property="Background" Value="Transparent"/><Setter Property="BorderThickness" Value="0"/>
</Style>
<Style x:Key="CBx" TargetType="ComboBox">
<Setter Property="Background" Value="#1c1c30"/>
<Setter Property="Foreground" Value="#c8d6f0"/>
<Setter Property="BorderBrush" Value="#2a2a45"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Padding" Value="6,4"/>
<Setter Property="FontSize" Value="11"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="ComboBoxItem">
<Setter Property="Background" Value="#1c1c30"/>
<Setter Property="Foreground" Value="#c8d6f0"/>
<Setter Property="Padding" Value="8,5"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#2a2a45"/>
</Trigger>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="#1a5a9a"/>
<Setter Property="Foreground" Value="White"/>
</Trigger>
</Style.Triggers>
</Style>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ComboBox">
<Grid>
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="4">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="24"/>
</Grid.ColumnDefinitions>
<ContentPresenter Grid.Column="0"
Content="{TemplateBinding SelectionBoxItem}"
ContentTemplate="{TemplateBinding SelectionBoxItemTemplate}"
Margin="{TemplateBinding Padding}"
VerticalAlignment="Center"/>
<TextBlock Grid.Column="1" Text="▾" Foreground="#55557a"
VerticalAlignment="Center" HorizontalAlignment="Center" FontSize="10"/>
</Grid>
</Border>
<Popup x:Name="PART_Popup" Placement="Bottom"
AllowsTransparency="True" Focusable="False"
IsOpen="{TemplateBinding IsDropDownOpen}"
PlacementTarget="{Binding RelativeSource={RelativeSource TemplatedParent}}">
<Border Background="#1c1c30" BorderBrush="#2a2a45" BorderThickness="1" CornerRadius="4"
MinWidth="{Binding ActualWidth, RelativeSource={RelativeSource AncestorType=ComboBox}}"
HorizontalAlignment="Right">
<ScrollViewer MaxHeight="200">
<ItemsPresenter/>
</ScrollViewer>
</Border>
</Popup>
<ToggleButton Focusable="False" IsChecked="{Binding IsDropDownOpen, RelativeSource={RelativeSource TemplatedParent}}"
Opacity="0"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid Margin="18">
<Grid.RowDefinitions>
@@ -559,7 +792,16 @@ $INSTALL_SCRIPT = [scriptblock]::Create($BG_COMMON.ToString() + @'
<TextBlock x:Name="LblOpts" Style="{StaticResource SL}"/>
<CheckBox x:Name="ChkFull" Style="{StaticResource CK}"/>
<CheckBox x:Name="ChkSkip" Style="{StaticResource CK}"/>
<CheckBox x:Name="ChkAlt" Style="{StaticResource CK}"/>
<CheckBox x:Name="ChkNoUpdate" Style="{StaticResource CK}" IsChecked="True"/>
<TextBlock x:Name="LblLauncher" Style="{StaticResource SL}" Margin="0,10,0,0"/>
<ComboBox x:Name="CmbLauncher" Style="{StaticResource CBx}"/>
<TextBlock x:Name="LblLauncherPath" Style="{StaticResource SL}" Margin="0,10,0,0"/>
<Grid>
<Grid.ColumnDefinitions><ColumnDefinition Width="*"/><ColumnDefinition Width="Auto"/></Grid.ColumnDefinitions>
<TextBox x:Name="LauncherPathBox" Grid.Column="0" Background="#12121f" BorderBrush="#2a2a45" BorderThickness="1"
Foreground="#c8d6f0" FontSize="11" Padding="8,6" VerticalContentAlignment="Center"/>
<Button x:Name="LauncherBrowseBtn" Grid.Column="1" Style="{StaticResource BGh}" Margin="8,0,0,0" FontSize="11"/>
</Grid>
</StackPanel>
</Border>
@@ -615,12 +857,17 @@ $INSTALL_SCRIPT = [scriptblock]::Create($BG_COMMON.ToString() + @'
$reader = [System.Xml.XmlNodeReader]::new($xaml)
$window = [Windows.Markup.XamlReader]::Load($reader)
$pathBox = $window.FindName('PathBox'); $browseBtn = $window.FindName('BrowseBtn')
$lblLauncherPath = $window.FindName('LblLauncherPath')
$launcherPathBox = $window.FindName('LauncherPathBox')
$launcherBrowseBtn = $window.FindName('LauncherBrowseBtn')
$chkNoUpdate = $window.FindName('ChkNoUpdate')
$statusLbl = $window.FindName('StatusLbl'); $logBox = $window.FindName('LogBox')
$installBtn = $window.FindName('InstallBtn');$launchBtn = $window.FindName('LaunchBtn')
$refreshBtn = $window.FindName('RefreshBtn')
$pBar = $window.FindName('PBar'); $pBarFile = $window.FindName('PBarFile')
$speedLbl = $window.FindName('SpeedLbl'); $curLbl = $window.FindName('CurLbl')
$chkFull = $window.FindName('ChkFull'); $chkSkip = $window.FindName('ChkSkip'); $chkAlt=$window.FindName('ChkAlt')
$chkFull = $window.FindName('ChkFull'); $chkSkip = $window.FindName('ChkSkip')
$lblLauncher = $window.FindName('LblLauncher'); $cmbLauncher = $window.FindName('CmbLauncher')
$dotGH = $window.FindName('DotGH'); $dotSrv = $window.FindName('DotSrv')
$dlcList = $window.FindName('DlcList')
$btnEn=$window.FindName('BtnEn'); $btnRu=$window.FindName('BtnRu'); $btnZh=$window.FindName('BtnZh')
@@ -646,7 +893,6 @@ $drainTimer.Add_Tick({
'log' { Add-LogItem $item.msg $item.lvl; Write-Log ($item.msg -replace '^\[.+?\] ','') $item.lvl }
'dot_gh' { $dotGH.Fill = if ($item.ok) { [System.Windows.Media.Brushes]::LimeGreen } else { [System.Windows.Media.Brushes]::Crimson } }
'dot_srv' { $dotSrv.Fill = if ($item.ok) { [System.Windows.Media.Brushes]::LimeGreen } else { [System.Windows.Media.Brushes]::Crimson } }
'server_data' { $script:serverUrl=$item.url; $script:altLauncher=$item.alt }
'dlc_data' { $script:dlcData=$item.data; Refresh-DlcList }
'outdated' { $script:outdatedFolders=$item.folders; Refresh-DlcList }
'init_done' { Update-UI; Refresh-DlcList }
@@ -673,7 +919,16 @@ function Set-LangActive([string]$lang) {
function Apply-UIText {
$titleLbl.Text=T 'title'; $lblPath.Text=T 'path_label'; $browseBtn.Content=T 'browse'
$lblStatus.Text=T 'status_label'; $lblOpts.Text=T 'options_label'
$chkFull.Content=T 'chk_full'; $chkSkip.Content=T 'chk_skip'; $chkAlt.Content=T 'chk_alt'
$chkFull.Content=T 'chk_full'; $chkSkip.Content=T 'chk_skip'
$chkNoUpdate.Content = T 'chk_no_update'
$lblLauncher.Text=T 'lbl_launcher'
$lblLauncherPath.Text = T 'lbl_launcher_path'
$launcherBrowseBtn.Content = T 'browse'
$sel = $cmbLauncher.SelectedIndex
$cmbLauncher.Items.Clear()
[void]$cmbLauncher.Items.Add((T 'launcher_default'))
foreach ($alt in $ALT_LAUNCHERS) { [void]$cmbLauncher.Items.Add($alt) }
if ($sel -ge 0) { $cmbLauncher.SelectedIndex = $sel } else { $cmbLauncher.SelectedIndex = 0 }
$lblDlc.Text=T 'dlc_label'; $installBtn.Content=T 'install_btn'; $launchBtn.Content=T 'launch_btn'
$refreshBtn.ToolTip=T 'refresh_tip'
$legOk.Text=T 'dlc_ok'; $legOld.Text=T 'dlc_old'; $legMiss.Text=T 'dlc_missing'
@@ -700,7 +955,7 @@ function Refresh-DlcList {
}
function Update-UI {
$st = Get-InstallStatus $pathBox.Text.Trim()
$ready = ($null -ne $script:dlcData -and $null -ne $script:serverUrl)
$ready = ($null -ne $script:dlcData)
switch ($st) {
'installed' { $statusLbl.Text=T 'status_installed'; $statusLbl.Foreground='#27ae60'; $installBtn.IsEnabled=$ready }
'not_installed' { $statusLbl.Text=T 'status_not_installed'; $statusLbl.Foreground='#f0a030'; $installBtn.IsEnabled=$ready }
@@ -714,14 +969,27 @@ $window.Add_Loaded({
$steam = Find-SteamPath
$sp = if ($steam) { Find-StellarisPath $steam } else { $null }
if (-not [string]::IsNullOrWhiteSpace($sp)) { $pathBox.Text = $sp }
$detectedLauncher = try {
$reg = (Get-ItemProperty 'HKCU:\Software\Paradox Interactive\Paradox Launcher v2' -ErrorAction Stop).LauncherInstallation
if ($reg -and (Test-Path $reg)) { $reg }
else { "$env:LOCALAPPDATA\Programs\Paradox Interactive\launcher" }
} catch { "$env:LOCALAPPDATA\Programs\Paradox Interactive\launcher" }
if (Test-Path $detectedLauncher) { $launcherPathBox.Text = $detectedLauncher }
$launcherBrowseBtn.Add_Click({
$path = [Picker.FolderDialog]::Pick('Select Paradox Launcher folder', $launcherPathBox.Text)
if ($path) { $launcherPathBox.Text = $path }
})
$statusLbl.Text=T 'status_loading'; $installBtn.IsEnabled=$false
Start-PSRunspace $INIT_SCRIPT @{
_Q = $script:Q
_GDLC = $GITHUB_DLC_URL; _GDU = $GITHUB_DATA_URL
_STEAMCMD_API=$STEAMCMD_API; _APPID = $STELLARIS_APP_ID
_ORIGIN_API= $ORIGIN_API; _CACHE_DIR= $CACHE_DIR
_GAMEPATH = if (-not [string]::IsNullOrWhiteSpace($sp)) { $sp } else { '' }
_Q = $script:Q
_GDLC = $GITHUB_DLC_URL; _GDLC_FALL = $JSDELIVR_DLC_URL;
_GHASH = $GITHUB_HASHES_URL; _GHASH_FALL = $JSDELIVR_HASHES_URL
_STEAMCMD_API= $STEAMCMD_API; _APPID = $STELLARIS_APP_ID
_ORIGIN_API = $ORIGIN_API; _CACHE_DIR = $CACHE_DIR
_SERVERURL = $SERVER_URL
_ALTLAUNCHERS= $ALT_LAUNCHERS
_GAMEPATH = if (-not [string]::IsNullOrWhiteSpace($sp)) { $sp } else { '' }
}
})
@@ -730,8 +998,8 @@ $btnRu.Add_Click({ Apply-Lang 'ru'; Refresh-DlcList })
$btnZh.Add_Click({ Apply-Lang 'zh'; Refresh-DlcList })
$browseBtn.Add_Click({
$dlg=[System.Windows.Forms.FolderBrowserDialog]::new(); $dlg.Description='Select Stellaris folder'
if ($dlg.ShowDialog() -eq 'OK') { $pathBox.Text=$dlg.SelectedPath; Update-UI; Refresh-DlcList }
$path = [Picker.FolderDialog]::Pick('Select Stellaris folder', $pathBox.Text)
if ($path) { $pathBox.Text = $path; Update-UI; Refresh-DlcList }
})
$refreshBtn.Add_Click({ Update-UI; Refresh-DlcList })
$pathBox.Add_TextChanged({ Update-UI })
@@ -744,11 +1012,17 @@ $chkFull.Add_Checked({
$chkFull.Add_Unchecked({ $chkSkip.IsEnabled=$true })
$chkSkip.Add_Checked({
$chkFull.IsChecked=$false; $chkFull.IsEnabled=$false
$chkAlt.IsChecked=$false; $chkAlt.IsEnabled=$false
$cmbLauncher.IsEnabled=$false
})
$chkSkip.Add_Unchecked({ $chkFull.IsEnabled=$true; $cmbLauncher.IsEnabled=$true })
$chkNoUpdate.Add_Unchecked({
$r = [System.Windows.MessageBox]::Show(
(T 'confirm_no_update'), (T 'warn_title'),
[System.Windows.MessageBoxButton]::YesNo,
[System.Windows.MessageBoxImage]::Warning)
if ($r -ne 'Yes') { $chkNoUpdate.IsChecked = $true }
})
$chkSkip.Add_Unchecked({ $chkFull.IsEnabled=$true; $chkAlt.IsEnabled=$true })
$chkAlt.Add_Checked({ $chkSkip.IsChecked=$false; $chkSkip.IsEnabled=$false })
$chkAlt.Add_Unchecked({ $chkSkip.IsEnabled=$true })
$launchBtn.Add_Click({
Start-Process "steam://rungameid/$STELLARIS_APP_ID"
@@ -760,22 +1034,26 @@ $installBtn.Add_Click({
if (-not (Test-Path (Join-Path $sp 'stellaris.exe'))) {
[System.Windows.MessageBox]::Show((T 'err_no_exe'),(T 'err_title'),[System.Windows.MessageBoxButton]::OK,[System.Windows.MessageBoxImage]::Warning) | Out-Null; return
}
if (-not $script:serverUrl -or -not $script:dlcData) {
if (-not $script:dlcData) {
[System.Windows.MessageBox]::Show((T 'err_loading'),(T 'wait_title'),[System.Windows.MessageBoxButton]::OK,[System.Windows.MessageBoxImage]::Information) | Out-Null; return
}
$installBtn.IsEnabled=$false; $launchBtn.Visibility=[System.Windows.Visibility]::Collapsed
$pBar.Value=0; $pBarFile.Value=0; $speedLbl.Text=''; $curLbl.Text=''
$selectedAlt = if ($cmbLauncher.SelectedIndex -gt 0) { $ALT_LAUNCHERS[$cmbLauncher.SelectedIndex - 1] } else { $null }
Start-PSRunspace $INSTALL_SCRIPT @{
_Q = $script:Q
_GAMEPATH = $sp
_LAUNCHERPATH = $launcherPathBox.Text.Trim()
_DLCDATA = $script:dlcData
_SERVERURL = $script:serverUrl
_ALTNAME = $script:altLauncher
_SERVERURL = $SERVER_URL
_ALTNAME = $selectedAlt
_OUTDATED = $script:outdatedFolders
_FULL = [bool]$chkFull.IsChecked
_SKIP = [bool]$chkSkip.IsChecked
_ALT = [bool]$chkAlt.IsChecked
_NO_UPDATE = [bool]$chkNoUpdate.IsChecked
_ALT = ($null -ne $selectedAlt)
_CACHE_DIR = $CACHE_DIR
_ORIGIN_API = $ORIGIN_API
_STEAMCMD_API = $STEAMCMD_API
@@ -784,5 +1062,6 @@ $installBtn.Add_Click({
}
})
[System.Windows.Media.RenderOptions]::ProcessRenderMode = [System.Windows.Interop.RenderMode]::SoftwareOnly
[void]$window.ShowDialog()
$drainTimer.Stop()
+749
View File
@@ -0,0 +1,749 @@
#!/bin/bash
set -uo pipefail
APPID="281990"
APP_DIR="$HOME/.local/share/StellarisDLCUnlocker"
CACHE_DIR="$APP_DIR/cache"
LOG_FILE="$APP_DIR/unlocker.log"
CONFIG_FILE="$APP_DIR/config.ini"
REPO_GITHUB_RAW="https://raw.githubusercontent.com/seuyh/stellaris-dlc-unlocker/main"
REPO_JSDELIVR="https://cdn.jsdelivr.net/gh/seuyh/stellaris-dlc-unlocker@main"
GITHUB_DLC_URL="$REPO_GITHUB_RAW/dlc_data.json"
JSDELIVR_DLC_URL="$REPO_JSDELIVR/dlc_data.json"
GITHUB_HASHES_URL="$REPO_GITHUB_RAW/hashes.json"
JSDELIVR_HASHES_URL="$REPO_JSDELIVR/hashes.json"
SERVER_URL="pub-0f87be5fdd68492c8328b66998eb46ad.r2.dev"
STEAMCMD_API="https://api.steamcmd.net/v1/info"
CREAMLINUX_SUBDIR="creamlinux"
CREAMLINUX_FILES=(cream.sh lib32Creamlinux.so lib64Creamlinux.so cream_api.ini)
mkdir -p "$APP_DIR" "$CACHE_DIR"
C_RESET=$'\e[0m'; C_BOLD=$'\e[1m'; C_DIM=$'\e[2m'
C_RED=$'\e[31m'; C_GREEN=$'\e[32m'; C_YELLOW=$'\e[33m'
C_BLUE=$'\e[34m'; C_MAGENTA=$'\e[35m'; C_CYAN=$'\e[36m'; C_WHITE=$'\e[97m'
LANGUAGE="en"
t() {
local key="$1"
case "$LANGUAGE" in
ru) _t_ru "$key" ;;
zh) _t_zh "$key" ;;
*) _t_en "$key" ;;
esac
}
_t_en() {
case "$1" in
title) echo "Stellaris DLC Unlocker — Linux Edition" ;;
menu_header) echo "MAIN MENU" ;;
m_status) echo "Status & paths" ;;
m_install) echo "Install / Update DLC unlocker" ;;
m_steam_launch) echo "Patch Steam launch options" ;;
m_lang) echo "Change language" ;;
m_log) echo "Show log" ;;
m_exit) echo "Exit" ;;
choose) echo "Choose an option: " ;;
detect_steam) echo "Detecting Steam installation..." ;;
steam_found) echo "Steam found:" ;;
steam_not_found) echo "Steam installation not found." ;;
detect_game) echo "Detecting Stellaris installation..." ;;
game_found) echo "Stellaris found:" ;;
game_not_found) echo "Stellaris installation not found." ;;
native_build) echo "Native Linux build detected — CreamLinux is compatible." ;;
not_native) echo "Native Linux build NOT detected (no 'stellaris' ELF binary). CreamLinux requires the native build, not Proton/Wine." ;;
downloading) echo "Downloading" ;;
fetching_dlc_list) echo "Fetching DLC list..." ;;
dlc_list_ok) echo "DLC list loaded." ;;
dlc_list_fail) echo "Failed to fetch DLC list from GitHub and jsDelivr." ;;
fetching_creamlinux) echo "Downloading CreamLinux..." ;;
creamlinux_ok) echo "CreamLinux files ready." ;;
copying_files) echo "Copying CreamLinux files into game folder..." ;;
checking_hashes) echo "Checking file integrity (hashes.json)..." ;;
to_download) echo "DLC archives to download:" ;;
unpacking) echo "Unpacking archives..." ;;
updating_ini) echo "Updating cream_api.ini via SteamCMD API..." ;;
ini_updated) echo "cream_api.ini updated, added DLC:" ;;
ini_skip) echo "SteamCMD API unavailable, skipped." ;;
install_done) echo "Installation complete!" ;;
patch_launch_header) echo "Patching Steam launch options" ;;
patch_userdata_not_found) echo "Could not find Steam userdata folder." ;;
patch_localconfig_not_found) echo "localconfig.vdf not found for any user." ;;
patch_backup) echo "Backup created:" ;;
patch_done) echo "Launch options set to: sh ./cream.sh %command%" ;;
patch_already) echo "Launch options already set correctly." ;;
patch_steam_running_warn) echo "WARNING: Steam appears to be running. Close Steam fully before/after patching, otherwise it may overwrite the change." ;;
press_enter) echo "Press Enter to continue..." ;;
enter_game_path) echo "Enter Stellaris game path manually (or press Enter to skip): " ;;
invalid_choice) echo "Invalid choice." ;;
lang_set) echo "Language set to English." ;;
log_empty) echo "Log is empty." ;;
confirm_install) echo "Proceed with installation? [y/N]: " ;;
cancelled) echo "Cancelled." ;;
no_dlc_to_download) echo "All DLC are up to date." ;;
flatpak_steam) echo "(Flatpak)" ;;
*) echo "$1" ;;
esac
}
_t_ru() {
case "$1" in
title) echo "Stellaris DLC Unlocker — версия для Linux" ;;
menu_header) echo "ГЛАВНОЕ МЕНЮ" ;;
m_status) echo "Статус и пути" ;;
m_install) echo "Установить / обновить разблокировщик DLC" ;;
m_steam_launch) echo "Прописать параметры запуска в Steam" ;;
m_lang) echo "Сменить язык" ;;
m_log) echo "Показать лог" ;;
m_exit) echo "Выход" ;;
choose) echo "Выберите пункт: " ;;
detect_steam) echo "Поиск установки Steam..." ;;
steam_found) echo "Steam найден:" ;;
steam_not_found) echo "Установка Steam не найдена." ;;
detect_game) echo "Поиск установки Stellaris..." ;;
game_found) echo "Stellaris найден:" ;;
game_not_found) echo "Установка Stellaris не найдена." ;;
native_build) echo "Обнаружена нативная Linux-версия — CreamLinux подходит." ;;
not_native) echo "Нативная Linux-версия не обнаружена (нет ELF-файла 'stellaris'). CreamLinux работает только с нативной версией, не с Proton/Wine." ;;
downloading) echo "Скачивание" ;;
fetching_dlc_list) echo "Получение списка DLC..." ;;
dlc_list_ok) echo "Список DLC загружен." ;;
dlc_list_fail) echo "Не удалось получить список DLC ни с GitHub, ни с jsDelivr." ;;
fetching_creamlinux) echo "Скачивание CreamLinux..." ;;
creamlinux_ok) echo "Файлы CreamLinux готовы." ;;
copying_files) echo "Копирование файлов CreamLinux в папку игры..." ;;
checking_hashes) echo "Проверка целостности файлов (hashes.json)..." ;;
to_download) echo "Архивов DLC к скачиванию:" ;;
unpacking) echo "Распаковка архивов..." ;;
updating_ini) echo "Обновление cream_api.ini через SteamCMD API..." ;;
ini_updated) echo "cream_api.ini обновлён, добавлено DLC:" ;;
ini_skip) echo "SteamCMD API недоступен, пропущено." ;;
install_done) echo "Установка завершена!" ;;
patch_launch_header) echo "Прописывание параметров запуска Steam" ;;
patch_userdata_not_found) echo "Не удалось найти папку userdata Steam." ;;
patch_localconfig_not_found) echo "Файл localconfig.vdf не найден ни для одного пользователя." ;;
patch_backup) echo "Создана резервная копия:" ;;
patch_done) echo "Параметры запуска установлены: sh ./cream.sh %command%" ;;
patch_already) echo "Параметры запуска уже выставлены верно." ;;
patch_steam_running_warn) echo "ВНИМАНИЕ: похоже, Steam запущен. Полностью закройте Steam до/после патчинга, иначе он может перезаписать изменение." ;;
press_enter) echo "Нажмите Enter для продолжения..." ;;
enter_game_path) echo "Введите путь к игре Stellaris вручную (или Enter, чтобы пропустить): " ;;
invalid_choice) echo "Неверный выбор." ;;
lang_set) echo "Язык переключён на русский." ;;
log_empty) echo "Лог пуст." ;;
confirm_install) echo "Продолжить установку? [y/N]: " ;;
cancelled) echo "Отменено." ;;
no_dlc_to_download) echo "Все DLC актуальны." ;;
flatpak_steam) echo "(Flatpak)" ;;
*) echo "$1" ;;
esac
}
_t_zh() {
case "$1" in
title) echo "Stellaris DLC 解锁器 — Linux 版" ;;
menu_header) echo "主菜单" ;;
m_status) echo "状态与路径" ;;
m_install) echo "安装 / 更新 DLC 解锁器" ;;
m_steam_launch) echo "设置 Steam 启动选项" ;;
m_lang) echo "切换语言" ;;
m_log) echo "查看日志" ;;
m_exit) echo "退出" ;;
choose) echo "请选择: " ;;
detect_steam) echo "正在检测 Steam 安装..." ;;
steam_found) echo "已找到 Steam:" ;;
steam_not_found) echo "未找到 Steam 安装。" ;;
detect_game) echo "正在检测 Stellaris 安装..." ;;
game_found) echo "已找到 Stellaris:" ;;
game_not_found) echo "未找到 Stellaris 安装。" ;;
native_build) echo "检测到原生 Linux 版本 — CreamLinux 兼容。" ;;
not_native) echo "未检测到原生 Linux 版本(缺少 'stellaris' ELF 文件)。CreamLinux 仅支持原生版本,不支持 Proton/Wine。" ;;
downloading) echo "下载中" ;;
fetching_dlc_list) echo "正在获取 DLC 列表..." ;;
dlc_list_ok) echo "DLC 列表已加载。" ;;
dlc_list_fail) echo "从 GitHub 和 jsDelivr 获取 DLC 列表均失败。" ;;
fetching_creamlinux) echo "正在下载 CreamLinux..." ;;
creamlinux_ok) echo "CreamLinux 文件已就绪。" ;;
copying_files) echo "正在将 CreamLinux 文件复制到游戏目录..." ;;
checking_hashes) echo "正在校验文件完整性 (hashes.json)..." ;;
to_download) echo "待下载的 DLC 压缩包:" ;;
unpacking) echo "正在解压..." ;;
updating_ini) echo "正在通过 SteamCMD API 更新 cream_api.ini..." ;;
ini_updated) echo "cream_api.ini 已更新,新增 DLC:" ;;
ini_skip) echo "SteamCMD API 不可用,已跳过。" ;;
install_done) echo "安装完成!" ;;
patch_launch_header) echo "设置 Steam 启动选项" ;;
patch_userdata_not_found) echo "未找到 Steam userdata 目录。" ;;
patch_localconfig_not_found) echo "未找到任何用户的 localconfig.vdf。" ;;
patch_backup) echo "已创建备份:" ;;
patch_done) echo "启动选项已设置为: sh ./cream.sh %command%" ;;
patch_already) echo "启动选项已正确设置。" ;;
patch_steam_running_warn) echo "警告: Steam 似乎正在运行。请在修改前后完全关闭 Steam,否则更改可能被覆盖。" ;;
press_enter) echo "按 Enter 继续..." ;;
enter_game_path) echo "请手动输入 Stellaris 游戏路径(直接按 Enter 跳过): " ;;
invalid_choice) echo "无效选择。" ;;
lang_set) echo "语言已切换为中文。" ;;
log_empty) echo "日志为空。" ;;
confirm_install) echo "是否继续安装?[y/N]: " ;;
cancelled) echo "已取消。" ;;
no_dlc_to_download) echo "所有 DLC 均为最新。" ;;
flatpak_steam) echo "(Flatpak)" ;;
*) echo "$1" ;;
esac
}
log() {
local level="$1"; shift
local msg="$*"
local color="$C_WHITE"
case "$level" in
OK) color="$C_GREEN" ;;
WARN) color="$C_YELLOW" ;;
ERROR) color="$C_RED" ;;
INFO) color="$C_CYAN" ;;
esac
echo -e "${color}[$level]${C_RESET} $msg"
echo "$(date '+%Y-%m-%d %H:%M:%S') [$level] $msg" >> "$LOG_FILE"
}
hr() { printf "${C_DIM}%s${C_RESET}\n" "------------------------------------------------------------"; }
header() {
clear
echo -e "${C_BOLD}${C_MAGENTA}=============================================================${C_RESET}"
echo -e "${C_BOLD}${C_WHITE} $(t title)${C_RESET}"
echo -e "${C_BOLD}${C_MAGENTA}=============================================================${C_RESET}"
echo
}
pause() {
echo
ask "$(t press_enter)" _dummy
}
progress_bar() {
local current="$1" total="$2" label="$3"
local width=30
local pct=0
[ "$total" -gt 0 ] && pct=$(( current * 100 / total ))
local filled=$(( pct * width / 100 ))
local empty=$(( width - filled ))
printf "\r${C_CYAN}[%-${width}s]${C_RESET} %3d%% %s" \
"$(printf '#%.0s' $(seq 1 $filled) 2>/dev/null)$(printf '.%.0s' $(seq 1 $empty) 2>/dev/null)" \
"$pct" "$label"
}
download_file() {
local url="$1" dest="$2" label="${3:-$1}"
mkdir -p "$(dirname "$dest")"
echo -e "${C_CYAN}$(t downloading):${C_RESET} $label"
if ! curl -L --fail --retry 3 --connect-timeout 15 -o "$dest" \
--progress-bar "$url"; then
log ERROR "Failed to download: $url"
return 1
fi
return 0
}
http_get() {
curl -sL --fail --retry 2 --connect-timeout 10 -A "StellarisDLCUnlocker-sh/1.0" "$1"
}
download_with_fallback() {
local rel="$1" dest="$2" label="${3:-$1}"
mkdir -p "$(dirname "$dest")"
if curl -fsSL --retry 2 --connect-timeout 10 -o "$dest" "$REPO_GITHUB_RAW/$rel" 2>>"$LOG_FILE"; then
log OK " (GitHub) $label"
return 0
fi
log WARN " GitHub failed for $label, trying jsDelivr..."
if curl -fsSL --retry 2 --connect-timeout 10 -o "$dest" "$REPO_JSDELIVR/$rel" 2>>"$LOG_FILE"; then
log OK " (jsDelivr) $label"
return 0
fi
log ERROR " Failed to fetch $label from GitHub and jsDelivr."
return 1
}
ask() {
local prompt="$1" __var="$2" __val=""
if [ -r /dev/tty ]; then
read -rp "$prompt" __val </dev/tty
else
read -rp "$prompt" __val
fi
printf -v "$__var" '%s' "$__val"
}
STEAM_DIR=""
GAME_DIR=""
IS_FLATPAK_STEAM=0
find_steam_dir() {
local candidates=(
"$HOME/.steam/steam"
"$HOME/.local/share/Steam"
"$HOME/.var/app/com.valvesoftware.Steam/data/Steam"
"$HOME/snap/steam/common/.local/share/Steam"
)
for c in "${candidates[@]}"; do
if [ -d "$c/steamapps" ]; then
STEAM_DIR="$c"
[[ "$c" == *".var/app/com.valvesoftware.Steam"* ]] && IS_FLATPAK_STEAM=1
[[ "$c" == *"snap/steam"* ]] && IS_FLATPAK_STEAM=1
return 0
fi
done
return 1
}
find_game_dir() {
[ -z "$STEAM_DIR" ] && return 1
local lib_vdf="$STEAM_DIR/steamapps/libraryfolders.vdf"
local libs=("$STEAM_DIR")
if [ -f "$lib_vdf" ]; then
while IFS= read -r p; do
[ -n "$p" ] && libs+=("$p")
done < <(grep -oP '"path"\s*"\K[^"]+' "$lib_vdf" 2>/dev/null)
fi
for lib in "${libs[@]}"; do
local candidate="$lib/steamapps/common/Stellaris"
if [ -d "$candidate" ]; then
GAME_DIR="$candidate"
return 0
fi
done
return 1
}
is_native_build() {
[ -f "$GAME_DIR/stellaris" ] && file "$GAME_DIR/stellaris" 2>/dev/null | grep -qi "ELF"
}
DLC_DATA_JSON=""
fetch_dlc_data() {
log INFO "$(t fetching_dlc_list)"
DLC_DATA_JSON=$(http_get "$GITHUB_DLC_URL" 2>/dev/null) || true
if [ -z "$DLC_DATA_JSON" ]; then
log WARN "GitHub failed, trying jsDelivr..."
DLC_DATA_JSON=$(http_get "$JSDELIVR_DLC_URL" 2>/dev/null) || true
fi
if [ -z "$DLC_DATA_JSON" ]; then
log ERROR "$(t dlc_list_fail)"
return 1
fi
log OK "$(t dlc_list_ok)"
return 0
}
dlc_folders() {
if command -v jq >/dev/null 2>&1; then
echo "$DLC_DATA_JSON" | jq -r '.[].dlc_folder // empty'
else
echo "$DLC_DATA_JSON" | grep -oP '"dlc_folder"\s*:\s*"\K[^"]+'
fi
}
fetch_creamlinux() {
local dest_dir="$CACHE_DIR/$CREAMLINUX_SUBDIR"
log INFO "$(t fetching_creamlinux)"
local ok=1
for f in "${CREAMLINUX_FILES[@]}"; do
download_with_fallback "$CREAMLINUX_SUBDIR/$f" "$dest_dir/$f" "$f" || ok=0
done
if [ "$ok" -eq 1 ]; then
log OK "$(t creamlinux_ok)"
return 0
fi
return 1
}
copy_creamlinux_to_game() {
local src_dir="$CACHE_DIR/$CREAMLINUX_SUBDIR"
log INFO "$(t copying_files)"
for f in cream.sh lib32Creamlinux.so lib64Creamlinux.so; do
if [ -f "$src_dir/$f" ]; then
cp -f "$src_dir/$f" "$GAME_DIR/$f"
else
log WARN "Missing in CreamLinux package: $f"
fi
done
chmod +x "$GAME_DIR/cream.sh" 2>/dev/null
log OK "CreamLinux files copied to: $GAME_DIR"
}
update_cream_ini() {
local ini_path="$GAME_DIR/cream_api.ini"
local cached_ini="$CACHE_DIR/$CREAMLINUX_SUBDIR/cream_api.ini"
if [ -f "$cached_ini" ]; then
cp -f "$cached_ini" "$ini_path"
sed -i 's/\r$//' "$ini_path"
else
: > "$ini_path"
fi
log INFO "$(t updating_ini)"
local info
info=$(http_get "$STEAMCMD_API/$APPID" 2>/dev/null) || true
if [ -z "$info" ]; then
log WARN "$(t ini_skip)"
return 0
fi
local csv
if command -v jq >/dev/null 2>&1; then
csv=$(echo "$info" | jq -r ".data[\"$APPID\"].extended.listofdlc // empty")
else
csv=$(echo "$info" | grep -oP '"listofdlc"\s*:\s*"\K[^"]+')
fi
if [ -z "$csv" ]; then
log WARN "$(t ini_skip)"
return 0
fi
if [ -s "$ini_path" ] && [ -n "$(tail -c1 "$ini_path")" ]; then
printf '\n' >> "$ini_path"
fi
local added=0
IFS=',' read -ra ids <<< "$csv"
for id in "${ids[@]}"; do
id="$(echo "$id" | xargs)"
[ -z "$id" ] && continue
grep -q "^$id " "$ini_path" 2>/dev/null && continue
local name="$id"
local dlc_info
dlc_info=$(http_get "$STEAMCMD_API/$id" 2>/dev/null) || true
if [ -n "$dlc_info" ]; then
if command -v jq >/dev/null 2>&1; then
local n
n=$(echo "$dlc_info" | jq -r ".data[\"$id\"].common.name // empty")
[ -n "$n" ] && name="$n"
else
local n
n=$(echo "$dlc_info" | grep -oP '"name"\s*:\s*"\K[^"]+' | head -1)
[ -n "$n" ] && name="$n"
fi
fi
echo "$id = $name" >> "$ini_path"
added=$((added+1))
done
if [ "$added" -gt 0 ]; then
log OK "$(t ini_updated) +$added"
fi
}
download_dlc_content() {
local dlc_dir="$GAME_DIR/dlc"
mkdir -p "$dlc_dir"
local folders=()
while IFS= read -r f; do
[ -n "$f" ] && folders+=("$f")
done < <(dlc_folders)
local total=${#folders[@]}
local queue=()
for f in "${folders[@]}"; do
local dir="$dlc_dir/$f"
local zip="$dlc_dir/$f.zip"
if [ -d "$dir" ]; then continue; fi
if [ -f "$zip" ]; then
unzip -tq "$zip" >/dev/null 2>&1 && continue
rm -f "$zip"
fi
queue+=("$f")
done
if [ ${#queue[@]} -eq 0 ]; then
log OK "$(t no_dlc_to_download)"
return 0
fi
log INFO "$(t to_download) ${#queue[@]} / $total"
local i=0
for f in "${queue[@]}"; do
i=$((i+1))
local url="https://$SERVER_URL/unlocker/$f.zip"
local dest="$dlc_dir/$f.zip"
echo
log INFO "[$i/${#queue[@]}] $f"
download_file "$url" "$dest" "$f" || continue
done
log INFO "$(t unpacking)"
for z in "$dlc_dir"/*.zip; do
[ -f "$z" ] || continue
local base
base="$(basename "$z" .zip)"
if unzip -oq "$z" -d "$dlc_dir"; then
rm -f "$z"
log OK "Unpacked: $base"
else
log ERROR "Unzip error: $base"
fi
done
}
patch_launch_options() {
header
echo -e "${C_BOLD}$(t patch_launch_header)${C_RESET}"
hr
if [ -z "$STEAM_DIR" ]; then
find_steam_dir || { log ERROR "$(t steam_not_found)"; pause; return 1; }
fi
if pgrep -x steam >/dev/null 2>&1 || pgrep -f "com.valvesoftware.Steam" >/dev/null 2>&1; then
log WARN "$(t patch_steam_running_warn)"
fi
local userdata_dir="$STEAM_DIR/userdata"
if [ ! -d "$userdata_dir" ]; then
log ERROR "$(t patch_userdata_not_found)"
pause; return 1
fi
local patched_any=0
for user_dir in "$userdata_dir"/*/; do
local vdf="${user_dir}config/localconfig.vdf"
[ -f "$vdf" ] || continue
local backup="${vdf}.bak.$(date +%Y%m%d%H%M%S)"
cp -f "$vdf" "$backup"
log INFO "$(t patch_backup) $backup"
local desired='sh ./cream.sh %command%'
local tmp
tmp=$(mktemp)
awk -v appid="\"$APPID\"" -v opt="$desired" '
BEGIN { in_block=0; depth=0; done=0 }
{
line=$0
trimmed=line
gsub(/^[ \t]+|[ \t]+$/, "", trimmed)
if (!in_block && trimmed == appid) {
in_block=1
print line
next
}
if (in_block && depth==0 && trimmed == "{") {
depth=1
print line
next
}
if (in_block && depth==1) {
if (trimmed ~ /^"LaunchOptions"/) {
sub(/"LaunchOptions".*/, "\"LaunchOptions\"\t\t\"" opt "\"", line)
print line
done=1
next
}
if (trimmed == "{") { depth++; print line; next }
if (trimmed == "}") {
if (!done) {
print "\t\t\t\"LaunchOptions\"\t\t\"" opt "\""
done=1
}
in_block=0
depth=0
print line
next
}
}
if (in_block && depth>1) {
if (trimmed == "{") depth++
if (trimmed == "}") depth--
}
print line
}
' "$vdf" > "$tmp"
if grep -q "$APPID" "$tmp"; then
mv "$tmp" "$vdf"
patched_any=1
log OK "$(t patch_done) ($user_dir)"
else
rm -f "$tmp"
log WARN "AppID $APPID not found in $vdf (game never launched from this Steam account?)"
fi
done
if [ "$patched_any" -eq 0 ]; then
log ERROR "$(t patch_localconfig_not_found)"
fi
pause
}
do_install() {
header
echo -e "${C_BOLD}$(t m_install)${C_RESET}"
hr
find_steam_dir
if [ -z "$STEAM_DIR" ]; then
log ERROR "$(t steam_not_found)"
pause; return 1
fi
log OK "$(t steam_found) $STEAM_DIR $([ "$IS_FLATPAK_STEAM" = 1 ] && t flatpak_steam)"
find_game_dir
if [ -z "$GAME_DIR" ]; then
log WARN "$(t game_not_found)"
ask "$(t enter_game_path)" manual_path
if [ -n "$manual_path" ] && [ -d "$manual_path" ]; then
GAME_DIR="$manual_path"
else
pause; return 1
fi
fi
log OK "$(t game_found) $GAME_DIR"
if is_native_build; then
log OK "$(t native_build)"
else
log ERROR "$(t not_native)"
pause; return 1
fi
echo
ask "$(t confirm_install)" confirm
if [[ ! "$confirm" =~ ^[yYдД]$ ]]; then
log INFO "$(t cancelled)"
pause; return 0
fi
fetch_dlc_data || { pause; return 1; }
fetch_creamlinux || { pause; return 1; }
copy_creamlinux_to_game
mkdir -p "$GAME_DIR/dlc"
update_cream_ini
download_dlc_content
echo
log OK "$(t install_done)"
echo -e "${C_DIM}sh ./cream.sh %command%${C_RESET}"
pause
}
show_status() {
header
echo -e "${C_BOLD}$(t m_status)${C_RESET}"
hr
find_steam_dir
if [ -n "$STEAM_DIR" ]; then
echo -e "${C_GREEN}$(t steam_found)${C_RESET} $STEAM_DIR $([ "$IS_FLATPAK_STEAM" = 1 ] && t flatpak_steam)"
else
echo -e "${C_RED}$(t steam_not_found)${C_RESET}"
fi
find_game_dir
if [ -n "$GAME_DIR" ]; then
echo -e "${C_GREEN}$(t game_found)${C_RESET} $GAME_DIR"
if is_native_build; then
echo -e "${C_GREEN}$(t native_build)${C_RESET}"
else
echo -e "${C_RED}$(t not_native)${C_RESET}"
fi
if [ -f "$GAME_DIR/cream.sh" ]; then
echo -e "${C_GREEN}cream.sh:${C_RESET} installed"
else
echo -e "${C_YELLOW}cream.sh:${C_RESET} not installed"
fi
if [ -f "$GAME_DIR/cream_api.ini" ]; then
local cnt
cnt=$(grep -c "=" "$GAME_DIR/cream_api.ini" 2>/dev/null || echo 0)
echo -e "${C_GREEN}cream_api.ini:${C_RESET} $cnt DLC entries"
fi
else
echo -e "${C_RED}$(t game_not_found)${C_RESET}"
fi
pause
}
show_log() {
header
echo -e "${C_BOLD}$(t m_log)${C_RESET}"
hr
if [ -s "$LOG_FILE" ]; then
tail -n 50 "$LOG_FILE"
else
echo "$(t log_empty)"
fi
pause
}
change_language() {
header
echo "1) English"
echo "2) Русский"
echo "3) 中文"
ask "$(t choose)" l
case "$l" in
1) LANGUAGE="en" ;;
2) LANGUAGE="ru" ;;
3) LANGUAGE="zh" ;;
esac
echo "$LANGUAGE" > "$CONFIG_FILE"
log OK "$(t lang_set)"
pause
}
check_deps() {
local missing=()
for c in curl unzip grep awk; do
command -v "$c" >/dev/null 2>&1 || missing+=("$c")
done
if [ ${#missing[@]} -gt 0 ]; then
echo -e "${C_RED}Missing required tools: ${missing[*]}${C_RESET}"
echo "Install them with your package manager, e.g.: sudo apt install ${missing[*]}"
exit 1
fi
command -v jq >/dev/null 2>&1 || log WARN "jq not found — falling back to grep/sed JSON parsing (less robust). Install jq for best reliability."
}
main_menu() {
while true; do
header
echo -e "${C_BOLD}$(t menu_header)${C_RESET}"
hr
echo -e " ${C_CYAN}1)${C_RESET} $(t m_status)"
echo -e " ${C_CYAN}2)${C_RESET} $(t m_install)"
echo -e " ${C_CYAN}3)${C_RESET} $(t m_steam_launch)"
echo -e " ${C_CYAN}4)${C_RESET} $(t m_lang)"
echo -e " ${C_CYAN}5)${C_RESET} $(t m_log)"
echo -e " ${C_CYAN}0)${C_RESET} $(t m_exit)"
hr
ask "$(t choose)" choice
case "$choice" in
1) show_status ;;
2) do_install ;;
3) patch_launch_options ;;
4) change_language ;;
5) show_log ;;
0) exit 0 ;;
*) log WARN "$(t invalid_choice)"; sleep 1 ;;
esac
done
}
[ -f "$CONFIG_FILE" ] && LANGUAGE="$(cat "$CONFIG_FILE")"
check_deps
main_menu
File diff suppressed because it is too large Load Diff
Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

-1004
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.
-336
View File
@@ -1,336 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="ru_RU" sourcelanguage="en">
<context>
<name>MainWindow</name>
<message>
<location filename="ui_main.ui" line="283"/>
<source>Server connection</source>
<extracomment>Server connection radio</extracomment>
<translation>Подключение к серверу</translation>
</message>
<message>
<location filename="ui_main.ui" line="319"/>
<source>Github conncection</source>
<extracomment>GitHub connection radio</extracomment>
<translation>Подключение к GitHub</translation>
</message>
<message>
<location filename="ui_main.ui" line="915"/>
<source>Welcome</source>
<extracomment>main page title</extracomment>
<translation>Приветствуем</translation>
</message>
<message>
<location filename="ui_main.ui" line="1207"/>
<source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;Segoe UI&apos;; font-size:10pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p align=&quot;center&quot; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-weight:600; text-decoration: underline; color:#008f96;&quot;&gt;Stellaris DLC Unlocker&lt;/span&gt;&lt;/p&gt;
&lt;p align=&quot;center&quot; style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:&apos;MS Shell Dlg 2&apos;; font-weight:600; text-decoration: underline;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-weight:600; text-decoration: underline;&quot;&gt;Requirements&lt;/span&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;;&quot;&gt;:&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;;&quot;&gt; &lt;/span&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-weight:600;&quot;&gt;License&lt;/span&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;;&quot;&gt;: Steam.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:&apos;MS Shell Dlg 2&apos;; font-weight:600; text-decoration: underline;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-weight:600; text-decoration: underline;&quot;&gt;Installation&lt;/span&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;;&quot;&gt;:&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;;&quot;&gt; Follow the installer instructions. Installation is almost entirely automatic. &lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p align=&quot;center&quot; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-weight:600; text-decoration: underline; color:#008f96;&quot;&gt;Terms Of Use&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:&apos;MS Shell Dlg 2&apos;; color:#0000ff;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;;&quot;&gt;1. This unlocker is distributed absolutely free of charge. Any commercial use of this unlocker is prohibited.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;;&quot;&gt;2. THIS UNLOCKER IS PROVIDED &amp;quot;AS IS&amp;quot;. NO WARRANTIES ARE PROVIDED OR IMPLIED. YOU USE THIS MODIFICATION OF THE ORIGINAL GAME AT YOUR OWN RISK. THE AUTHORS OF THE MODIFICATION WILL NOT BE LIABLE FOR ANY LOSSES OR DATA CORRUPTION, ANY LOST PROFITS IN THE PROCESS OF USE OR MISUSE OF THIS MODIFICATION.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;;&quot;&gt;3. All rights not expressly granted here are reserved by the copyright holders.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;;&quot;&gt;4. Installation and use of this modification implies that you have read and understand the terms of this license agreement and agree to them.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:&apos;MS Shell Dlg 2&apos;;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<extracomment>Welcome page text</extracomment>
<translation>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;Segoe UI&apos;; font-size:10pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p align=&quot;center&quot; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-weight:600; text-decoration: underline; color:#008f96;&quot;&gt;Stellaris DLC Unlocker&lt;/span&gt;&lt;/p&gt;
&lt;p align=&quot;center&quot; style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:&apos;MS Shell Dlg 2&apos;; font-weight:600; text-decoration: underline;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-weight:600; text-decoration: underline;&quot;&gt;Требования&lt;/span&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;;&quot;&gt;:&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;;&quot;&gt; &lt;/span&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-weight:600;&quot;&gt;Лицензия&lt;/span&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;;&quot;&gt;: Steam.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:&apos;MS Shell Dlg 2&apos;; font-weight:600; text-decoration: underline;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-weight:600; text-decoration: underline;&quot;&gt;Установка&lt;/span&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;;&quot;&gt;:&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;;&quot;&gt; Следуйте инструкциям инсталлятора. Установка почти полностью автоматическая. &lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p align=&quot;center&quot; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-weight:600; text-decoration: underline; color:#008f96;&quot;&gt;Правила использования&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:&apos;MS Shell Dlg 2&apos;; color:#0000ff;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;;&quot;&gt;1. Данный анлокер распространяется абсолютно бесплатно. Любое коммерческое использование запрещается.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;;&quot;&gt;2. ДАННЫЙ АНЛОКЕР ПОСТАВЛЯЕТСЯ ПО ПРИНЦИПУ «AS IS». НИКАКИХ ГАРАНТИЙ НЕ ПРИЛАГАЕТСЯ И НЕ ПРЕДУСМАТРИВАЕТСЯ. ВЫ ИСПОЛЬЗУЕТЕ ЭТУ МОДИФИКАЦИЮ ОРИГИНАЛЬНОЙ ИГРЫ НА СВОЙ СТРАХ И РИСК. АВТОРЫ МОДИФИКАЦИИ НЕ БУДУТ ОТВЕЧАТЬ НИ ЗА КАКИЕ ПОТЕРИ ИЛИ ИСКАЖЕНИЯ ДАННЫХ, ЛЮБУЮ УПУЩЕННУЮ ВЫГОДУ В ПРОЦЕССЕ ИСПОЛЬЗОВАНИЯ ИЛИ НЕПРАВИЛЬНОГО ИСПОЛЬЗОВАНИЯ ДАННОЙ МОДИФИКАЦИИ.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;;&quot;&gt;3. Все права, не предоставленные здесь явно, сохраняются за правообладателями.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;;&quot;&gt;4. Установка и использование данной модификации означает, что вы ознакомились и понимаете положения настоящего лицензионного соглашения и согласны с ними.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:&apos;MS Shell Dlg 2&apos;;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
</message>
<message>
<location filename="ui_main.ui" line="1336"/>
<source>Next</source>
<extracomment>next button</extracomment>
<translation>Далее</translation>
</message>
<message>
<location filename="ui_main.ui" line="1392"/>
<source>Unlock settings</source>
<extracomment>work page label</extracomment>
<translation>Настройка разблокировки</translation>
</message>
<message>
<location filename="ui_main.ui" line="1733"/>
<source>Stellaris path</source>
<comment>sadsadsa</comment>
<extracomment>stellaris path text</extracomment>
<translation>Путь к игре</translation>
</message>
<message>
<location filename="ui_main.ui" line="1820"/>
<source>Open</source>
<extracomment>open path button</extracomment>
<translation>Открыть</translation>
</message>
<message>
<location filename="ui_main.ui" line="1881"/>
<source>Unlock</source>
<extracomment>Unlock start button</extracomment>
<translation>Начать</translation>
</message>
<message>
<location filename="ui_main.ui" line="1963"/>
<source>Full launcher reinstallation</source>
<extracomment>full reinstal checkbox</extracomment>
<translation>Полная переустановка</translation>
</message>
<message>
<location filename="ui_main.ui" line="1994"/>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This function will delete all saves and presets of mods.&lt;/p&gt;&lt;p&gt;It is only needed if something did not work during the normal installation&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<extracomment>full reinstall tooltip</extracomment>
<translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Эта функция удалит все сохранения и пресеты модов.&lt;/p&gt;&lt;p&gt;Необходима только при проблемах с нормальной разблокировкой&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
</message>
<message>
<location filename="ui_main.ui" line="2088"/>
<source>Skip launcher reinstall</source>
<translation>Пропустить переустановку лаунчера</translation>
</message>
<message>
<location filename="ui_main.ui" line="2103"/>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;It is used if it is impossible to automatically reinstall the launcher.&lt;/p&gt;&lt;p&gt;The program will assume that the launcher has already been reinstalled before&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<oldsource>It is used if it is impossible to automatically reinstall the launcher.
The program will assume that the launcher has already been reinstalled before</oldsource>
<extracomment>Skip launcher reinstall tooltip</extracomment>
<translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Использовать при проблемах с автоматической переустановкой лаунчера.&lt;/p&gt;&lt;p&gt;Программа будет считать, что переустановка уже была произведена&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
</message>
<message>
<location filename="ui_main.ui" line="1477"/>
<source>Alternative unlock</source>
<extracomment>Alternative unlock checkbox</extracomment>
<translation>Альернативная разблокировка</translation>
</message>
<message>
<location filename="ui_main.ui" line="1492"/>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Uses a different unlock method&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<extracomment>Alternative unlock tooltip</extracomment>
<translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Использует иной метод разблокировки&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
</message>
<message>
<location filename="ui_main.ui" line="2180"/>
<source>Progress</source>
<extracomment>Progress label</extracomment>
<translation>Прогресс</translation>
</message>
<message>
<location filename="ui_main.ui" line="2216"/>
<source>Download files</source>
<extracomment>Download files progress radio</extracomment>
<translation>Загрузка файлов</translation>
</message>
<message>
<location filename="ui_main.ui" line="2261"/>
<source>Launcher reinstall</source>
<extracomment>Launcher reinstall progress radio</extracomment>
<translation>Переустановка лаунчера</translation>
</message>
<message>
<location filename="ui_main.ui" line="2300"/>
<source>Copy files</source>
<extracomment>Copy files progress radio</extracomment>
<translation>Копирование файлов</translation>
</message>
<message>
<location filename="ui_main.ui" line="2345"/>
<source>DLC Download progress</source>
<extracomment>DLC Download progress label</extracomment>
<translation>Прогресс загрузки DLC</translation>
</message>
<message>
<location filename="ui_main.ui" line="2415"/>
<source>Current DLC progress</source>
<extracomment>Current DLC Download progress label</extracomment>
<translation>Текущее DLC</translation>
</message>
<message>
<location filename="ui_main.ui" line="2559"/>
<source>Launch game</source>
<extracomment>Launch game checkbox</extracomment>
<translation>Запустить игру</translation>
</message>
<message>
<location filename="ui_main.ui" line="2616"/>
<source>Done</source>
<extracomment>Done buttone</extracomment>
<translation>Готово</translation>
</message>
<message>
<location filename="ui_main.ui" line="2672"/>
<source>Log</source>
<extracomment>bug page title</extracomment>
<translation>Лог</translation>
</message>
<message>
<location filename="../../UI_logic/MainWindow.py" line="88"/>
<source>Close</source>
<translation>Закрыть</translation>
</message>
<message>
<location filename="ui_main.ui" line="2541"/>
<source>Update old DLCs</source>
<translatorcomment>Update old DLCs button</translatorcomment>
<translation>Обновить DLC</translation>
</message>
<message>
<location filename="ui_main.ui" line="2577"/>
<source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;Segoe UI&apos;; font-size:8.25pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt; color:#ff0000;&quot;&gt;Old DLCs detected! &lt;/span&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt; color:#ffffff;&quot;&gt;You can just update it while unlocking&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translatorcomment>Update old DLCs text</translatorcomment>
<translation>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;Segoe UI&apos;; font-size:8.25pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt; color:#ff0000;&quot;&gt;Обнаружены старые DLC! &lt;/span&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt; color:#ffffff;&quot;&gt;Можно обновить их вместе с разблокировкой&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
</message>
<message>
<location filename="../../UI_logic/MainWindow.py" line="88"/>
<source>Exit Unlocker?</source>
<translation>Закрыть программу?</translation>
</message>
<message>
<location filename="../../UI_logic/MainWindow.py" line="88"/>
<source>No</source>
<translation>Нет</translation>
</message>
<message>
<location filename="../../UI_logic/MainWindow.py" line="88"/>
<source>Yes</source>
<translation>Да</translation>
</message>
<message>
<location filename="../../UI_logic/MainWindow.py" line="152"/>
<source>Choose Stellaris path</source>
<translation>Выберите путь к игре</translation>
</message>
<message>
<location filename="../../UI_logic/MainWindow.py" line="162"/>
<source>This is not Stellaris path</source>
<translation>Это не путь к игре</translation>
</message>
<message>
<location filename="../../UI_logic/MainWindow.py" line="293"/>
<source>Ok</source>
<translation>Ок</translation>
</message>
<message>
<location filename="../../UI_logic/MainWindow.py" line="173"/>
<source>Please choose game path</source>
<translation>Пожайлуйста выберите путь к игре</translation>
</message>
<message>
<location filename="../../UI_logic/MainWindow.py" line="185"/>
<source>New version</source>
<translation>Новая версия</translation>
</message>
<message>
<location filename="../../UI_logic/MainWindow.py" line="185"/>
<source>New version found
Please update the program to correctly work </source>
<translation>Найдена новая версия
Пожалуйста обновите программу для корректной работы </translation>
</message>
<message>
<location filename="../../UI_logic/MainWindow.py" line="185"/>
<source>Cancel</source>
<translation>Отмена</translation>
</message>
<message>
<location filename="../../UI_logic/MainWindow.py" line="185"/>
<source>Update</source>
<translation>Обновить</translation>
</message>
<message>
<location filename="../../UI_logic/MainWindow.py" line="205"/>
<source>Can&apos;t establish connection with GitHub. Check internet</source>
<translation>Невозможно подключится к GitHub. Проверьте подключение</translation>
</message>
<message>
<location filename="../../UI_logic/MainWindow.py" line="214"/>
<source>Connection error</source>
<translation>Ошибка подключения</translation>
</message>
<message>
<location filename="../../UI_logic/MainWindow.py" line="214"/>
<source>Cant establish connection with server
Check your connection or you can try download DLC directly
Unzip downloaded &quot;dlc&quot; folder to game folder
Then you can continue</source>
<translation>Невозможно подключится к серверу
Проверьте подключение или вы можете попробовать скачать DLC напрямую
Распакуйте скачанные DLC в папку &quot;DLC&quot; в папке с игрой
Затем вы сможете продолжить</translation>
</message>
<message>
<location filename="../../UI_logic/MainWindow.py" line="501"/>
<source>Exit</source>
<translation>Выйти</translation>
</message>
<message>
<location filename="../../UI_logic/MainWindow.py" line="267"/>
<source>Alt launcher downloading</source>
<translation>Загрузка альт лаунчера</translation>
</message>
<message>
<location filename="../../UI_logic/MainWindow.py" line="268"/>
<source>Downloading</source>
<translation>Загрузка</translation>
</message>
<message>
<location filename="../../UI_logic/MainWindow.py" line="293"/>
<source>Cant download alt launcher</source>
<translation>Невозможно загрузить альт лаунчер</translation>
</message>
<message>
<location filename="../../UI_logic/MainWindow.py" line="402"/>
<source>File download error</source>
<translation>Ошибка загрузки файлов</translation>
</message>
<message>
<location filename="../../UI_logic/MainWindow.py" line="440"/>
<source>Launcher reinstall error</source>
<translation>Ошибка переустановки лаунчера</translation>
</message>
<message>
<location filename="../../UI_logic/MainWindow.py" line="501"/>
<source>Error while unzipping</source>
<translation>Ошибка во время распаковки</translation>
</message>
<message>
<location filename="../../UI_logic/MainWindow.py" line="465"/>
<source>Error unknown launcher</source>
<translation>Ошибка: неизвестный лаунчер</translation>
</message>
</context>
</TS>
Binary file not shown.
-334
View File
@@ -1,334 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="zh_CN" sourcelanguage="en">
<context>
<name>MainWindow</name>
<message>
<location filename="ui_main.ui" line="283"/>
<source>Server connection</source>
<extracomment>Server connection radio</extracomment>
<translation></translation>
</message>
<message>
<location filename="ui_main.ui" line="319"/>
<source>Github conncection</source>
<extracomment>GitHub connection radio</extracomment>
<translation>Github </translation>
</message>
<message>
<location filename="ui_main.ui" line="915"/>
<source>Welcome</source>
<extracomment>main page title</extracomment>
<translation></translation>
</message>
<message>
<location filename="../ui_main.ui" line="1207"/>
<source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;Segoe UI&apos;; font-size:10pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p align=&quot;center&quot; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-weight:600; text-decoration: underline; color:#008f96;&quot;&gt;Stellaris DLC Unlocker&lt;/span&gt;&lt;/p&gt;
&lt;p align=&quot;center&quot; style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:&apos;MS Shell Dlg 2&apos;; font-weight:600; text-decoration: underline;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-weight:600; text-decoration: underline;&quot;&gt;Requirements&lt;/span&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;;&quot;&gt;:&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;;&quot;&gt; &lt;/span&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-weight:600;&quot;&gt;License&lt;/span&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;;&quot;&gt;: Steam.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:&apos;MS Shell Dlg 2&apos;; font-weight:600; text-decoration: underline;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-weight:600; text-decoration: underline;&quot;&gt;Installation&lt;/span&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;;&quot;&gt;:&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;;&quot;&gt; Follow the installer instructions. Installation is almost entirely automatic. &lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p align=&quot;center&quot; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-weight:600; text-decoration: underline; color:#008f96;&quot;&gt;Terms Of Use&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:&apos;MS Shell Dlg 2&apos;; color:#0000ff;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;;&quot;&gt;1. This unlocker is distributed absolutely free of charge. Any commercial use of this unlocker is prohibited.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;;&quot;&gt;2. THIS UNLOCKER IS PROVIDED &amp;quot;AS IS&amp;quot;. NO WARRANTIES ARE PROVIDED OR IMPLIED. YOU USE THIS MODIFICATION OF THE ORIGINAL GAME AT YOUR OWN RISK. THE AUTHORS OF THE MODIFICATION WILL NOT BE LIABLE FOR ANY LOSSES OR DATA CORRUPTION, ANY LOST PROFITS IN THE PROCESS OF USE OR MISUSE OF THIS MODIFICATION.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;;&quot;&gt;3. All rights not expressly granted here are reserved by the copyright holders.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;;&quot;&gt;4. Installation and use of this modification implies that you have read and understand the terms of this license agreement and agree to them.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:&apos;MS Shell Dlg 2&apos;;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<extracomment>Welcome page text</extracomment>
<translation>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;Segoe UI&apos;; font-size:10pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p align=&quot;center&quot; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-weight:600; text-decoration: underline; color:#008f96;&quot;&gt;Stellaris DLC &lt;/span&gt;&lt;/p&gt;
&lt;p align=&quot;center&quot; style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:&apos;MS Shell Dlg 2&apos;; font-weight:600; text-decoration: underline;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-weight:600; text-decoration: underline;&quot;&gt;&lt;/span&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;;&quot;&gt;:&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;;&quot;&gt; &lt;/span&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-weight:600;&quot;&gt;&lt;/span&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;;&quot;&gt;: &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:&apos;MS Shell Dlg 2&apos;; font-weight:600; text-decoration: underline;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-weight:600; text-decoration: underline;&quot;&gt;&lt;/span&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;;&quot;&gt;:&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;;&quot;&gt; &lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p align=&quot;center&quot; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-weight:600; text-decoration: underline; color:#008f96;&quot;&gt;使&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:&apos;MS Shell Dlg 2&apos;; color:#0000ff;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;;&quot;&gt;1. 退&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;;&quot;&gt;2. 使使 VAC &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;;&quot;&gt;3. &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;;&quot;&gt;4. 使&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:&apos;MS Shell Dlg 2&apos;;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
</message>
<message>
<location filename="ui_main.ui" line="1336"/>
<source>Next</source>
<extracomment>next button</extracomment>
<translation></translation>
</message>
<message>
<location filename="ui_main.ui" line="1392"/>
<source>Unlock settings</source>
<extracomment>work page label</extracomment>
<translation></translation>
</message>
<message>
<location filename="ui_main.ui" line="1477"/>
<source>Alternative unlock</source>
<extracomment>Alternative unlock checkbox</extracomment>
<translation></translation>
</message>
<message>
<location filename="ui_main.ui" line="1492"/>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Uses a different unlock method&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<extracomment>Alternative unlock tooltip</extracomment>
<translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;使&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
</message>
<message>
<location filename="ui_main.ui" line="1733"/>
<source>Stellaris path</source>
<comment>sadsadsa</comment>
<extracomment>stellaris path text</extracomment>
<translation>Stellaris </translation>
</message>
<message>
<location filename="ui_main.ui" line="1820"/>
<source>Open</source>
<extracomment>open path button</extracomment>
<translation></translation>
</message>
<message>
<location filename="ui_main.ui" line="1881"/>
<source>Unlock</source>
<extracomment>Unlock start button</extracomment>
<translation></translation>
</message>
<message>
<location filename="ui_main.ui" line="1963"/>
<source>Full launcher reinstallation</source>
<extracomment>full reinstal checkbox</extracomment>
<translation></translation>
</message>
<message>
<location filename="ui_main.ui" line="1994"/>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This function will delete all saves and presets of mods.&lt;/p&gt;&lt;p&gt;It is only needed if something did not work during the normal installation&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<extracomment>full reinstall tooltip</extracomment>
<translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;使&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
</message>
<message>
<location filename="ui_main.ui" line="2088"/>
<source>Skip launcher reinstall</source>
<translation></translation>
</message>
<message>
<location filename="ui_main.ui" line="2103"/>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;It is used if it is impossible to automatically reinstall the launcher.&lt;/p&gt;&lt;p&gt;The program will assume that the launcher has already been reinstalled before&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<extracomment>Skip launcher reinstall tooltip</extracomment>
<translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;使&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
</message>
<message>
<location filename="ui_main.ui" line="2180"/>
<source>Progress</source>
<extracomment>Progress label</extracomment>
<translation></translation>
</message>
<message>
<location filename="ui_main.ui" line="2216"/>
<source>Download files</source>
<extracomment>Download files progress radio</extracomment>
<translation></translation>
</message>
<message>
<location filename="ui_main.ui" line="2261"/>
<source>Launcher reinstall</source>
<extracomment>Launcher reinstall progress radio</extracomment>
<translation></translation>
</message>
<message>
<location filename="ui_main.ui" line="2300"/>
<source>Copy files</source>
<extracomment>Copy files progress radio</extracomment>
<translation></translation>
</message>
<message>
<location filename="ui_main.ui" line="2345"/>
<source>DLC Download progress</source>
<extracomment>DLC Download progress label</extracomment>
<translation>DLC </translation>
</message>
<message>
<location filename="ui_main.ui" line="2415"/>
<source>Current DLC progress</source>
<extracomment>Current DLC Download progress label</extracomment>
<translation> DLC </translation>
</message>
<message>
<location filename="ui_main.ui" line="2559"/>
<source>Launch game</source>
<extracomment>Launch game checkbox</extracomment>
<translation></translation>
</message>
<message>
<location filename="ui_main.ui" line="2616"/>
<source>Done</source>
<extracomment>Done buttone</extracomment>
<translation></translation>
</message>
<message>
<location filename="ui_main.ui" line="2672"/>
<source>Log</source>
<extracomment>bug page title</extracomment>
<translation></translation>
</message>
<message>
<location filename="../../UI_logic/MainWindow.py" line="91"/>
<source>Close</source>
<translation></translation>
</message>
<message>
<location filename="ui_main.ui" line="2541"/>
<source>Update old DLCs</source>
<translatorcomment>Update old DLCs button</translatorcomment>
<translation>DLCs</translation>
</message>
<message>
<location filename="ui_main.ui" line="2577"/>
<source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;Segoe UI&apos;; font-size:8.25pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt; color:#ff0000;&quot;&gt;Old DLCs detected! &lt;/span&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt; color:#ffffff;&quot;&gt;You can just update it while unlocking&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translatorcomment>Update old DLCs text</translatorcomment>
<translation>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;Segoe UI&apos;; font-size:8.25pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt; color:#ff0000;&quot;&gt;DLCs &lt;/span&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt; color:#ffffff;&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
</message>
<message>
<location filename="../../UI_logic/MainWindow.py" line="91"/>
<source>Exit Unlocker?</source>
<translation>退</translation>
</message>
<message>
<location filename="../../UI_logic/MainWindow.py" line="91"/>
<source>No</source>
<translation></translation>
</message>
<message>
<location filename="../../UI_logic/MainWindow.py" line="91"/>
<source>Yes</source>
<translation></translation>
</message>
<message>
<location filename="../../UI_logic/MainWindow.py" line="169"/>
<source>Choose Stellaris path</source>
<translation> Stellaris </translation>
</message>
<message>
<location filename="../../UI_logic/MainWindow.py" line="179"/>
<source>This is not Stellaris path</source>
<translation> Stellaris </translation>
</message>
<message>
<location filename="../../UI_logic/MainWindow.py" line="310"/>
<source>Ok</source>
<translation></translation>
</message>
<message>
<location filename="../../UI_logic/MainWindow.py" line="190"/>
<source>Please choose game path</source>
<translation></translation>
</message>
<message>
<location filename="../../UI_logic/MainWindow.py" line="202"/>
<source>New version</source>
<translation></translation>
</message>
<message>
<location filename="../../UI_logic/MainWindow.py" line="202"/>
<source>New version found
Please update the program to correctly work </source>
<translation>
</translation>
</message>
<message>
<location filename="../../UI_logic/MainWindow.py" line="202"/>
<source>Cancel</source>
<translation></translation>
</message>
<message>
<location filename="../../UI_logic/MainWindow.py" line="202"/>
<source>Update</source>
<translation></translation>
</message>
<message>
<location filename="../../UI_logic/MainWindow.py" line="222"/>
<source>Can&apos;t establish connection with GitHub. Check internet</source>
<translation> GitHub 使</translation>
</message>
<message>
<location filename="../../UI_logic/MainWindow.py" line="231"/>
<source>Connection error</source>
<translation></translation>
</message>
<message>
<location filename="../../UI_logic/MainWindow.py" line="231"/>
<source>Cant establish connection with server
Check your connection or you can try download DLC directly
Unzip downloaded &quot;dlc&quot; folder to game folder
Then you can continue</source>
<translation>
DLC
&quot;dlc&quot;
</translation>
</message>
<message>
<location filename="../../UI_logic/MainWindow.py" line="520"/>
<source>Exit</source>
<translation>退</translation>
</message>
<message>
<location filename="../../UI_logic/MainWindow.py" line="284"/>
<source>Alt launcher downloading</source>
<translation></translation>
</message>
<message>
<location filename="../../UI_logic/MainWindow.py" line="285"/>
<source>Downloading</source>
<translation></translation>
</message>
<message>
<location filename="../../UI_logic/MainWindow.py" line="310"/>
<source>Cant download alt launcher</source>
<translation></translation>
</message>
<message>
<location filename="../../UI_logic/MainWindow.py" line="421"/>
<source>File download error</source>
<translation></translation>
</message>
<message>
<location filename="../../UI_logic/MainWindow.py" line="459"/>
<source>Launcher reinstall error</source>
<translation></translation>
</message>
<message>
<location filename="../../UI_logic/MainWindow.py" line="520"/>
<source>Error while unzipping</source>
<translation></translation>
</message>
<message>
<location filename="../../UI_logic/MainWindow.py" line="484"/>
<source>Error unknown launcher</source>
<translation></translation>
</message>
</context>
</TS>
-167
View File
@@ -1,167 +0,0 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'UI/ui_dialog.ui'
#
# Created by: PyQt5 UI code generator 5.15.10
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_Dialog(object):
def setupUi(self, Dialog):
Dialog.setObjectName("Dialog")
Dialog.resize(450, 235)
Dialog.setMinimumSize(QtCore.QSize(450, 235))
Dialog.setMaximumSize(QtCore.QSize(450, 235))
Dialog.setStyleSheet("QDialog {\n"
" background:rgb(51,51,51);\n"
"}")
self.verticalLayout = QtWidgets.QVBoxLayout(Dialog)
self.verticalLayout.setContentsMargins(0, 0, 0, 0)
self.verticalLayout.setSpacing(0)
self.verticalLayout.setObjectName("verticalLayout")
self.frame_2 = QtWidgets.QFrame(Dialog)
self.frame_2.setStyleSheet("background:rgb(51,51,51);")
self.frame_2.setFrameShape(QtWidgets.QFrame.NoFrame)
self.frame_2.setFrameShadow(QtWidgets.QFrame.Plain)
self.frame_2.setObjectName("frame_2")
self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.frame_2)
self.verticalLayout_2.setContentsMargins(2, 2, 2, 2)
self.verticalLayout_2.setSpacing(0)
self.verticalLayout_2.setObjectName("verticalLayout_2")
self.frame_top = QtWidgets.QFrame(self.frame_2)
self.frame_top.setMinimumSize(QtCore.QSize(0, 55))
self.frame_top.setMaximumSize(QtCore.QSize(16777215, 55))
self.frame_top.setStyleSheet("background:rgb(91,90,90);")
self.frame_top.setFrameShape(QtWidgets.QFrame.NoFrame)
self.frame_top.setFrameShadow(QtWidgets.QFrame.Plain)
self.frame_top.setObjectName("frame_top")
self.horizontalLayout = QtWidgets.QHBoxLayout(self.frame_top)
self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
self.horizontalLayout.setSpacing(0)
self.horizontalLayout.setObjectName("horizontalLayout")
self.lab_heading = QtWidgets.QLabel(self.frame_top)
font = QtGui.QFont()
font.setFamily("Segoe UI")
font.setPointSize(14)
self.lab_heading.setFont(font)
self.lab_heading.setStyleSheet("color:rgb(255,255,255);")
self.lab_heading.setText("")
self.lab_heading.setAlignment(QtCore.Qt.AlignCenter)
self.lab_heading.setObjectName("lab_heading")
self.horizontalLayout.addWidget(self.lab_heading)
self.bn_close = QtWidgets.QPushButton(self.frame_top)
self.bn_close.setMinimumSize(QtCore.QSize(55, 55))
self.bn_close.setMaximumSize(QtCore.QSize(55, 55))
self.bn_close.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))
self.bn_close.setStyleSheet("QPushButton {\n"
" border: none;\n"
" background-color: rgba(0,0,0,0);\n"
"}\n"
"QPushButton:hover {\n"
" background-color: rgb(255,107,107);\n"
"}\n"
"QPushButton:pressed { \n"
" background-color: rgba(0,0,0,0);\n"
"}")
self.bn_close.setText("")
icon = QtGui.QIcon()
icon.addPixmap(QtGui.QPixmap("UI\\icons/1x/closeAsset 43.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.bn_close.setIcon(icon)
self.bn_close.setIconSize(QtCore.QSize(22, 22))
self.bn_close.setAutoDefault(False)
self.bn_close.setFlat(True)
self.bn_close.setObjectName("bn_close")
self.horizontalLayout.addWidget(self.bn_close)
self.verticalLayout_2.addWidget(self.frame_top)
self.frame_bottom = QtWidgets.QFrame(self.frame_2)
self.frame_bottom.setStyleSheet("background:rgb(91,90,90);")
self.frame_bottom.setFrameShape(QtWidgets.QFrame.StyledPanel)
self.frame_bottom.setFrameShadow(QtWidgets.QFrame.Raised)
self.frame_bottom.setObjectName("frame_bottom")
self.gridLayout = QtWidgets.QGridLayout(self.frame_bottom)
self.gridLayout.setContentsMargins(15, -1, 35, 0)
self.gridLayout.setHorizontalSpacing(5)
self.gridLayout.setVerticalSpacing(0)
self.gridLayout.setObjectName("gridLayout")
self.bn_east = QtWidgets.QPushButton(self.frame_bottom)
self.bn_east.setMinimumSize(QtCore.QSize(80, 25))
self.bn_east.setMaximumSize(QtCore.QSize(80, 25))
font = QtGui.QFont()
font.setFamily("Segoe UI")
font.setPointSize(12)
self.bn_east.setFont(font)
self.bn_east.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))
self.bn_east.setStyleSheet("QPushButton {\n"
" border: 2px solid rgb(51,51,51);\n"
" border-radius: 5px; \n"
" color:rgb(255,255,255);\n"
" background-color: rgb(51,51,51);\n"
"}\n"
"QPushButton:hover {\n"
" border: 2px solid rgb(0,143,150);\n"
" background-color: rgb(0,143,150);\n"
"}\n"
"QPushButton:pressed { \n"
" border: 2px solid rgb(0,143,150);\n"
" background-color: rgb(51,51,51);\n"
"}")
self.bn_east.setText("")
self.bn_east.setAutoDefault(False)
self.bn_east.setObjectName("bn_east")
self.gridLayout.addWidget(self.bn_east, 1, 3, 1, 1)
self.lab_icon = QtWidgets.QLabel(self.frame_bottom)
self.lab_icon.setMinimumSize(QtCore.QSize(40, 40))
self.lab_icon.setMaximumSize(QtCore.QSize(40, 40))
self.lab_icon.setText("")
self.lab_icon.setObjectName("lab_icon")
self.gridLayout.addWidget(self.lab_icon, 0, 0, 1, 1)
self.bn_west = QtWidgets.QPushButton(self.frame_bottom)
self.bn_west.setMinimumSize(QtCore.QSize(69, 25))
self.bn_west.setMaximumSize(QtCore.QSize(69, 25))
font = QtGui.QFont()
font.setFamily("Segoe UI")
font.setPointSize(12)
self.bn_west.setFont(font)
self.bn_west.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))
self.bn_west.setStyleSheet("QPushButton {\n"
" border: 2px solid rgb(51,51,51);\n"
" border-radius: 5px; \n"
" color:rgb(255,255,255);\n"
" background-color: rgb(51,51,51);\n"
"}\n"
"QPushButton:hover {\n"
" border: 2px solid rgb(255,107,107);\n"
" background-color: rgb(255,107,107);\n"
"}\n"
"QPushButton:pressed { \n"
" border: 2px solid rgb(255,107,107);\n"
" background-color: rgb(51,51,51);\n"
"}")
self.bn_west.setText("")
self.bn_west.setAutoDefault(False)
self.bn_west.setObjectName("bn_west")
self.gridLayout.addWidget(self.bn_west, 1, 2, 1, 1)
self.lab_message = QtWidgets.QLabel(self.frame_bottom)
font = QtGui.QFont()
font.setFamily("Segoe UI")
font.setPointSize(12)
self.lab_message.setFont(font)
self.lab_message.setStyleSheet("color:rgb(255,255,255);")
self.lab_message.setText("")
self.lab_message.setWordWrap(True)
self.lab_message.setObjectName("lab_message")
self.gridLayout.addWidget(self.lab_message, 0, 1, 1, 3)
self.verticalLayout_2.addWidget(self.frame_bottom)
self.verticalLayout.addWidget(self.frame_2)
self.retranslateUi(Dialog)
QtCore.QMetaObject.connectSlotsByName(Dialog)
def retranslateUi(self, Dialog):
_translate = QtCore.QCoreApplication.translate
Dialog.setWindowTitle(_translate("Dialog", "Dialog"))
-99
View File
@@ -1,99 +0,0 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'UI/ui_error.ui'
#
# Created by: PyQt5 UI code generator 5.15.10
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_Error(object):
def setupUi(self, Error):
Error.setObjectName("Error")
Error.resize(250, 150)
Error.setMinimumSize(QtCore.QSize(250, 150))
Error.setMaximumSize(QtCore.QSize(250, 150))
self.horizontalLayout = QtWidgets.QHBoxLayout(Error)
self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
self.horizontalLayout.setSpacing(0)
self.horizontalLayout.setObjectName("horizontalLayout")
self.frame = QtWidgets.QFrame(Error)
self.frame.setStyleSheet("background:rgb(51,51,51);")
self.frame.setFrameShape(QtWidgets.QFrame.NoFrame)
self.frame.setFrameShadow(QtWidgets.QFrame.Plain)
self.frame.setObjectName("frame")
self.verticalLayout = QtWidgets.QVBoxLayout(self.frame)
self.verticalLayout.setContentsMargins(2, 2, 2, 2)
self.verticalLayout.setSpacing(0)
self.verticalLayout.setObjectName("verticalLayout")
self.frame_top = QtWidgets.QFrame(self.frame)
self.frame_top.setMinimumSize(QtCore.QSize(0, 55))
self.frame_top.setMaximumSize(QtCore.QSize(16777215, 55))
self.frame_top.setStyleSheet("background:rgb(91,90,90);")
self.frame_top.setFrameShape(QtWidgets.QFrame.NoFrame)
self.frame_top.setFrameShadow(QtWidgets.QFrame.Plain)
self.frame_top.setObjectName("frame_top")
self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.frame_top)
self.horizontalLayout_2.setContentsMargins(15, 5, 0, 0)
self.horizontalLayout_2.setSpacing(5)
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
self.lab_icon = QtWidgets.QLabel(self.frame_top)
self.lab_icon.setMinimumSize(QtCore.QSize(35, 35))
self.lab_icon.setMaximumSize(QtCore.QSize(35, 35))
self.lab_icon.setText("")
self.lab_icon.setObjectName("lab_icon")
self.horizontalLayout_2.addWidget(self.lab_icon)
self.lab_heading = QtWidgets.QLabel(self.frame_top)
font = QtGui.QFont()
font.setFamily("Segoe UI")
font.setPointSize(12)
self.lab_heading.setFont(font)
self.lab_heading.setStyleSheet("color:rgb(255,255,255);")
self.lab_heading.setText("")
self.lab_heading.setWordWrap(True)
self.lab_heading.setObjectName("lab_heading")
self.horizontalLayout_2.addWidget(self.lab_heading)
self.verticalLayout.addWidget(self.frame_top)
self.frame_bottom = QtWidgets.QFrame(self.frame)
self.frame_bottom.setStyleSheet("background:rgb(91,90,90);")
self.frame_bottom.setFrameShape(QtWidgets.QFrame.NoFrame)
self.frame_bottom.setFrameShadow(QtWidgets.QFrame.Plain)
self.frame_bottom.setObjectName("frame_bottom")
self.gridLayout = QtWidgets.QGridLayout(self.frame_bottom)
self.gridLayout.setContentsMargins(-1, -1, -1, 0)
self.gridLayout.setObjectName("gridLayout")
self.bn_ok = QtWidgets.QPushButton(self.frame_bottom)
self.bn_ok.setMinimumSize(QtCore.QSize(69, 25))
self.bn_ok.setMaximumSize(QtCore.QSize(69, 25))
self.bn_ok.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))
self.bn_ok.setStyleSheet("QPushButton {\n"
" border: 2px solid rgb(51,51,51);\n"
" border-radius: 5px; \n"
" color:rgb(255,255,255);\n"
" background-color: rgb(51,51,51);\n"
"}\n"
"QPushButton:hover {\n"
" border: 2px solid rgb(0,143,150);\n"
" background-color: rgb(0,143,150);\n"
"}\n"
"QPushButton:pressed { \n"
" border: 2px solid rgb(0,143,150);\n"
" background-color: rgb(51,51,51);\n"
"}\n"
"")
self.bn_ok.setText("")
self.bn_ok.setObjectName("bn_ok")
self.gridLayout.addWidget(self.bn_ok, 0, 0, 1, 1)
self.verticalLayout.addWidget(self.frame_bottom)
self.horizontalLayout.addWidget(self.frame)
self.retranslateUi(Error)
QtCore.QMetaObject.connectSlotsByName(Error)
def retranslateUi(self, Error):
_translate = QtCore.QCoreApplication.translate
Error.setWindowTitle(_translate("Error", "Dialog"))
-1433
View File
File diff suppressed because it is too large Load Diff
-57
View File
@@ -1,57 +0,0 @@
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QDialog
from PyQt5 import QtGui
from UI.ui_dialog import Ui_Dialog
import UI.recources_rc
class dialogUi(QDialog):
def __init__(self, parent=None):
super(dialogUi, self).__init__(parent)
self.d = Ui_Dialog()
self.d.setupUi(self)
self.setWindowFlags(Qt.FramelessWindowHint)
self.d.bn_close.clicked.connect(lambda: self.close())
self.d.bn_east.clicked.connect(lambda: self.close())
self.d.bn_west.clicked.connect(lambda: self.close())
self.dragPos = self.pos()
def movedialogWindow(event):
if event.buttons() == Qt.LeftButton:
self.move(self.pos() + event.globalPos() - self.dragPos)
self.dragPos = event.globalPos()
event.accept()
self.d.frame_top.mouseMoveEvent = movedialogWindow
def mousePressEvent(self, event):
self.dragPos = event.globalPos()
def dialogConstrict(self, heading, message, btn1, btn2, icon, parent=None):
self.d.lab_heading.setText(str(heading))
self.d.lab_message.setText(str(message))
self.d.bn_east.setText(str(btn2))
self.d.bn_west.setText(str(btn1))
pixmap = QtGui.QPixmap(icon)
self.d.lab_icon.setPixmap(pixmap)
if parent:
parent_rect = parent.frameGeometry()
self.move(
parent_rect.left() + (parent_rect.width() - self.width()) // 2,
parent_rect.top() + (parent_rect.height() - self.height()) // 2
)
try:
self.d.bn_east.clicked.disconnect()
self.d.bn_west.clicked.disconnect()
except TypeError:
pass
self.d.bn_west.clicked.connect(self.reject)
self.d.bn_east.clicked.connect(self.accept)
-57
View File
@@ -1,57 +0,0 @@
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QDialog, QApplication, QDesktopWidget
from PyQt5 import QtGui
from UI.ui_error import Ui_Error
import UI.recources_rc
class errorUi(QDialog):
def __init__(self, parent=None):
super(errorUi, self).__init__(parent)
self.e = Ui_Error()
self.e.setupUi(self)
self.setWindowFlags(Qt.FramelessWindowHint)
self.setAttribute(Qt.WA_TranslucentBackground)
self.screen = QDesktopWidget().screenGeometry()
self.e.bn_ok.clicked.connect(self.close_app)
self.dragPos = self.pos()
def moveWindow(event):
# MOVE WINDOW
if event.buttons() == Qt.LeftButton:
self.move(self.pos() + event.globalPos() - self.dragPos)
self.dragPos = event.globalPos()
event.accept()
self.e.frame_top.mouseMoveEvent = moveWindow
self.exitApp = False
def mousePressEvent(self, event):
self.dragPos = event.globalPos()
def errorConstrict(self, heading, icon, btnOk, parent=None, exitApp=False):
self.exitApp = exitApp
self.e.lab_heading.setText(heading)
self.e.bn_ok.setText(btnOk)
pixmap2 = QtGui.QPixmap(icon)
self.e.lab_icon.setPixmap(pixmap2)
if parent:
parent_rect = parent.frameGeometry()
self.move(
parent_rect.left() + (parent_rect.width() - self.width()) // 2,
parent_rect.top() + (parent_rect.height() - self.height()) // 2
)
else:
x = (self.screen.width() - self.width()) // 2
y = (self.screen.height() - self.height()) // 2
self.move(x, y)
def close_app(self):
if self.exitApp:
QApplication.quit()
else:
self.close()
-854
View File
@@ -1,854 +0,0 @@
import os
from shutil import rmtree, copytree
from sys import argv
from zipfile import ZipFile, BadZipFile
import requests
import winreg
from PyQt5.QtGui import QDesktopServices, QColor, QBrush, QIcon
from PyQt5.QtWidgets import QMainWindow, QFileDialog, QListWidgetItem, QProgressDialog, QApplication
from PyQt5.QtCore import Qt, QUrl, QTimer, QTranslator, QLocale, QObject, pyqtSignal, QThread
from subprocess import run, CREATE_NO_WINDOW
from pathlib import Path
import UI.ui_main as ui_main
from Libs.ConnectionCheck import ConnectionCheckThread
from Libs.LauncherReinstall import ReinstallThread
from Libs.logger import Logger
from UI_logic.DialogWindow import dialogUi
from UI_logic.ErrorWindow import errorUi
from Libs.GamePath import stellaris_path, launcher_path, get_user_logon_name
from Libs.ServerData import get_dlc_data, get_server_data
from Libs.CreamApiMaker import CreamAPI
from Libs.DownloadThread import DownloaderThread
from Libs.MD5Check import MD5
class SetupWorker(QObject):
log_message = pyqtSignal(str)
initial_data_ready = pyqtSignal(dict)
update_info = pyqtSignal(dict)
dlc_check_complete = pyqtSignal(list)
connection_check_ready = pyqtSignal(object)
finished = pyqtSignal()
def __init__(self, parent=None):
super().__init__(parent)
self.GITHUB_API_URL = "https://api.github.com/repos/seuyh/stellaris-dlc-unlocker"
self.server_url = None
self.server_alturl = None
self.alt_launcher_name = None
self.latest_version = None
def run(self):
try:
dlc_data = get_dlc_data()
try:
user_logon_name = get_user_logon_name()
except:
user_logon_name = os.getlogin()
server_data = get_server_data()
self.server_url = server_data.get('url')
self.server_alturl = server_data.get('alturl')
self.alt_launcher_name = server_data.get('altlauncher')
self.latest_version = server_data.get('version')
path = stellaris_path()
initial_data = {
'dlc_data': dlc_data,
'user_logon_name': user_logon_name,
'server_url': self.server_url,
'server_alturl': self.server_alturl,
'alt_launcher_name': self.alt_launcher_name,
'latest_version': self.latest_version,
'path': path
}
self.initial_data_ready.emit(initial_data)
self.kill_process('Paradox Launcher.exe')
self.kill_process('stellaris.exe')
if self.server_url:
connection_thread = ConnectionCheckThread(self.server_url, self.GITHUB_API_URL)
self.connection_check_ready.emit(connection_thread)
if path and self.server_url:
md5_checker = MD5(f"{path}\\dlc", self.server_url)
not_updated_dlc = md5_checker.check_files()
self.dlc_check_complete.emit(not_updated_dlc)
else:
self.dlc_check_complete.emit([])
try:
print(f"Checking updates via GitHub: {self.GITHUB_API_URL}")
releases_url = f"{self.GITHUB_API_URL}/releases/latest"
response = requests.get(releases_url, timeout=5)
if response.status_code == 200:
update_info = response.json()
update_info['source'] = 'github'
else:
raise Exception(f"GitHub returned {response.status_code}")
except Exception as e:
print(f"Update check failed/skipped ({e}). Using server fallback.")
update_info = {
'tag_name': self.latest_version,
'source': 'server'
}
self.update_info.emit(update_info)
except Exception as e:
print(f"CRITICAL ERROR IN WORKER: {e}")
self.log_message.emit(f"Critical error: {e}")
finally:
self.finished.emit()
def kill_process(self, process_name):
self.log_message.emit(f'Killing {process_name}')
try:
run(["taskkill", "/F", "/IM", process_name], check=True, creationflags=CREATE_NO_WINDOW)
except:
self.log_message.emit(f'No process named {process_name}')
class MainWindow(QMainWindow, ui_main.Ui_MainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.translator = QTranslator()
self.setWindowFlags(Qt.FramelessWindowHint)
self.setupUi(self)
self.setWindowState(Qt.WindowActive)
self.error = errorUi()
self.diag = dialogUi()
self.game_path = None
self.not_updated_dlc = []
self.dlc_data = []
self.user_logon_name = ''
self.server_url, self.server_alturl, self.alt_launcher_name, self.latest_version = '', '', '', ''
self.connection_thread = None
self.draggable_elements = [self.frame_user, self.server_status, self.gh_status, self.lappname_title,
self.frame_top]
for element in self.draggable_elements:
element.mousePressEvent = self.mousePressEvent
element.mouseMoveEvent = self.mouseMoveEvent
element.mouseReleaseEvent = self.mouseReleaseEvent
self.is_dragging = False
self.last_mouse_position = None
self.launcher_downloaded = None
self.continued = False
self.downloaded_launcher_dir = None
self.parent_directory = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
self.is_downloading = False
self.download_thread = None
self.creamapidone = False
self.current_version = '2.52'
self.version_label.setText(f'Ver. {str(self.current_version)}')
self.next_button.setEnabled(False)
self.original_next_button_text = self.next_button.text()
self.spinner_chars = ['/', '-', '\\', '|']
self.spinner_index = 0
self.spinner_timer = QTimer(self)
self.spinner_timer.timeout.connect(self.update_spinner)
self.copy_files_radio.setVisible(False)
self.download_files_radio.setVisible(False)
self.launcher_reinstall_radio.setVisible(False)
self.progress_label.setVisible(False)
self.dlc_download_label.setVisible(False)
self.dlc_download_progress_bar.setVisible(False)
self.current_dlc_label.setVisible(False)
self.current_dlc_progress_bar.setVisible(False)
self.lauch_game_checkbox.setVisible(False)
self.done_button.setVisible(False)
self.speed_label.setVisible(False)
self.update_dlc_button.setVisible(False)
self.old_dlc_text.setVisible(False)
#self.gh_status.setVisible(False)
self.en_lang.toggled.connect(self.switch_to_english)
self.ru_lang.toggled.connect(self.switch_to_russian)
self.cn_lang.toggled.connect(self.switch_to_chinese)
self.setWindowTitle("Stellaris DLC Unlocker")
self.setWindowIcon(QIcon(f'{self.parent_directory}/UI/icons/stellaris.png'))
self.bn_bug.clicked.connect(lambda: self.stackedWidget.setCurrentIndex(2))
self.path_choose_button.clicked.connect(self.browse_folder)
self.next_button.clicked.connect(
lambda: (
setattr(self, 'continued', True),
self.stackedWidget.setCurrentIndex(1),
self.old_dlc_show()
)
)
self.bn_home.clicked.connect(lambda: self.stackedWidget.setCurrentIndex(1 if self.continued else 0))
self.unlock_button.clicked.connect(self.unlock)
self.done_button.clicked.connect(self.finish)
self.bn_close.clicked.connect(
lambda: self.close() if self.dialogexec(self.tr("Close"), self.tr("Exit Unlocker?"), self.tr("No"),
self.tr("Yes")) else None)
self.bottom_label_github.setText(
'<html><head/><body><p>'
'<a href="https://github.com/seuyh/stellaris-dlc-unlocker">'
'<span style=" text-decoration: underline; color:#008f96;">GitHub Repo</span></a>'
' | '
'<a href="https://femboysex.pro/">'
'<span style=" text-decoration: underline; color:#008f96;">Site</span></a>'
'</p></body></html>'
)
self.bottom_label_github.linkActivated.connect(self.open_link_in_browser)
#self.bottom_label_github.setVisible(False)
self.logger = Logger('unlocker.log', self.log_widget)
self.log_widget.clear()
self.setup_thread = QThread()
self.setup_worker = SetupWorker()
self.setup_worker.moveToThread(self.setup_thread)
self.setup_thread.started.connect(self.setup_worker.run)
self.setup_worker.finished.connect(self.on_loading_complete)
self.setup_worker.finished.connect(self.setup_thread.quit)
self.setup_worker.finished.connect(self.setup_worker.deleteLater)
self.setup_thread.finished.connect(self.setup_thread.deleteLater)
self.setup_worker.initial_data_ready.connect(self.on_initial_data_ready)
self.setup_worker.dlc_check_complete.connect(self.on_dlc_check_complete)
self.setup_worker.update_info.connect(self.on_update_info_ready)
self.setup_worker.connection_check_ready.connect(self.on_connection_check_ready)
self.setup_worker.log_message.connect(self.add_log_message)
def add_log_message(self, message):
print(message)
def update_spinner(self):
self.spinner_index = (self.spinner_index + 1) % len(self.spinner_chars)
self.next_button.setText(self.spinner_chars[self.spinner_index])
def on_loading_complete(self):
print("All background tasks are complete.")
self.spinner_timer.stop()
self.next_button.setText(self.original_next_button_text)
self.retranslateUi(self)
self.next_button.setEnabled(True)
def on_initial_data_ready(self, data):
self.dlc_data = data['dlc_data']
self.user_logon_name = data['user_logon_name']
self.server_url = data['server_url']
self.server_alturl = data['server_alturl']
self.alt_launcher_name = data['alt_launcher_name']
self.latest_version = data['latest_version']
if data['path']:
path = data['path']
print(f'Auto detected game path: {path}')
self.game_path_line.setText(path)
self.game_path = os.path.normpath(path)
else:
print('Cant detect game path')
def on_dlc_check_complete(self, not_updated_dlc):
self.not_updated_dlc = not_updated_dlc
if self.game_path:
self.loadDLCNames()
def on_connection_check_ready(self, connection_thread):
self.connection_thread = connection_thread
self.connection_thread.github_status_checked.connect(self.handle_github_status)
self.connection_thread.server_status_checked.connect(self.handle_server_status)
self.connection_thread.start()
def on_update_info_ready(self, latest_release):
if not latest_release:
return
remote_version = latest_release.get('tag_name')
source = latest_release.get('source', 'server')
current_version = self.current_version
if remote_version and remote_version > current_version:
if self.dialogexec(self.tr('New version'),
self.tr(f'New version {remote_version} found!'),
self.tr('Cancel'), self.tr('Update')):
url_to_open = None
if source == 'github':
for asset in latest_release.get('assets', []):
if asset.get('name', '').endswith('.exe'):
url_to_open = asset.get('browser_download_url')
break
if not url_to_open:
url_to_open = f"https://{self.server_url}/unlocker/Stellaris-DLC-Unlocker.exe"
self.open_link_in_browser(url_to_open)
self.close()
elif remote_version and remote_version < current_version:
self.errorexec(self.tr("Beta"), self.tr("Ok"), exitApp=False)
else:
print(f"Unlocker is up to date. (Checked via {source})")
def get_app_language(self):
lang = QLocale.system().name().lower()
print(f"Detected language: {lang}")
if not lang:
return "en"
lang = lang.lower()
if lang.startswith("ru"):
return "ru"
elif lang.startswith("zh"):
return "zh"
else:
return "en"
def apply_initial_language(self):
self.set_language_radio(self.get_app_language())
def set_language_radio(self, lang):
if lang == "ru":
self.ru_lang.setChecked(True)
elif lang == "zh":
self.cn_lang.setChecked(True)
else:
self.en_lang.setChecked(True)
self.apply_language(lang)
def apply_language(self, lang):
app = QApplication.instance()
app.removeTranslator(self.translator)
print(f"Trying to apply language: {lang}")
translation_path = ""
if lang == "ru":
translation_path = os.path.join(self.parent_directory, "UI", "translations", "ru_RU.qm")
elif lang == "zh":
translation_path = os.path.join(self.parent_directory, "UI", "translations", "zh_CN.qm")
if translation_path and self.translator.load(translation_path):
app.installTranslator(self.translator)
print(f"{lang} translate Successfully loaded")
else:
if translation_path:
print(f"Unable to load {lang}.qm")
self.retranslateUi(self)
def on_full_reinstall_checkbox_toggled(self, checked):
if checked:
if self.dialogexec("",
self.tr("<html><head/><body><p>This function will delete all saves and presets of mods.</p><p>It is only needed if something did not work during the normal installation</p></body></html>"),
self.tr("No"), self.tr("Yes")):
self.full_reinstall_checkbox.setChecked(True)
self.skip_launcher_reinstall_checbox.setChecked(False)
else:
self.full_reinstall_checkbox.setChecked(False)
def on_alternative_unloc_checkbox_toggled(self, checked):
if checked:
self.skip_launcher_reinstall_checbox.setChecked(False)
def on_skip_launcher_reinstall_checbox_toggled(self, checked):
if checked:
self.full_reinstall_checkbox.setChecked(False)
self.alternative_unloc_checkbox.setChecked(False)
def showEvent(self, event):
super(MainWindow, self).showEvent(event)
self.apply_initial_language()
print('Start connection check')
if not self.setup_thread.isRunning():
self.spinner_timer.start(150)
self.setup_thread.start()
print('Start updates check')
def switch_to_russian(self):
if self.ru_lang.isChecked():
self.apply_language("ru")
print("ru_RU translate Successfully loaded")
def switch_to_english(self):
if self.en_lang.isChecked():
self.apply_language("en")
print("en-US translate Successfully loaded")
def switch_to_chinese(self):
if self.cn_lang.isChecked():
self.apply_language("zh")
print("zh_CN translate Successfully loaded")
@staticmethod
def open_link_in_browser(url):
print(f"Attempting to open URL: {url}")
QDesktopServices.openUrl(QUrl(url))
def mousePressEvent(self, event):
if event.button() == Qt.LeftButton:
self.is_dragging = True
self.last_mouse_position = event.globalPos() - self.frameGeometry().topLeft()
def mouseMoveEvent(self, event):
if self.is_dragging:
self.move(event.globalPos() - self.last_mouse_position)
def mouseReleaseEvent(self, event):
if event.button() == Qt.LeftButton:
self.is_dragging = False
def dialogexec(self, heading, message, btn1, btn2, icon=":/icons/icons/1x/errorAsset 55.png"):
print(f'Dialog exec {heading, message}')
dialogUi.dialogConstrict(self.diag, heading, message, btn1, btn2, icon, self)
return self.diag.exec_()
def errorexec(self, heading, btnOk, icon=":/icons/icons/1x/closeAsset 43.png", exitApp=False):
print(f'Error exec {heading, exitApp}')
errorUi.errorConstrict(self.error, heading, icon, btnOk, self, exitApp)
self.error.exec_()
def remove_compatibility(self, exe_path):
exe_path = os.path.abspath(exe_path)
keys = [
(winreg.HKEY_CURRENT_USER, r"Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers"),
(winreg.HKEY_LOCAL_MACHINE, r"Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers"),
]
for root, subkey in keys:
try:
with winreg.OpenKey(root, subkey, 0, winreg.KEY_ALL_ACCESS) as key:
try:
value, regtype = winreg.QueryValueEx(key, exe_path)
print(f"Found compatibility parameter: {value} in {root}\\{subkey}")
winreg.DeleteValue(key, exe_path)
print(f"Compatibility parameter deleted: {exe_path}")
except FileNotFoundError:
print(f"Compatibility for ({subkey}) parameters are not set")
pass
except FileNotFoundError:
continue
def browse_folder(self):
directory = QFileDialog.getExistingDirectory(self, self.tr("Choose Stellaris path"),
self.game_path_line.text())
directory = os.path.normpath(directory)
if directory and os.path.isfile(os.path.join(directory, "stellaris.exe")):
self.game_path_line.setText(directory)
self.game_path = directory
print(f'Path browsed: {self.game_path}')
if self.server_url:
md5_checker = MD5(f"{self.game_path}\\dlc", self.server_url)
self.on_dlc_check_complete(md5_checker.check_files())
else:
print('Path browsed incorrectly')
self.errorexec(self.tr("This is not Stellaris path"), self.tr("Ok"))
def path_check(self):
path = os.path.normpath(self.game_path_line.text())
if path and os.path.isfile(os.path.join(path, "stellaris.exe")):
print(f'Game path: {path}')
return path
self.errorexec(self.tr("Please choose game path"), self.tr("Ok"))
return False
def old_dlc_show(self):
if self.not_updated_dlc:
print(f"Not updated DLCs: {self.not_updated_dlc}")
self.update_dlc_button.setVisible(True)
self.old_dlc_text.setVisible(True)
else:
self.update_dlc_button.setChecked(False)
print("All DlCs is up to date or server return error")
def handle_github_status(self, status):
self.gh_status.setChecked(status)
print('GitHub connection established' if status else 'GitHub connection cant be established')
if not status:
print("Warning: GitHub is unavailable. Updates will be handled via fallback server.")
def handle_server_status(self, status):
self.server_status.setChecked(status)
print('Server connection established' if status else 'Server connection cant be established')
if not status:
if self.dialogexec(self.tr('Connection error'), self.tr(
'Cant establish connection with server\nCheck your connection or you can try download DLC directly\nUnzip downloaded "dlc" folder to game folder\nThen you can continue'),
self.tr("Exit"), self.tr("Open")):
self.open_link_in_browser(self.server_alturl)
self.alternative_unloc_checkbox.setEnabled(False)
else:
self.close()
def loadDLCNames(self):
self.dlc_status_widget.clear()
for dlc in self.dlc_data:
dlc_name = dlc.get('dlc_name', '').strip()
if not dlc_name:
continue
item = QListWidgetItem(dlc_name)
status_color = self.checkDLCStatus(dlc.get('dlc_folder', ''))
if status_color != 'black':
item.setForeground(QBrush(QColor(status_color)))
if status_color == "orange":
item.setText(item.text() + " (old)")
self.dlc_status_widget.addItem(item)
def checkDLCStatus(self, dlc_folder):
if not dlc_folder or not self.game_path:
return "black"
dlc_path_folder = os.path.join(self.game_path, "dlc", dlc_folder)
dlc_path_zip = os.path.join(self.game_path, "dlc", f'{dlc_folder}.zip')
if os.path.exists(dlc_path_folder) or os.path.exists(dlc_path_zip):
return "orange" if dlc_folder in self.not_updated_dlc else "teal"
else:
return "LightCoral"
def full_reinstall(self):
try:
print(f'Deleting documents folder...')
user_home = os.path.join("C:\\Users", self.user_logon_name)
rmtree(os.path.join(user_home, "Documents", "Paradox Interactive", "Stellaris"))
except Exception as e:
print(f'Cant delete documents folder: {e}')
try:
print(f'Deleting dlc folder...')
rmtree(os.path.join(self.game_path, "dlc"))
except Exception as e:
print(f'Cant delete dlc folder: {e}')
def download_alt_method(self):
print('Downloading alt launcher')
file = argv[0]
dir = os.path.dirname(file)
print(f'Path to download: {dir}')
self.downloaded_launcher_dir = f'{dir}\\{self.alt_launcher_name}'
print(f'Download path {self.downloaded_launcher_dir}')
print(f'Path exist: {os.path.isfile(self.downloaded_launcher_dir)}')
if os.path.isfile(self.downloaded_launcher_dir):
return
progress_dialog = QProgressDialog(self.tr('Alt launcher downloading'), None, 0, 100)
progress_dialog.setWindowTitle(self.tr('Downloading'))
progress_dialog.setWindowModality(2)
progress_dialog.show()
download_breaked = 0
try:
response = requests.get(
f"https://{self.server_url}/unlocker/{self.alt_launcher_name}",
stream=True)
total_size_in_bytes = int(response.headers.get('content-length', 0))
block_size = 1024
downloaded_bytes = 0
with open(self.downloaded_launcher_dir, 'wb') as file:
for data in response.iter_content(block_size):
if progress_dialog.wasCanceled():
download_breaked = 1
break
file.write(data)
downloaded_bytes += len(data)
progress = int(downloaded_bytes / total_size_in_bytes * 100)
progress_dialog.setValue(progress)
except Exception as e:
print(f'Error while download alt launcher. Please try again or dont use this method Error: {e}')
self.errorexec(self.tr("Cant download alt launcher"), self.tr("Ok"),
exitApp=True)
finally:
progress_dialog.close()
if not download_breaked:
self.launcher_downloaded = True
print(f'Launcher downloaded {self.downloaded_launcher_dir}')
def unlock(self):
print('Unlocking...')
if not self.path_check():
print('Error: incorrect path, return')
return
self.game_path = os.path.normpath(self.game_path_line.text())
print('Unlock started')
print(
f'Settings:\nPath: {self.game_path}\nFull reinstall: {self.full_reinstall_checkbox.isChecked()}\nAlt unlock: {self.alternative_unloc_checkbox.isChecked()}\nSkip reinstall: {self.skip_launcher_reinstall_checbox.isChecked()}')
self.unlock_button.setEnabled(False)
self.game_path_line.setEnabled(False)
self.path_choose_button.setEnabled(False)
self.full_reinstall_checkbox.setEnabled(False)
self.alternative_unloc_checkbox.setEnabled(False)
self.skip_launcher_reinstall_checbox.setEnabled(False)
self.update_dlc_button.setEnabled(False)
self.copy_files_radio.setVisible(True)
self.download_files_radio.setVisible(True)
self.launcher_reinstall_radio.setVisible(True)
self.progress_label.setVisible(True)
self.dlc_download_label.setVisible(True)
self.dlc_download_progress_bar.setVisible(True)
self.current_dlc_label.setVisible(True)
self.current_dlc_progress_bar.setVisible(True)
self.speed_label.setVisible(True)
if self.full_reinstall_checkbox.isChecked():
self.full_reinstall()
if self.alternative_unloc_checkbox.isChecked():
self.download_alt_method()
if not os.path.exists(os.path.join(self.game_path, "dlc")):
os.makedirs(os.path.join(self.game_path, "dlc"))
if self.game_path:
try:
self.remove_compatibility(f"{self.game_path}\\stellaris.exe")
except Exception as e:
print(f"Cant remove compatibility: {e}")
pass
self.is_downloading = True
if self.update_dlc_button.isChecked():
print("Updating DLCs...")
self.delete_folders(f"{self.game_path}\\dlc", self.not_updated_dlc)
self.not_updated_dlc = []
self.loadDLCNames()
self.creamapi_maker = CreamAPI()
self.creamapi_maker.progress_signal.connect(self.update_creamapi_progress)
self.creamapi_maker.start()
self.dlc_count = 0
self.dlc_downloaded = 0
self.download_queue = []
def start_next_download():
if self.download_queue:
file_url, save_path = self.download_queue.pop(0)
print(f"Now downloading: {os.path.basename(file_url)}")
self.download_thread = DownloaderThread(file_url, save_path, self.dlc_downloaded, self.dlc_count)
self.download_thread.progress_signal.connect(self.update_progress)
self.download_thread.progress_signal_2.connect(self.update_progress_2)
self.download_thread.error_signal.connect(self.show_error)
self.download_thread.speed_signal.connect(self.show_download_speed)
self.download_thread.finished.connect(start_next_download)
self.download_thread.start()
for item in self.dlc_data:
if 'dlc_folder' in item and item['dlc_folder']:
self.dlc_count += 1
for dlc in self.dlc_data:
dlc_folder = dlc['dlc_folder']
if dlc_folder == '':
continue
file_url = f"https://{self.server_url}/unlocker/{dlc_folder}.zip"
save_path = os.path.join(self.game_path, 'dlc', f'{dlc_folder}.zip')
dlc_path = os.path.join(self.game_path, 'dlc', dlc_folder)
if not os.path.exists(dlc_path) and self.is_invalid_zip(save_path):
if os.path.exists(save_path):
os.remove(save_path)
self.download_queue.append((file_url, save_path))
else:
self.dlc_downloaded += 1
if self.dlc_count > 0:
self.update_progress(int((self.dlc_downloaded / self.dlc_count) * 100))
if self.download_queue:
print('Starting downloads...')
if self.server_status.isChecked():
start_next_download()
else:
self.download_files_radio.setVisible(False)
self.progress_label.setVisible(False)
self.dlc_download_label.setVisible(False)
self.dlc_download_progress_bar.setVisible(False)
self.current_dlc_label.setVisible(False)
self.current_dlc_progress_bar.setVisible(False)
self.speed_label.setVisible(False)
self.reinstall()
def update_creamapi_progress(self, value):
if value == 100:
self.creamapidone = True
self.download_complete()
@staticmethod
def is_invalid_zip(path):
if not os.path.exists(path):
return True
if os.path.getsize(path) == 0:
return True
try:
with ZipFile(path, 'r') as zf:
if zf.testzip() is not None:
return True
except BadZipFile:
return True
return False
@staticmethod
def delete_folders(base_path, folders):
base_path = Path(base_path)
for name in folders:
dir_path = base_path / name
try:
if dir_path.is_dir():
rmtree(dir_path)
print(f"Deleted: {dir_path}")
else:
pass
except Exception as e:
print(f"Can't delete {dir_path}: {e}")
def update_progress(self, value, by_download=False):
self.dlc_download_progress_bar.setValue(value)
if by_download:
self.dlc_downloaded += 1
if self.dlc_count > 0:
self.update_progress(int((self.dlc_downloaded / self.dlc_count) * 100))
self.loadDLCNames()
if value == 100:
self.speed_label.setText(f"")
self.update_progress_2(100)
# self.download_text_dlc(' ')
self.download_complete()
def update_progress_2(self, value):
self.current_dlc_progress_bar.setValue(value)
def show_download_speed(self, speed):
self.speed_label.setText(f'{speed}MB/s')
def show_error(self, error_message):
print(f'DownloadThread error signal: {error_message}')
self.errorexec(self.tr("File download error"), self.tr("Exit"), exitApp=True)
def show_reinstall_error(self, error_message):
print(f'ReinstallThread error signal: {error_message}')
self.errorexec(self.tr("Launcher reinstall error"), self.tr("Exit"), exitApp=True)
def download_complete(self):
if self.dlc_download_progress_bar.value() == 100 and self.creamapidone == True:
print('DLC downloaded')
self.reinstall()
def reinstall(self):
self.download_files_radio.setChecked(True)
print('Reinstalling')
paradox_folder1, paradox_folder2, paradox_folder3, paradox_folder4 = launcher_path()
print(f"Launcher folders: [{paradox_folder1, paradox_folder2, paradox_folder3, paradox_folder4}]")
if not self.skip_launcher_reinstall_checbox.isChecked():
self.reinstall_thread = ReinstallThread(self.game_path, paradox_folder1, paradox_folder2, paradox_folder3,
paradox_folder4, self.launcher_downloaded,
self.downloaded_launcher_dir, self.user_logon_name)
self.reinstall_thread.error_signal.connect(self.show_reinstall_error)
self.reinstall_thread.continue_reinstall.connect(self.finalize_reinstallation)
self.reinstall_thread.start()
else:
print('Reinstalling skipped')
self.finalize_reinstallation(paradox_folder1)
def finalize_reinstallation(self, paradox_folder1):
self.launcher_reinstall_radio.setChecked(True)
print('Starting post-install file processing...')
try:
print('Unzipping DLC files...')
zip_files = [f for f in os.listdir(os.path.join(self.game_path, 'dlc')) if f.endswith('.zip')]
if not zip_files:
print("No .zip files found in DLC folder to unpack.")
for zip_file in zip_files:
self.unzip_and_replace(zip_file)
except Exception as e:
print(f"An error occurred during unzipping: {e}")
self.errorexec(self.tr("Error while unzipping"), self.tr("Exit"), exitApp=True)
return
try:
all_launcher_folders = [
os.path.join(paradox_folder1, item)
for item in os.listdir(paradox_folder1)
if item.startswith("launcher") and os.path.isdir(os.path.join(paradox_folder1, item))
]
except FileNotFoundError:
self.errorexec(self.tr("Launcher directory not found!"), self.tr("Exit"), exitApp=True)
return
if not all_launcher_folders:
print("CRITICAL: No launcher folders found after installation.")
self.errorexec(self.tr("Error unknown launcher"), self.tr("Exit"), exitApp=True)
return
print(f"Found {len(all_launcher_folders)} launcher folder(s) to patch.")
was_any_folder_patched = False
for launcher_folder in all_launcher_folders:
print(f"\n--- Processing: {launcher_folder} ---")
copy_to_path = None
path1 = os.path.join(launcher_folder, 'resources', 'app.asar.unpacked', 'node_modules', 'greenworks', 'lib')
path2 = os.path.join(launcher_folder, 'resources', 'app', 'dist', 'main')
if os.path.exists(path1):
copy_to_path = path1
elif os.path.exists(path2):
copy_to_path = path2
if copy_to_path:
was_any_folder_patched = True
print(f"Valid target path found: {copy_to_path}")
try:
copytree(f'{self.parent_directory}/creamapi_launcher_files', copy_to_path, dirs_exist_ok=True)
print("Launcher-specific files copied successfully.")
except Exception as e:
print(f"ERROR copying launcher files: {e}")
self.errorexec(self.tr("Error copying files"), self.tr("Exit"), exitApp=True)
return
try:
xdelta_path = os.path.join(launcher_folder, 'xdelta3.exe')
if os.path.exists(xdelta_path):
os.remove(xdelta_path)
print("xdelta3.exe removed.")
except OSError as e:
print(f"Could not remove xdelta3.exe: {e}")
else:
print("No valid target path found in this folder. Skipping.")
if not was_any_folder_patched:
print("CRITICAL: Found launcher folders, but none contained a valid target path.")
self.errorexec(self.tr("Error unknown launcher"), self.tr("Exit"), exitApp=True)
return
print("\nCopying game-specific files...")
copytree(f'{self.parent_directory}/creamapi_steam_files', self.game_path, dirs_exist_ok=True)
print("Copy completed.")
self.copy_files_radio.setChecked(True)
self.lauch_game_checkbox.setVisible(True)
self.update_dlc_button.setVisible(False)
self.old_dlc_text.setVisible(False)
self.done_button.setVisible(True)
print('All done!')
def unzip_and_replace(self, dlc_path):
zip_path = os.path.join(self.game_path, 'dlc', dlc_path)
extract_folder = os.path.join(self.game_path, 'dlc')
os.makedirs(extract_folder, exist_ok=True)
try:
with ZipFile(zip_path, 'r') as zip_ref:
zip_ref.extractall(extract_folder)
os.remove(zip_path)
except Exception as e:
print(f'Error while unzipping {e}')
self.errorexec(self.tr("Error while unzipping"), self.tr("Exit"), exitApp=True)
def finish(self):
if self.lauch_game_checkbox.isChecked():
try:
run('start steam://run/281990', shell=True, creationflags=CREATE_NO_WINDOW)
except:
pass
self.close()
+39
View File
@@ -0,0 +1,39 @@
# add the dlc you want to fake here
# APPID = DLC Name
# copying a file from windows creamapi will not work, you need to fix line endings
# the DLC id's below are for HOI4
[config]
# if we should also list the DLCs you own and not just the ones on this list
issubscribedapp_on_false_use_real = true
[methods]
disable_steamapps_issubscribedapp = false
[dlc]
447680 = Stellaris: Symbols of Domination
447683 = Stellaris: Arachnoid Portrait Pack
447681 = Stellaris: Sign-up Campaign Bonus
498870 = Stellaris: Plantoids Species Pack
462720 = Stellaris: Creatures of the Void
518910 = Stellaris: Leviathans Story Pack
554350 = Stellaris: Horizon Signal
553280 = Stellaris: Utopia
633310 = Stellaris: Anniversary Portraits
642750 = Stellaris: Synthetic Dawn
716670 = Stellaris: Apocalypse
756010 = Stellaris: Humanoids Species Pack
844810 = Stellaris: Distant Stars Story Pack
944290 = Stellaris: MegaCorp
1045980 = Stellaris: Ancient Relics Story Pack
1140000 = Stellaris: Lithoids Species Pack
1140001 = Stellaris: Federations
1341520 = Stellaris: Necroids Species Pack
1522090 = Stellaris: Nemesis
1749080 = Stellaris: Aquatics Species Pack
1889490 = Stellaris: Overlord
2115770 = Stellaris: Toxoids Species Pack
2277860 = Stellaris: First Contact Story Pack
2380030 = Stellaris: Galactic Paragons
2534090 = Stellaris: Astral Planes
2840100 = Stellaris: The Machine Age
2863180 = Stellaris: Rick The Cube Species Portrait
Binary file not shown.
Binary file not shown.
-5
View File
@@ -1,5 +0,0 @@
{"url": "femboysex.pro", "alturl": "https://mega.nz/folder/4zFRnD6a#aVGAK32ZHPxCp7bMtG87BA", "altlauncher": "launcher-installer-windows_2024.14.msi", "server_msg": ""}
-15
View File
@@ -1,15 +0,0 @@
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication
from UI_logic.MainWindow import MainWindow
import sys
if hasattr(Qt, 'AA_EnableHighDpiScaling'):
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True)
if hasattr(Qt, 'AA_UseHighDpiPixmaps'):
QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps, True)
if __name__ == '__main__':
app = QApplication(sys.argv)
print(f"Using AA_EnableHighDpiScaling > {QApplication.testAttribute(Qt.AA_EnableHighDpiScaling)}")
print(f"Using AA_UseHighDpiPixmaps > {QApplication.testAttribute(Qt.AA_UseHighDpiPixmaps)}")
main_window = MainWindow()
main_window.show()
app.exec_()
-4
View File
@@ -1,4 +0,0 @@
PyQt5==5.15.10
PyQt5_sip==12.13.0
Requests==2.32.5
vdf==3.4