mirror of
https://github.com/seuyh/stellaris-dlc-unlocker.git
synced 2026-07-28 14:47:05 -07:00
Update
This commit is contained in:
@@ -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)
|
||||
@@ -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')
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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_()
|
||||
@@ -1,78 +0,0 @@
|
||||
# Stellaris DLC Unlocker
|
||||
|
||||

|
||||
|
||||
| [Русский](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" В последствии продукт может изменяться, дополняться, улучшаться. Не исключено наличие багов, недочетов, вылетов.*
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
# Stellaris DLC Unlocker
|
||||
|
||||

|
||||
|
||||
| [Русский](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.*
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
# 群星DLC解锁器
|
||||
|
||||

|
||||
|
||||
| [Русский](README.md) | [English](README_EN.md) | [中文](README_ZHCN.md) |
|
||||
|
||||
---
|
||||
|
||||
## 项目描述
|
||||
用于自动解锁和安装 Stellaris(Steam 版)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
@@ -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()
|
||||
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -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><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
||||
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
|
||||
p, li { white-space: pre-wrap; }
|
||||
</style></head><body style=" font-family:'Segoe UI'; font-size:10pt; font-weight:400; font-style:normal;">
|
||||
<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-weight:600; text-decoration: underline; color:#008f96;">Stellaris DLC Unlocker</span></p>
|
||||
<p align="center" style="-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:'MS Shell Dlg 2'; font-weight:600; text-decoration: underline;"><br /></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-weight:600; text-decoration: underline;">Requirements</span><span style=" font-family:'MS Shell Dlg 2';">:</span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2';"> </span><span style=" font-family:'MS Shell Dlg 2'; font-weight:600;">License</span><span style=" font-family:'MS Shell Dlg 2';">: Steam.</span></p>
|
||||
<p style="-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:'MS Shell Dlg 2'; font-weight:600; text-decoration: underline;"><br /></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-weight:600; text-decoration: underline;">Installation</span><span style=" font-family:'MS Shell Dlg 2';">:</span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2';"> Follow the installer instructions. Installation is almost entirely automatic. <br /></span></p>
|
||||
<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-weight:600; text-decoration: underline; color:#008f96;">Terms Of Use</span></p>
|
||||
<p style="-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:'MS Shell Dlg 2'; color:#0000ff;"><br /></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2';">1. This unlocker is distributed absolutely free of charge. Any commercial use of this unlocker is prohibited.</span></p>
|
||||
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2';">2. THIS UNLOCKER IS PROVIDED &quot;AS IS&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.</span></p>
|
||||
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2';">3. All rights not expressly granted here are reserved by the copyright holders.</span></p>
|
||||
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2';">4. Installation and use of this modification implies that you have read and understand the terms of this license agreement and agree to them.</span></p>
|
||||
<p style="-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:'MS Shell Dlg 2';"><br /></p></body></html></source>
|
||||
<extracomment>Welcome page text</extracomment>
|
||||
<translation><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
||||
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
|
||||
p, li { white-space: pre-wrap; }
|
||||
</style></head><body style=" font-family:'Segoe UI'; font-size:10pt; font-weight:400; font-style:normal;">
|
||||
<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-weight:600; text-decoration: underline; color:#008f96;">Stellaris DLC Unlocker</span></p>
|
||||
<p align="center" style="-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:'MS Shell Dlg 2'; font-weight:600; text-decoration: underline;"><br /></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-weight:600; text-decoration: underline;">Требования</span><span style=" font-family:'MS Shell Dlg 2';">:</span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2';"> </span><span style=" font-family:'MS Shell Dlg 2'; font-weight:600;">Лицензия</span><span style=" font-family:'MS Shell Dlg 2';">: Steam.</span></p>
|
||||
<p style="-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:'MS Shell Dlg 2'; font-weight:600; text-decoration: underline;"><br /></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-weight:600; text-decoration: underline;">Установка</span><span style=" font-family:'MS Shell Dlg 2';">:</span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2';"> Следуйте инструкциям инсталлятора. Установка почти полностью автоматическая. <br /></span></p>
|
||||
<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-weight:600; text-decoration: underline; color:#008f96;">Правила использования</span></p>
|
||||
<p style="-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:'MS Shell Dlg 2'; color:#0000ff;"><br /></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2';">1. Данный анлокер распространяется абсолютно бесплатно. Любое коммерческое использование запрещается.</span></p>
|
||||
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2';">2. ДАННЫЙ АНЛОКЕР ПОСТАВЛЯЕТСЯ ПО ПРИНЦИПУ «AS IS». НИКАКИХ ГАРАНТИЙ НЕ ПРИЛАГАЕТСЯ И НЕ ПРЕДУСМАТРИВАЕТСЯ. ВЫ ИСПОЛЬЗУЕТЕ ЭТУ МОДИФИКАЦИЮ ОРИГИНАЛЬНОЙ ИГРЫ НА СВОЙ СТРАХ И РИСК. АВТОРЫ МОДИФИКАЦИИ НЕ БУДУТ ОТВЕЧАТЬ НИ ЗА КАКИЕ ПОТЕРИ ИЛИ ИСКАЖЕНИЯ ДАННЫХ, ЛЮБУЮ УПУЩЕННУЮ ВЫГОДУ В ПРОЦЕССЕ ИСПОЛЬЗОВАНИЯ ИЛИ НЕПРАВИЛЬНОГО ИСПОЛЬЗОВАНИЯ ДАННОЙ МОДИФИКАЦИИ.</span></p>
|
||||
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2';">3. Все права, не предоставленные здесь явно, сохраняются за правообладателями.</span></p>
|
||||
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2';">4. Установка и использование данной модификации означает, что вы ознакомились и понимаете положения настоящего лицензионного соглашения и согласны с ними.</span></p>
|
||||
<p style="-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:'MS Shell Dlg 2';"><br /></p></body></html></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><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></source>
|
||||
<extracomment>full reinstall tooltip</extracomment>
|
||||
<translation><html><head/><body><p>Эта функция удалит все сохранения и пресеты модов.</p><p>Необходима только при проблемах с нормальной разблокировкой</p></body></html></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><html><head/><body><p>It is used if it is impossible to automatically reinstall the launcher.</p><p>The program will assume that the launcher has already been reinstalled before</p></body></html></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><html><head/><body><p>Использовать при проблемах с автоматической переустановкой лаунчера.</p><p>Программа будет считать, что переустановка уже была произведена</p></body></html></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><html><head/><body><p>Uses a different unlock method</p></body></html></source>
|
||||
<extracomment>Alternative unlock tooltip</extracomment>
|
||||
<translation><html><head/><body><p>Использует иной метод разблокировки</p></body></html></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><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
||||
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
|
||||
p, li { white-space: pre-wrap; }
|
||||
</style></head><body style=" font-family:'Segoe UI'; font-size:8.25pt; font-weight:400; font-style:normal;">
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-size:12pt; color:#ff0000;">Old DLCs detected! </span><span style=" font-family:'MS Shell Dlg 2'; font-size:12pt; color:#ffffff;">You can just update it while unlocking</span></p></body></html></source>
|
||||
<translatorcomment>Update old DLCs text</translatorcomment>
|
||||
<translation><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
||||
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
|
||||
p, li { white-space: pre-wrap; }
|
||||
</style></head><body style=" font-family:'Segoe UI'; font-size:8.25pt; font-weight:400; font-style:normal;">
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-size:12pt; color:#ff0000;">Обнаружены старые DLC! </span><span style=" font-family:'MS Shell Dlg 2'; font-size:12pt; color:#ffffff;">Можно обновить их вместе с разблокировкой</span></p></body></html></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'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 "dlc" folder to game folder
|
||||
Then you can continue</source>
|
||||
<translation>Невозможно подключится к серверу
|
||||
Проверьте подключение или вы можете попробовать скачать DLC напрямую
|
||||
Распакуйте скачанные DLC в папку "DLC" в папке с игрой
|
||||
Затем вы сможете продолжить</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.
@@ -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><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
||||
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
|
||||
p, li { white-space: pre-wrap; }
|
||||
</style></head><body style=" font-family:'Segoe UI'; font-size:10pt; font-weight:400; font-style:normal;">
|
||||
<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-weight:600; text-decoration: underline; color:#008f96;">Stellaris DLC Unlocker</span></p>
|
||||
<p align="center" style="-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:'MS Shell Dlg 2'; font-weight:600; text-decoration: underline;"><br /></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-weight:600; text-decoration: underline;">Requirements</span><span style=" font-family:'MS Shell Dlg 2';">:</span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2';"> </span><span style=" font-family:'MS Shell Dlg 2'; font-weight:600;">License</span><span style=" font-family:'MS Shell Dlg 2';">: Steam.</span></p>
|
||||
<p style="-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:'MS Shell Dlg 2'; font-weight:600; text-decoration: underline;"><br /></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-weight:600; text-decoration: underline;">Installation</span><span style=" font-family:'MS Shell Dlg 2';">:</span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2';"> Follow the installer instructions. Installation is almost entirely automatic. <br /></span></p>
|
||||
<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-weight:600; text-decoration: underline; color:#008f96;">Terms Of Use</span></p>
|
||||
<p style="-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:'MS Shell Dlg 2'; color:#0000ff;"><br /></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2';">1. This unlocker is distributed absolutely free of charge. Any commercial use of this unlocker is prohibited.</span></p>
|
||||
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2';">2. THIS UNLOCKER IS PROVIDED &quot;AS IS&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.</span></p>
|
||||
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2';">3. All rights not expressly granted here are reserved by the copyright holders.</span></p>
|
||||
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2';">4. Installation and use of this modification implies that you have read and understand the terms of this license agreement and agree to them.</span></p>
|
||||
<p style="-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:'MS Shell Dlg 2';"><br /></p></body></html></source>
|
||||
<extracomment>Welcome page text</extracomment>
|
||||
<translation><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
||||
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
|
||||
p, li { white-space: pre-wrap; }
|
||||
</style></head><body style=" font-family:'Segoe UI'; font-size:10pt; font-weight:400; font-style:normal;">
|
||||
<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-weight:600; text-decoration: underline; color:#008f96;">Stellaris DLC 解锁器</span></p>
|
||||
<p align="center" style="-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:'MS Shell Dlg 2'; font-weight:600; text-decoration: underline;"><br /></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-weight:600; text-decoration: underline;">要求</span><span style=" font-family:'MS Shell Dlg 2';">:</span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2';"> </span><span style=" font-family:'MS Shell Dlg 2'; font-weight:600;">许可</span><span style=" font-family:'MS Shell Dlg 2';">: 已购买。</span></p>
|
||||
<p style="-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:'MS Shell Dlg 2'; font-weight:600; text-decoration: underline;"><br /></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-weight:600; text-decoration: underline;">安装</span><span style=" font-family:'MS Shell Dlg 2';">:</span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2';"> 安装过程几乎全自动化,您只需依照安装程序的提示进行操作。 <br /></span></p>
|
||||
<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-weight:600; text-decoration: underline; color:#008f96;">使用条款</span></p>
|
||||
<p style="-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:'MS Shell Dlg 2'; color:#0000ff;"><br /></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2';">1. 此解锁器完全免费分发。禁止用于任何商业用途。如果您是从任何渠道购买,请退款并差评。</span></p>
|
||||
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2';">2. 此解锁器按“原样”提供,不附带任何明示或暗示的保证,包括但不限于适销性、特定用途适用性或无侵权性的保证。在适用法律允许的最大范围内,作者在任何情况下均不对因使用或无法使用本修改而引起的任何直接、间接、附带、特殊或后续损害承担责任。这些损害包括但不限于数据丢失、经济损失、账号被封禁(包括 VAC 封禁或其他发行商采取的惩罚措施)。</span></p>
|
||||
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2';">3. 版权所有者保留所有未明确授予的权利。</span></p>
|
||||
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2';">4. 继续安装和使用此修改版即表示您已阅读并理解本许可协议的条款,并同意这些条款。</span></p>
|
||||
<p style="-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:'MS Shell Dlg 2';"><br /></p></body></html></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><html><head/><body><p>Uses a different unlock method</p></body></html></source>
|
||||
<extracomment>Alternative unlock tooltip</extracomment>
|
||||
<translation><html><head/><body><p>使用不同的解锁方式</p></body></html></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><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></source>
|
||||
<extracomment>full reinstall tooltip</extracomment>
|
||||
<translation><html><head/><body><p>警告!此功能将删除所有存档和模组播放集。</p><p>请仅在在正常安装过程中出现问题时使用</p></body></html></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><html><head/><body><p>It is used if it is impossible to automatically reinstall the launcher.</p><p>The program will assume that the launcher has already been reinstalled before</p></body></html></source>
|
||||
<extracomment>Skip launcher reinstall tooltip</extracomment>
|
||||
<translation><html><head/><body><p>请仅在无法自动重新安装启动器时使用。</p><p>解锁器将假定启动器已在之前重新安装</p></body></html></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><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
||||
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
|
||||
p, li { white-space: pre-wrap; }
|
||||
</style></head><body style=" font-family:'Segoe UI'; font-size:8.25pt; font-weight:400; font-style:normal;">
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-size:12pt; color:#ff0000;">Old DLCs detected! </span><span style=" font-family:'MS Shell Dlg 2'; font-size:12pt; color:#ffffff;">You can just update it while unlocking</span></p></body></html></source>
|
||||
<translatorcomment>Update old DLCs text</translatorcomment>
|
||||
<translation><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
||||
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
|
||||
p, li { white-space: pre-wrap; }
|
||||
</style></head><body style=" font-family:'Segoe UI'; font-size:8.25pt; font-weight:400; font-style:normal;">
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-size:12pt; color:#ff0000;">检测到旧的DLCs! </span><span style=" font-family:'MS Shell Dlg 2'; font-size:12pt; color:#ffffff;">解锁的时将自动更新为最新版本</span></p></body></html></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'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 "dlc" folder to game folder
|
||||
Then you can continue</source>
|
||||
<translation>无法与服务器建立连接
|
||||
请检查您的连接,或者您可以尝试直接下载 DLC
|
||||
将下载的 "dlc" 文件夹解压至游戏文件夹
|
||||
然后继续操作</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
@@ -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"))
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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)
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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.
@@ -1,5 +0,0 @@
|
||||
{"url": "femboysex.pro", "alturl": "https://mega.nz/folder/4zFRnD6a#aVGAK32ZHPxCp7bMtG87BA", "altlauncher": "launcher-installer-windows_2024.14.msi", "server_msg": ""}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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_()
|
||||
@@ -1,4 +0,0 @@
|
||||
PyQt5==5.15.10
|
||||
PyQt5_sip==12.13.0
|
||||
Requests==2.32.5
|
||||
vdf==3.4
|
||||
Reference in New Issue
Block a user