diff --git a/installer_gui.py b/installer_gui.py
index 3dca5b8..d4dc2a4 100644
--- a/installer_gui.py
+++ b/installer_gui.py
@@ -10,9 +10,11 @@
#
# You should have received a copy of the GNU General Public License along with this program. If not,
# see .
+import hashlib
import json
import os
import shutil
+import string
import subprocess
import sys
import threading
@@ -20,6 +22,7 @@
import tkinter as tk
import urllib.request
import webbrowser
+import winreg
# pip install urllib3==1.25.11
# The newer urllib has break changes.
import xml.etree.ElementTree as ET
@@ -38,7 +41,7 @@
project_repo_link = 'https://github.com/LocalizedKorabli/Korabli-LESTA-L10N/'
installer_repo_link = 'https://github.com/LocalizedKorabli/L10nInstallerGUI/'
-version = '0.0.3b'
+version = '0.1.0'
locale_config = '''
ru
@@ -83,8 +86,18 @@
'WOWS.CN.PRODUCTION': ('zh_cn', True)
}
+launcher_dict: Dict[str, str] = {
+ 'lgc_api.exe': '莱服客户端',
+ 'wgc_api.exe': '直营服客户端',
+ 'wgc360_api.exe': '国服客户端'
+}
+
base_path: str = getattr(sys, '_MEIPASS', os.path.abspath(os.path.dirname(__file__)))
resource_path: str = os.path.join(base_path, 'resources')
+game_path_current = '<程序运行目录>'
+game_path_unknown = '<请选择游戏目录>'
+msg_game_path_may_be_invalid = '''您选择的游戏目录缺失必要的游戏文件,
+可能并非战舰世界安装目录。'''
class LocalizationInstaller:
@@ -103,20 +116,18 @@ class LocalizationInstaller:
install_progress_text: tk.StringVar
download_progress_text: tk.StringVar
install_progress: tk.DoubleVar
+ game_path: tk.StringVar
# Variables
+ global_settings: Dict[str, Any] = None
choice: Dict[str, Any] = None
- installed_l10n_version = ''
+ last_installed_l10n_version = ''
run_dirs: Dict[str, str] = {}
is_installing: bool = False
- game_launcher_file: str = ''
+ game_launcher_file: Optional[Path] = None
+ available_game_paths: List[str] = []
def __init__(self, parent: tk.Tk):
- mkdir('l10n_installer/cache')
- mkdir('l10n_installer/downloads')
- mkdir('l10n_installer/mods')
- mkdir('l10n_installer/processed')
- mkdir('l10n_installer/settings')
self.root = parent
self.root.title(f'汉化安装器v{version}')
@@ -132,62 +143,79 @@ def __init__(self, parent: tk.Tk):
self.game_launcher_status = tk.StringVar()
self.download_progress_text = tk.StringVar()
self.install_progress = tk.DoubleVar()
+ self.game_path = tk.StringVar()
+
+ # 游戏目录
+ ttk.Label(parent, text='游戏目录:') \
+ .grid(row=0, column=0, columnspan=1, pady=5, sticky=tk.W)
+
+ self.game_path_combo = ttk.Combobox(root, width=26, textvariable=self.game_path, state='readonly')
+ self.refresh_path_combo()
+
+ self.game_path_combo.grid(row=0, column=1, columnspan=3, padx=5, pady=5, sticky=tk.W)
+
+ self.auto_search = ttk.Button(parent, text='自动检测', command=lambda: self.find_game(overwrite=False),
+ style='success')
+ self.auto_search.grid(row=1, column=2, columnspan=1)
+
+ self.game_path_button = ttk.Button(parent, text='选择目录', command=self.choose_path, style='warning')
+ self.game_path_button.grid(row=1, column=3, columnspan=1)
# 游戏版本
- ttk.Label(parent, text='游戏版本——汉化版本:') \
- .grid(row=0, column=0, columnspan=4, pady=5, sticky=tk.W)
+ ttk.Label(parent, text='游戏版本/汉化版本') \
+ .grid(row=1, column=0, columnspan=4, pady=5, sticky=tk.W)
# 汉化状态
self.localization_status_label_1st = ttk.Label(parent, textvariable=self.localization_status_1st)
- self.localization_status_label_1st.grid(row=1, column=0, columnspan=4, pady=5, sticky=tk.W)
+ self.localization_status_label_1st.grid(row=3, column=0, columnspan=4, pady=5, sticky=tk.W)
self.localization_status_label_2nd = ttk.Label(parent, textvariable=self.localization_status_2nd)
- self.localization_status_label_2nd.grid(row=2, column=0, columnspan=4, pady=5, sticky=tk.W)
+ self.localization_status_label_2nd.grid(row=4, column=0, columnspan=4, pady=5, sticky=tk.W)
self.parse_game_version()
# 游戏区服
- ttk.Label(parent, text='游戏区服:').grid(row=3, column=0, pady=5, sticky=tk.W)
+ ttk.Label(parent, text='游戏区服:').grid(row=5, column=0, pady=5, sticky=tk.W)
# 游戏区服选项
ttk.Radiobutton(parent, text='莱服', variable=self.server_region, value='ru') \
- .grid(row=3, column=1, sticky=tk.W)
+ .grid(row=5, column=1, sticky=tk.W)
ttk.Radiobutton(parent, text='直营服', variable=self.server_region, value='zh_sg', style='warning') \
- .grid(row=3, column=2, sticky=tk.W)
+ .grid(row=5, column=2, sticky=tk.W)
ttk.Radiobutton(parent, text='国服', variable=self.server_region, value='zh_cn', style='danger') \
- .grid(row=3, column=3, sticky=tk.W)
+ .grid(row=5, column=3, sticky=tk.W)
# 游戏类型
- ttk.Label(parent, text='游戏类型:').grid(row=4, column=0, pady=5, sticky=tk.W)
+ ttk.Label(parent, text='游戏类型:').grid(row=6, column=0, pady=5, sticky=tk.W)
# 游戏类型选项
ttk.Radiobutton(parent, text='正式服', variable=self.is_release, value=True, style='success') \
- .grid(row=4, column=1, sticky=tk.W)
+ .grid(row=6, column=1, sticky=tk.W)
ttk.Radiobutton(parent, text='PT服', variable=self.is_release, value=False, style='danger') \
- .grid(row=4, column=2, sticky=tk.W)
+ .grid(row=6, column=2, sticky=tk.W)
self.detect_game_type_button = ttk.Button(parent, text='自动检测',
command=lambda: self.detect_game_status(manually=True))
- self.detect_game_type_button.grid(row=4, column=3)
+ self.detect_game_type_button.grid(row=6, column=3)
# 下载源
- ttk.Label(parent, text='汉化来源:').grid(row=5, column=0, pady=5, sticky=tk.W)
+ ttk.Label(parent, text='汉化来源:').grid(row=7, column=0, pady=5, sticky=tk.W)
# 下载源选项
ttk.Radiobutton(parent, text='Gitee', variable=self.download_source, value='gitee', style='danger') \
- .grid(row=5, column=1, sticky=tk.W)
+ .grid(row=7, column=1, sticky=tk.W)
ttk.Radiobutton(parent, text='GitHub', variable=self.download_source, value='github', style='dark') \
- .grid(row=5, column=2, sticky=tk.W)
+ .grid(row=7, column=2, sticky=tk.W)
ttk.Radiobutton(parent, text='本地文件', variable=self.download_source, value='local') \
- .grid(row=5, column=3, sticky=tk.W)
+ .grid(row=7, column=3, sticky=tk.W)
# 体验增强包/汉化修改包
- ttk.Checkbutton(parent, text='安装体验增强包', variable=self.ee_selection) \
- .grid(row=7, column=0, columnspan=2, pady=5, sticky=tk.W)
+ self.ee_button = ttk.Checkbutton(parent, text='安装体验增强包', variable=self.ee_selection)
+ self.ee_button.grid(row=9, column=0, columnspan=2, pady=5, sticky=tk.W)
ttk.Checkbutton(parent, text='安装模组(汉化修改包)', variable=self.mod_selection) \
- .grid(row=8, column=0, columnspan=2, pady=5, sticky=tk.W)
+ .grid(row=30, column=0, columnspan=2, pady=5, sticky=tk.W)
self.mods_button = ttk.Button(parent, text='模组目录', command=self.open_mods_folder)
- self.mods_button.grid(row=8, column=2, columnspan=1)
- self.download_mods_button = ttk.Button(parent, text='下载模组',
+ self.mods_button.grid(row=30, column=2, columnspan=1)
+ self.download_mods_button = ttk.Button(parent, text='下载模组', style='info',
command=lambda: webbrowser.open_new_tab(mods_link))
- self.download_mods_button.grid(row=8, column=3, columnspan=1)
+ self.download_mods_button.grid(row=30, column=3, columnspan=1)
# 安装路径选择/下载进度
self.install_path_entry = ttk.Entry(parent, textvariable=self.mo_path, width=20)
@@ -198,46 +226,60 @@ def __init__(self, parent: tk.Tk):
# 安装/更新按钮
self.install_button = ttk.Button(parent, text='安装汉化', command=self.install_update,
style=ttk.SUCCESS)
- self.install_button.grid(row=9, column=0, pady=5)
+ self.install_button.grid(row=31, column=0, pady=5)
# 安装进度
- ttk.Label(parent, textvariable=self.install_progress_text).grid(row=9, column=1, columnspan=3,
+ ttk.Label(parent, textvariable=self.install_progress_text).grid(row=31, column=1, columnspan=3,
padx=5, sticky=tk.W)
self.install_progress_bar = ttk.Progressbar(parent, variable=self.install_progress, maximum=100.0,
style='success-striped', length=400)
- self.install_progress_bar.grid(row=10, column=0, columnspan=4, padx=10)
+ self.install_progress_bar.grid(row=32, column=0, columnspan=4, padx=10)
# 启动游戏
self.launch_button = ttk.Button(parent, text='启动游戏', command=self.launch_game, style=ttk.WARNING)
- self.launch_button.grid(row=11, column=0, pady=5)
+ self.launch_button.grid(row=33, column=0, pady=5)
# 启动器状态
- ttk.Label(parent, textvariable=self.game_launcher_status).grid(row=11, column=1, columnspan=3,
+ ttk.Label(parent, textvariable=self.game_launcher_status).grid(row=33, column=1, columnspan=3,
padx=5, sticky=tk.W)
# 相关链接
about_button = ttk.Button(parent, text='关于项目', command=lambda: webbrowser.open_new_tab(project_repo_link),
style=ttk.INFO)
- about_button.grid(row=12, column=0, pady=5)
+ about_button.grid(row=34, column=0, pady=5)
src_button = ttk.Button(parent, text='代码仓库', command=lambda: webbrowser.open_new_tab(installer_repo_link),
style=ttk.DANGER)
- src_button.grid(row=12, column=1, pady=5, padx=5)
+ src_button.grid(row=34, column=1, pady=5, padx=5)
# 版权声明
- ttk.Label(parent, text='© 2024 LocalizedKorabli').grid(row=12, column=2, columnspan=3, pady=5)
+ ttk.Label(parent, text='© 2024 LocalizedKorabli').grid(row=34, column=2, columnspan=3, pady=5)
# 根据下载源选项显示或隐藏安装路径选择
- self.download_source.trace('w', self.toggle_install_path)
+ self.download_source.trace('w', self.on_download_source_changed)
+ # 更换游戏路径时,刷新数据
+ self.game_path.trace('w', self.on_game_path_changed)
+ # 非俄服客户端无需安装体验增强包
+ self.server_region.trace('w', self.on_server_region_or_game_type_changed)
+ self.is_release.trace('w', self.on_server_region_or_game_type_changed)
- choice = self.parse_choice()
+ mkdir('l10n_installer/cache')
+ mkdir('l10n_installer/downloads')
+ mkdir('l10n_installer/mods')
+ mkdir('l10n_installer/processed')
+ mkdir('l10n_installer/settings')
- self.server_region.set(choice.get('server_region', 'ru'))
- self.is_release.set(choice.get('is_release', True))
- self.download_source.set(choice.get('download_source', 'gitee'))
- self.ee_selection.set(choice.get('use_ee', True))
- self.mod_selection.set(choice.get('apply_mods', True))
+ global_settings = self.parse_global_settings()
+ last_saved_paths = global_settings.get('available_game_paths')
+ if isinstance(last_saved_paths, list):
+ for saved_path in last_saved_paths:
+ self.available_game_paths.append(saved_path)
+ self.refresh_path_combo()
+ self.game_path.set(global_settings.get('last_game_path'))
+ self.game_path_combo.current()
+ self.find_game()
+ self.on_game_path_changed()
self.safely_set_download_progress_text('准备')
self.safely_set_install_progress_text('准备')
@@ -253,6 +295,115 @@ def safely_set_install_progress_text(self, msg: str):
def safely_set_install_progress(self, progress: Optional[float] = None):
self.root.after(0, self.install_progress.set(progress))
+ def refresh_path_combo(self):
+ self.game_path_combo['values'] = list(dict.fromkeys(self.available_game_paths))
+
+ def get_game_path(self, find: bool = True) -> Optional[Path]:
+ game_path_str = self.game_path.get()
+ if game_path_str == game_path_unknown:
+ return self.find_game() if find else None
+ if game_path_str == game_path_current:
+ return Path('.')
+ return Path(game_path_str)
+
+ def on_game_path_changed(self, *args) -> None:
+ if self.game_path.get() == game_path_unknown:
+ return
+
+ game_path = self.get_game_path()
+ if not game_path:
+ return
+
+ self.detect_game_status()
+ self.parse_game_version()
+ self.find_launcher()
+
+ mkdir(game_path.joinpath('l10n_installer/settings'))
+ mkdir(game_path.joinpath('l10n_installer/mods'))
+
+ choice = self.parse_choice(use_cache=False)
+
+ self.server_region.set(choice.get('server_region', 'ru'))
+ self.is_release.set(choice.get('is_release', True))
+ self.download_source.set(choice.get('download_source', 'gitee'))
+ self.ee_selection.set(choice.get('use_ee', True))
+ self.mod_selection.set(choice.get('apply_mods', True))
+
+ def on_server_region_or_game_type_changed(self, *args):
+ self.ee_button.configure(state=('' if self.supports_ee() else 'disabled'))
+
+ def supports_ee(self):
+ return self.server_region.get() == 'ru' and self.is_release.get()
+
+ def find_game(self, overwrite: bool = True) -> Optional[Path]:
+ found_in_reg = self.find_from_reg()
+ found_manually = self.find_manually()
+ game_path_str = self.game_path.get()
+ if not overwrite:
+ return None
+ game_path = Path(game_path_str)
+ if is_valid_game_path(game_path):
+ return game_path
+ if is_valid_game_path(Path('.')):
+ self.game_path.set(game_path_current)
+ self.available_game_paths.append(game_path_current)
+ self.refresh_path_combo()
+ return Path('.')
+ if found_in_reg:
+ reg_first = found_in_reg[0]
+ self.game_path.set(str(reg_first.absolute()))
+ return reg_first
+ # Manually
+ if found_manually:
+ manually_first = found_manually[0]
+ self.game_path.set(str(manually_first.absolute()))
+ return manually_first
+ return None
+
+ def find_from_reg(self) -> List[Path]:
+ try:
+ with winreg.OpenKey(winreg.HKEY_CURRENT_USER, r'Software\Classes\lgc\DefaultIcon') as key:
+ lgc_dir_str, _ = winreg.QueryValueEx(key, '')
+ if lgc_dir_str is None:
+ # Try the default value
+ lgc_dir_str = r'C:\ProgramData\Lesta\GameCenter\lgc.exe'
+ if ',' in lgc_dir_str:
+ lgc_dir_str = lgc_dir_str.split(',')[0]
+ preferences_path = Path(lgc_dir_str).parent.joinpath('preferences.xml')
+ if not preferences_path.is_file():
+ return []
+ pref_root = ET.parse(preferences_path).getroot()
+ games_block = pref_root.find('.//application/games_manager/games')
+ games = games_block.findall('.//game')
+ if not games:
+ return []
+ path_strs = [game.find('working_dir').text for game in games if game.find('working_dir') is not None]
+ path_strs_filtered = [dir_str for dir_str in path_strs if
+ 'Tank' not in dir_str and 'GameCheck' not in dir_str]
+ for path_str in path_strs_filtered:
+ self.available_game_paths.append(path_str)
+ self.refresh_path_combo()
+ return [Path(dir_str) for dir_str in path_strs_filtered]
+ except Exception:
+ pass
+
+ def find_manually(self) -> List[Path]:
+ found_manually: List[Path] = []
+ drives = find_all_drives()
+ for drive in drives:
+ for possible_path in [
+ Path(drive).joinpath('Games').joinpath('Korabli'),
+ Path(drive).joinpath('Games').joinpath('Korabli_PT'),
+ Path(drive).joinpath('Korabli'),
+ Path(drive).joinpath('Korabli_PT'),
+ ]:
+ if is_valid_game_path(possible_path):
+ found_manually.append(possible_path)
+ for found_path in found_manually:
+ self.available_game_paths.append(str(found_path.absolute()))
+ self.refresh_path_combo()
+ return found_manually
+
def popup_result(self, nothing_wrong: bool):
if nothing_wrong:
msg_response = Messagebox.show_question('汉化安装完成。是否启动游戏?', '安装完成', alert=True, buttons=[
@@ -264,15 +415,15 @@ def popup_result(self, nothing_wrong: bool):
else:
Messagebox.show_error('汉化安装失败。请检查您的网络,选择合适的汉化来源重试。', '安装失败')
- def toggle_install_path(self, *args):
+ def on_download_source_changed(self, *args):
if self.download_source.get() == 'local':
- self.install_path_entry.grid(row=6, column=0, columnspan=3)
- self.install_path_button.grid(row=6, column=3)
+ self.install_path_entry.grid(row=8, column=0, columnspan=3)
+ self.install_path_button.grid(row=8, column=3)
self.download_progress_label.grid_forget()
self.download_progress_info.grid_forget()
else:
- self.download_progress_label.grid(row=6, column=0, pady=5, sticky=tk.W)
- self.download_progress_info.grid(row=6, column=1, pady=5, columnspan=3, sticky=tk.W)
+ self.download_progress_label.grid(row=8, column=0, pady=5, sticky=tk.W)
+ self.download_progress_info.grid(row=8, column=1, pady=5, columnspan=3, sticky=tk.W)
self.install_path_entry.grid_forget()
self.install_path_button.grid_forget()
@@ -281,6 +432,16 @@ def open_mods_folder(self):
mkdir(mods_folder)
subprocess.run(['explorer', mods_folder.absolute()])
+ def choose_path(self):
+ game_path_chosen = filedialog.askdirectory(initialdir='.')
+ if game_path_chosen:
+ if not is_valid_game_path(Path(game_path_chosen)):
+ Messagebox.show_warning(msg_game_path_may_be_invalid)
+ else:
+ self.available_game_paths.append(game_path_chosen)
+ self.refresh_path_combo()
+ self.game_path.set(game_path_chosen)
+
def choose_mo(self):
mo_path = filedialog.askopenfilename(initialdir='.', filetypes=[('汉化包', ['*.mo', '*.zip']),
('MO汉化文件', '*.mo'),
@@ -293,19 +454,24 @@ def install_update(self):
Messagebox.show_warning('安装已在进行!', '安装汉化')
return
self.is_installing = True
- self.save_choice()
tr = threading.Thread(target=self.do_install_update)
tr.start()
def do_install_update(self) -> None:
self.safely_set_install_progress(progress=0.0)
+ game_path = self.get_game_path()
+ if not is_valid_game_path(game_path):
+ self.root.after(0, Messagebox.show_error, '游戏目录不可用,无法安装。', '安装汉化')
+ self.is_installing = False
+ return
run_dirs = self.run_dirs.keys()
if len(run_dirs) == 0:
self.root.after(0, Messagebox.show_error, '未发现游戏版本,无法安装。', '安装汉化')
+ self.is_installing = False
return
is_release = self.is_release.get()
for run_dir in run_dirs:
- target_path = Path('bin').joinpath(run_dir).joinpath('res_mods' if is_release else 'res')
+ target_path = game_path.joinpath('bin').joinpath(run_dir).joinpath('res_mods' if is_release else 'res')
mkdir(target_path)
self.safely_set_install_progress_text('安装locale_config')
if not is_release:
@@ -323,7 +489,7 @@ def do_install_update(self) -> None:
proxies = {scheme: proxy for scheme, proxy in urllib.request.getproxies().items()}
if is_release:
# EE
- if self.ee_selection.get():
+ if self.supports_ee() and self.ee_selection.get():
self.safely_set_install_progress_text('安装体验增强包')
output_file = 'l10n_installer/downloads/LK_EE.zip'
self.safely_set_download_progress_text('下载体验增强包——连接中')
@@ -346,7 +512,8 @@ def do_install_update(self) -> None:
self.safely_set_download_progress_text('下载体验增强包——请求异常')
if ee_ready:
for run_dir in run_dirs:
- target_path = Path('bin').joinpath(run_dir).joinpath('res_mods' if is_release else 'res')
+ target_path = game_path.joinpath('bin').joinpath(run_dir).joinpath(
+ 'res_mods' if is_release else 'res')
with zipfile.ZipFile(output_file, 'r') as mo_zip:
mo_zip.extractall(target_path)
self.safely_set_install_progress_text('安装体验增强包——完成')
@@ -372,10 +539,10 @@ def do_install_update(self) -> None:
remote_version = 'local'
execution_time = str(time.time_ns())
for run_dir in run_dirs:
- target_path = Path('bin').joinpath(run_dir).joinpath('res_mods' if is_release else 'res')
- info_path = Path('bin').joinpath(run_dir).joinpath('l10n')
+ target_path = game_path.joinpath('bin').joinpath(run_dir).joinpath('res_mods' if is_release else 'res')
+ info_path = game_path.joinpath('bin').joinpath(run_dir).joinpath('l10n')
mkdir(info_path)
- info_file = info_path.joinpath('version.info')
+ version_info_file = info_path.joinpath('version.info')
if downloaded_file.endswith('.zip'):
extracted_path = Path('l10n_installer').joinpath('downloads').joinpath('extracted_mo')
mkdir(extracted_path)
@@ -391,8 +558,8 @@ def do_install_update(self) -> None:
mo_file_name = mo_files[0].filename
mo_zip.extract(mo_file_name, extracted_path)
downloaded_file = os.path.join(extracted_path, mo_file_name)
- if info_fetched and info_file.is_file():
- with open(info_file, 'r', encoding='utf-8') as f:
+ if info_fetched and version_info_file.is_file():
+ with open(version_info_file, 'r', encoding='utf-8') as f:
remote_version = f.readline()
if not downloaded_file.endswith('.mo') or not os.path.isfile(downloaded_file):
self.safely_set_install_progress_text('安装汉化包——文件异常')
@@ -416,8 +583,15 @@ def do_install_update(self) -> None:
if not os.path.isfile(old_mo_backup) and os.path.isfile(old_mo):
shutil.copy(old_mo, old_mo_backup)
shutil.copy(downloaded_file, old_mo)
- with open(info_file, 'w', encoding='utf-8') as f:
- f.write(remote_version)
+ installation_info_file = info_path.joinpath('installation.info')
+ with open(installation_info_file, 'w', encoding='utf-8') as f:
+ f.writelines([
+ remote_version,
+ '\n',
+ str(old_mo.absolute()),
+ '\n',
+ get_sha256_for_mo(old_mo)
+ ])
try:
float(remote_version)
except ValueError:
@@ -425,10 +599,14 @@ def do_install_update(self) -> None:
self.is_installing = False
if nothing_wrong:
self.safely_set_install_progress(progress=100.0)
+ self.available_game_paths.append(self.game_path.get())
+ self.save_global_settings()
+ self.save_choice()
self.safely_set_install_progress_text('完成!' if nothing_wrong else '失败!')
self.parse_game_version()
self.root.after(0, self.popup_result, nothing_wrong)
+ # Returns (output_file: str, remote_version: str)
def check_version_and_fetch_mo(self, download_link_base: str, proxies: Dict) -> (str, str):
remote_version: str = 'latest'
self.safely_set_download_progress_text('下载汉化包——获取版本')
@@ -452,7 +630,7 @@ def check_version_and_fetch_mo(self, download_link_base: str, proxies: Dict) ->
mo_file_name = f'{remote_version}.mo'
output_file = f'l10n_installer/downloads/{mo_file_name}'
# Check existed
- if valid_version and self.installed_l10n_version == remote_version:
+ if valid_version and self.last_installed_l10n_version == remote_version:
if os.path.isfile(output_file):
try:
if polib.mofile(output_file):
@@ -513,7 +691,7 @@ def parse_and_apply_mods(self, downloaded_mo: str, mods: List[str], execution_ti
return modded_file_name
def get_mods(self) -> List[str]:
- if not self.mod_selection.get() or not os.path.isdir('l10n_installer/mods'):
+ if not self.mod_selection.get() or not Path('l10n_installer/mods').is_dir():
return []
files = os.listdir('l10n_installer/mods')
return [('l10n_installer/mods/' + file) for file in files if (file.endswith('.po') or file.endswith('.mo'))]
@@ -524,14 +702,18 @@ def parse_game_version(self) -> None:
self.localization_status_2nd.set('')
v_1st = ''
v_2nd = ''
- if not os.path.isdir('bin'):
+ game_path = self.get_game_path()
+ if not game_path:
+ return
+ bin_path = game_path.joinpath('bin')
+ if not bin_path.is_dir():
return
- for v_dir_b in os.listdir(Path('bin')):
+ for v_dir_b in os.listdir(bin_path):
v_dir = str(v_dir_b)
# v_dir_num = 0
try:
v_dir_num = int(v_dir)
- if not is_valid_build_dir(Path('bin').joinpath(v_dir)):
+ if not is_valid_build_dir(bin_path.joinpath(v_dir)):
continue
except ValueError:
continue
@@ -555,7 +737,7 @@ def parse_game_version(self) -> None:
if not self.localization_status_2nd.get():
self.localization_status_label_2nd.grid_forget()
else:
- self.localization_status_label_2nd.grid(row=2, column=0, columnspan=4, pady=5, sticky=tk.W)
+ self.localization_status_label_2nd.grid(row=4, column=0, columnspan=4, pady=5, sticky=tk.W)
def get_choice_template(self):
self.detect_game_status()
@@ -567,66 +749,117 @@ def get_choice_template(self):
'apply_mods': True
}
+ def get_global_settings_template(self):
+ return {
+ 'last_game_path': game_path_unknown,
+ 'available_game_paths': [
+ game_path_current
+ ]
+ }
+
def detect_game_status(self, manually: bool = False):
if not manually:
self.server_region.set('ru')
self.is_release.set(True)
- if not os.path.isfile('game_info.xml'):
+ game_path = self.get_game_path()
+ if not game_path:
return
- game_info = ET.parse('game_info.xml')
+ game_info_file = game_path.joinpath('game_info.xml')
+ if not game_info_file.is_file():
+ return
+ game_info = ET.parse(game_info_file)
game_id = game_info.find('.//game/id')
if game_id is None:
return
game_type: (str, bool) = server_regions_dict.get(game_id.text, ('ru', 'PT.PRODUCTION' not in game_id.text))
self.server_region.set(game_type[0])
self.is_release.set(game_type[1])
- self.parse_game_version()
# 返回:(汉化版本号: str, 汉化状态: str)
def get_local_l10n_version(self, run_dir: str) -> (str, str):
- info_file = Path('bin').joinpath(run_dir).joinpath('l10n').joinpath('version.info')
- if not os.path.isfile(info_file):
+ installation_info_file = self.get_game_path().joinpath('bin').joinpath(run_dir).joinpath('l10n') \
+ .joinpath('installation.info')
+ if not os.path.isfile(installation_info_file):
return '', f'{run_dir}——未安装汉化'
- with open(info_file, 'r', encoding='utf-8') as f:
- parsed_version = f.readline()
- self.installed_l10n_version = parsed_version
+ to_return: (str, str)
+ with open(installation_info_file, 'r', encoding='utf-8') as f:
+ parsed_version = f.readline().strip()
+ mo_path = Path(f.readline().strip())
+ if not mo_path.is_file():
+ return '', f'{run_dir}——未安装汉化'
+ mo_sha256 = f.readline().strip()
+ self.last_installed_l10n_version = parsed_version
+ not_parsable = False
try:
float(parsed_version)
- return parsed_version, f'{run_dir}——{parsed_version}'
+ to_return = parsed_version, f'{run_dir}——{parsed_version}'
except ValueError:
- pass
- try:
- inst_time = float(f.readline())
- time_formatted = datetime.fromtimestamp(inst_time).strftime('%Y-%m-%d %H:%M:%S')
- return '', f'{run_dir}——汉化版本未知,于{time_formatted}安装'
- except ValueError:
- return '', f'{run_dir}——汉化版本未知,安装时间未知'
+ not_parsable = True
+ if not_parsable:
+ try:
+ inst_time = float(f.readline())
+ time_formatted = datetime.fromtimestamp(inst_time).strftime('%Y-%m-%d %H:%M')
+ to_return = '', f'{run_dir}——汉化版本未知,于{time_formatted}安装'
+ except ValueError:
+ to_return = '', f'{run_dir}——汉化版本未知,安装时间未知'
+
+ return to_return if check_sha256(mo_path, mo_sha256) else ('', to_return[1] + '(被篡改)')
def find_launcher(self) -> str:
- if os.path.isfile('lgc_api.exe'):
- self.game_launcher_file = 'lgc_api.exe'
- return '莱服客户端'
- elif os.path.isfile('wgc_api.exe'):
- self.game_launcher_file = 'wgc_api.exe'
- return '直营服客户端'
- elif os.path.isfile('wgc360_api.exe'):
- self.game_launcher_file = 'wgc360_api.exe'
- return '国服客户端'
+ game_path = self.get_game_path()
+ if game_path:
+ for launcher in launcher_dict.keys():
+ launcher_file = game_path.joinpath(launcher)
+ if launcher_file.is_file():
+ self.game_launcher_file = launcher_file
+ return launcher_dict.get(launcher)
+
return '未找到客户端'
def launch_game(self) -> None:
- if not self.game_launcher_file or not os.path.isfile(self.game_launcher_file):
+ if not self.game_launcher_file or not self.game_launcher_file.is_file():
self.find_launcher()
- if not self.game_launcher_file or not os.path.isfile(self.game_launcher_file):
+ if not self.game_launcher_file or not self.game_launcher_file.is_file():
Messagebox.show_warning('未找到客户端!', '启动游戏')
return
subprocess.run(self.game_launcher_file)
- def parse_choice(self) -> Dict[str, str]:
- if self.choice:
+ def parse_global_settings(self):
+ if self.global_settings:
+ return self.global_settings
+ self.global_settings = {}
+ global_settings_file = Path('l10n_installer/settings/global.json')
+ if global_settings_file.is_file():
+ try:
+ with open(global_settings_file, 'r', encoding='utf-8') as f:
+ self.global_settings = json.load(f)
+ except Exception:
+ pass
+ self.check_global_settings()
+ return self.global_settings
+
+ def check_global_settings(self):
+ template = self.get_global_settings_template()
+ for entry in ['last_game_path', 'available_game_paths']:
+ if entry not in self.global_settings.keys():
+ self.global_settings[entry] = template[entry]
+
+ def save_global_settings(self):
+ if self.global_settings:
+ self.global_settings['last_game_path'] = self.game_path.get()
+ self.global_settings['available_game_paths'] = list(dict.fromkeys(self.available_game_paths))
+ self.global_settings['last_installed_l10n_version'] = self.last_installed_l10n_version
+ with open('l10n_installer/settings/global.json', 'w', encoding='utf-8') as f:
+ json.dump(self.global_settings, f, ensure_ascii=False, indent=4)
+
+ def parse_choice(self, use_cache: bool = True) -> Dict[str, str]:
+ if use_cache and self.choice:
return self.choice
self.choice = {}
- choice_file = 'l10n_installer/settings/choice.json'
+ game_path = self.get_game_path(find=False)
+ if not game_path:
+ return
+ choice_file = game_path.joinpath('l10n_installer/settings/choice.json')
if os.path.isfile(choice_file):
try:
with open(choice_file, 'r', encoding='utf-8') as f:
@@ -643,14 +876,20 @@ def check_choice(self):
self.choice[choice] = template[choice]
def save_choice(self) -> None:
- if self.choice:
+ if self.choice and self.game_path.get() != game_path_unknown:
+ game_path = self.get_game_path(find=False)
+ if not game_path:
+ return
self.choice['is_release'] = self.is_release.get()
self.choice['download_source'] = self.download_source.get()
self.choice['use_ee'] = self.ee_selection.get()
self.choice['apply_mods'] = self.mod_selection.get()
- with open('l10n_installer/settings/choice.json', 'w', encoding='utf-8') as f:
+ with open(game_path.joinpath('l10n_installer/settings/choice.json'), 'w', encoding='utf-8') as f:
json.dump(self.choice, f, ensure_ascii=False, indent=4)
+ def on_closed(self):
+ self.save_global_settings()
+
def mkdir(t_dir: Any):
os.makedirs(t_dir, exist_ok=True)
@@ -680,22 +919,40 @@ def process_modification_file(source_mo, translated_path: str):
source_mo.append(t_entry)
+def is_valid_game_path(game_path: Path):
+ return game_path.joinpath('game_info.xml').is_file() and game_path.joinpath('bin').is_dir()
+
+
def is_valid_build_dir(build_dir: Path) -> bool:
res_dir = Path(build_dir).joinpath('res')
- if not os.path.isdir(res_dir):
+ if not res_dir.is_dir():
return False
return os.path.isfile(res_dir.joinpath('locale_config.xml'))
+def check_sha256(mo_path: Path, sha256: str):
+ return get_sha256_for_mo(mo_path) == sha256
+
+
+def find_all_drives() -> List[str]:
+ return ['%s:/' % d for d in string.ascii_uppercase if os.path.exists('%s:' % d)]
+
+
+def get_sha256_for_mo(mo_path: Path):
+ with open(mo_path, 'rb') as file:
+ return hashlib.sha256(file.read()).hexdigest()
+
+
if __name__ == '__main__':
root = ttk.Window()
icon = os.path.join(resource_path, 'icon.ico')
root.iconbitmap(default=icon)
root.iconbitmap(bitmap=icon)
- half_screen_width = int(root.winfo_screenwidth() / 2) - 200
+ half_screen_width = int(root.winfo_screenwidth() / 2) - 225
half_screen_height = int(root.winfo_screenheight() / 2) - 300
root.geometry(f'+{half_screen_width}+{half_screen_height}')
app = LocalizationInstaller(root)
root.mainloop()
+ app.on_closed()
# pyinstaller -w -i resources/icon.ico --onefile --add-data "resources\*;resources" installer_gui.py --clean