From 32eee8bafb28c0b399c47e9ed2b4052730f2d62e Mon Sep 17 00:00:00 2001 From: "matanki.saito" Date: Sun, 8 Dec 2019 16:55:09 +0900 Subject: [PATCH 1/2] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=EF=BC=9Aopen=E3=81=ABign?= =?UTF-8?q?ore=E3=82=AA=E3=83=97=E3=82=B7=E3=83=A7=E3=83=B3=E3=82=92?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- angelica.py | 6 +++--- github_tool.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/angelica.py b/angelica.py index 38bf054..f8b2b28 100644 --- a/angelica.py +++ b/angelica.py @@ -116,7 +116,7 @@ def get_game_install_dir(dir_path, target_app_id): # acfファイルにある"installdir" "xxxx"をさがす install_dir_pattern = re.compile(r'\s*"installdir"\s+"(.*)') game_install_dir_name = None - with open(target_app_acf_path, mode='r', encoding="utf8") as target_app_acf_file: + with open(target_app_acf_path, mode='r', encoding="utf8", errors='ignore') as target_app_acf_file: for line in target_app_acf_file: result = install_dir_pattern.match(line) if result is not None: @@ -144,7 +144,7 @@ def get_lib_folders_from_vdf(steam_apps_path): # vdfファイルにある"[数字]" "xxxx"をさがす install_dir_pattern = re.compile(r'\s*"[0-9]+"\s+"(.*)') game_libs_paths = [] - with open(library_folders_vdf_path, mode='r', encoding="utf8") as target_vdf_file: + with open(library_folders_vdf_path, mode='r', encoding="utf8", errors='ignore') as target_vdf_file: for line in target_vdf_file: result = install_dir_pattern.match(line) if result is not None: @@ -154,7 +154,7 @@ def get_lib_folders_from_vdf(steam_apps_path): def install_key_file(save_file_path, mod_title, key_file_path): - with open(save_file_path, encoding="utf-8", mode='w') as fw: + with open(save_file_path, encoding="utf-8", mode='w', errors='ignore') as fw: fw.write("\n".join([ mod_title, key_file_path diff --git a/github_tool.py b/github_tool.py index 7a99c01..2d80383 100644 --- a/github_tool.py +++ b/github_tool.py @@ -46,7 +46,7 @@ def download_asset_from_github(repository_author, req = urllib.request.Request(request_url) - with open(out_file_path, "wb") as my_file: + with open(out_file_path, "wb", errors='ignore') as my_file: my_file.write(urllib.request.urlopen(req).read()) return out_file_path From a1e105fea7669054ce512f412a8f3aadfe0239cb Mon Sep 17 00:00:00 2001 From: "matanki.saito" Date: Sun, 26 Jan 2020 22:46:12 +0900 Subject: [PATCH 2/2] =?UTF-8?q?=E6=A9=9F=E8=83=BD=E8=BF=BD=E5=8A=A0?= =?UTF-8?q?=E3=81=A8=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - タブをまとめた - 翻訳を修正 - 一時ファイル作成が失敗するのでコードを修正 - I:RのDLLを追加できるようにした - ログをファイルに書き出すようにした --- .idea/checkstyle-idea.xml | 2 +- .idea/misc.xml | 2 +- angelica.py | 303 ++++++++++++++++++++++++++------------ eu4mods.json | 4 + github_tool.py | 40 +++-- loca.py | 36 ++--- resource/image.png | Bin 6889 -> 8092 bytes 7 files changed, 261 insertions(+), 126 deletions(-) diff --git a/.idea/checkstyle-idea.xml b/.idea/checkstyle-idea.xml index 32af77d..c22f903 100644 --- a/.idea/checkstyle-idea.xml +++ b/.idea/checkstyle-idea.xml @@ -3,7 +3,7 @@ - + \ No newline at end of file diff --git a/angelica.py b/angelica.py index f8b2b28..00cbbe0 100644 --- a/angelica.py +++ b/angelica.py @@ -1,6 +1,7 @@ import concurrent.futures import ctypes import json +import logging import os.path import re import shutil @@ -27,6 +28,8 @@ def remove_util(path): :return: 成否 """ + logging.info('remove files, path=%s', path) + if not os.path.exists(path): return False @@ -40,26 +43,49 @@ def remove_util(path): def install(install_dir_path, target_zip_url, final_check_file): + logging.info('install') + + final_check_file_path = __(install_dir_path, final_check_file) + logging.info('final_check_file_path=%s', final_check_file_path) + # 最終チェックファイルがあるかを確認する - if os.path.exists(__(install_dir_path, final_check_file)) is False: + if os.path.exists(final_check_file_path) is False: raise Exception(_('ERR_NOT_EXIST_FINAL_CHECK_FILE')) + # 最終チェックファイルがある場所に対してファイルを展開する + final_check_file_dir = os.path.dirname(final_check_file_path) + logging.info('final_check_file_dir=%s', final_check_file_dir) + path, headers = urllib.request.urlretrieve(target_zip_url) + + logging.info('path=%s,headers=%s', path, headers) + with zipfile.ZipFile(path) as existing_zip: - existing_zip.extractall(install_dir_path) + existing_zip.extractall(final_check_file_dir) + + logging.info('zip unpacked') def install_downloader(target_repository, install_dir_path): - asset_file_path = download_asset_from_github( - repository_author=target_repository.get("author"), - repository_name=target_repository.get("name"), - out_file_path=__(tempfile.mkdtemp(), "a.zip") - ) - with zipfile.ZipFile(asset_file_path) as existing_zip: - existing_zip.extractall(install_dir_path) + logging.info('install downloader') + + with tempfile.TemporaryFile() as w: + download_asset_from_github( + repository_author=target_repository.get("author"), + repository_name=target_repository.get("name"), + out_file=w + ) + + logging.info('asset_file_path=%s', w) + + with zipfile.ZipFile(w) as existing_zip: + existing_zip.extractall(install_dir_path) + + logging.info('zip unpacked') def get_game_install_dir_path(target_app_id): + logging.info('Get install dir path') # レジストリを見て、Steamのインストール先を探す # 32bitを見て、なければ64bitのキーを探しに行く。それでもなければそもそもインストールされていないと判断する try: @@ -70,12 +96,15 @@ def get_game_install_dir_path(target_app_id): except OSError: raise Exception(_("ERR_NOT_FIND_STEAM_REGKEY")) + logging.info('Find steam_install_key in reg') + try: steam_install_path, key_type = winreg.QueryValueEx(steam_install_key, "InstallPath") except FileNotFoundError: raise Exception(_("ERR_NOT_FIND_INSTALLPATH_IN_STEAM_REGKEY")) steam_install_key.Close() + logging.info('steam_install_path=%s, key_type=%s', steam_install_path, key_type) # 基本問題ないと思うが念の為 if key_type != winreg.REG_SZ: @@ -86,10 +115,14 @@ def get_game_install_dir_path(target_app_id): if os.path.exists(steam_apps_path) is False: raise Exception(_("ERR_NOT_EXIST_DEFAULT_STEAMAPPS_DIR")) + logging.info('steam_apps_path=%s', steam_apps_path) + # acfがあるフォルダを列挙 acf_dir_paths = [steam_apps_path] acf_dir_paths.extend(get_lib_folders_from_vdf(steam_apps_path)) + logging.info('acf_dir_paths=%s', acf_dir_paths) + # 各ディレクトリについて処理 game_install_dir_path = None for dir_path in acf_dir_paths: @@ -99,6 +132,8 @@ def get_game_install_dir_path(target_app_id): else: break + logging.info('game_install_dir_path=%s', game_install_dir_path) + if game_install_dir_path is None: raise Exception(_("ERR_NOT_FIND_TARGET_GAME_ON_YOUR_PC")) @@ -106,9 +141,13 @@ def get_game_install_dir_path(target_app_id): def get_game_install_dir(dir_path, target_app_id): + logging.info('get game install dir') + # インストールディレクトリにあるsteamapps/appmanifest_[APPID].acfを探す target_app_acf_path = __(dir_path, "appmanifest_{}.acf".format(target_app_id)) + logging.info('target_app_acf_path=%s', target_app_acf_path) + # なければ終了 if os.path.exists(target_app_acf_path) is False: return None @@ -117,12 +156,15 @@ def get_game_install_dir(dir_path, target_app_id): install_dir_pattern = re.compile(r'\s*"installdir"\s+"(.*)') game_install_dir_name = None with open(target_app_acf_path, mode='r', encoding="utf8", errors='ignore') as target_app_acf_file: + logging.info('open %s', target_app_acf_path) for line in target_app_acf_file: result = install_dir_pattern.match(line) if result is not None: game_install_dir_name = result.group(1).strip("\"") break + logging.info('game_install_dir_name=%s', game_install_dir_name) + if game_install_dir_name is None: raise Exception(_("ERR_INVALID_ACF")) @@ -132,37 +174,57 @@ def get_game_install_dir(dir_path, target_app_id): if os.path.exists(game_install_dir_path) is False: raise Exception(_("ERR_NOT_EXIST_GAME_INSTALL_DIR")) + logging.info('game_install_dir_path=%s', game_install_dir_path) + return game_install_dir_path def get_lib_folders_from_vdf(steam_apps_path): + logging.info('get lib folders from vdf') + # vdfファイルを探す library_folders_vdf_path = __(steam_apps_path, "libraryfolders.vdf") if os.path.exists(library_folders_vdf_path) is False: raise Exception(_("ERR_NOT_FIND_LIBRARYFOLDERS_VDF")) + logging.info('library_folders_vdf_path=%s', library_folders_vdf_path) + # vdfファイルにある"[数字]" "xxxx"をさがす install_dir_pattern = re.compile(r'\s*"[0-9]+"\s+"(.*)') + logging.info('install_dir_pattern=%s', install_dir_pattern) + game_libs_paths = [] with open(library_folders_vdf_path, mode='r', encoding="utf8", errors='ignore') as target_vdf_file: + logging.info('open %s', library_folders_vdf_path) for line in target_vdf_file: result = install_dir_pattern.match(line) if result is not None: game_libs_paths.append(__(result.group(1).strip("\"").replace("\\\\", "\\"), "steamapps")) + logging.info('game_libs_paths=%s', game_libs_paths) + return game_libs_paths def install_key_file(save_file_path, mod_title, key_file_path): + logging.info('install key file') + with open(save_file_path, encoding="utf-8", mode='w', errors='ignore') as fw: + logging.info('open %s', save_file_path) fw.write("\n".join([ mod_title, key_file_path ])) + logging.info('finish') + def dll_installer(app_id, final_check_file, target_zip_url=None, target_repository=None): + logging.info('Start dll installer') install_dir_path = get_game_install_dir_path(app_id) + + logging.info('install_dir_path=%s', install_dir_path) + if target_zip_url is not None: src_url = target_zip_url else: @@ -170,14 +232,23 @@ def dll_installer(app_id, final_check_file, target_zip_url=None, target_reposito repository_author=target_repository.get("author"), repository_name=target_repository.get("name") ) + + logging.info('src_url=%s', src_url) + install(install_dir_path, src_url, final_check_file) + logging.info('finish') + def uninstaller(uninstall_info_list): + logging.info('uninstall') + for info in uninstall_info_list: final_check_file = info['final_check_file'] remove_target_paths = info['remove_target_paths'] + logging.info('remove_target_paths=%s', remove_target_paths) + base_path = '.' if 'app_id' in info: @@ -188,8 +259,11 @@ def uninstaller(uninstall_info_list): info['game_dir_name']) # Modダウンローダーをuninstallモードで起動 if os.path.exists(__(base_path, "claes.exe")): + logging.info('claes uninstall mode') sb.call(__(base_path, "claes.exe /uninstall-all")) + logging.info('base_path=%s', base_path) + # 最終チェックファイルがあるかを確認する。このファイルがあるかどうかで本当にインストールされているか判断する if os.path.exists(__(base_path, final_check_file)) is False: raise Exception(_('ERR_NOT_EXIST_FINAL_CHECK_FILE')) @@ -197,46 +271,74 @@ def uninstaller(uninstall_info_list): for path in remove_target_paths: remove_util(__(base_path, path)) + logging.info('finish') + def get_my_documents_folder(): + logging.info('get my documents folder') + # APIを使って探す buf = ctypes.create_unicode_buffer(MAX_PATH + 1) if ctypes.windll.shell32.SHGetSpecialFolderPathW(None, buf, 0x0005, False): + logging.info('search done. buf.value=%s', buf.value) return buf.value else: raise Exception(_("ERR_SHGetSpecialFolderPathW")) def mod_installer(app_id, target_repository, key_file_name, game_dir_name, key_list_url): + logging.info('mod install') + # My Documents をAPIから見つける install_game_dir_path = __(get_my_documents_folder(), "Paradox Interactive", game_dir_name) + logging.info('install_game_dir_path=%s', install_game_dir_path) + # exeを見つける install_dll_dir_path = get_game_install_dir_path(app_id) + logging.info('install_dll_dir_path=%s', install_dll_dir_path) + + logging.info('install downloader') + # Modダウンローダーを配置 install_downloader(target_repository=target_repository, install_dir_path=install_game_dir_path) + logging.info('done') + # keyファイルを保存 os.makedirs(__(install_game_dir_path, 'claes.key'), exist_ok=True) key_ids = json.loads(urllib.request.urlopen(key_list_url).read().decode('utf8')) + + logging.info('key_ids=%s', key_ids) + for item in key_ids: install_key_file(save_file_path=__(install_game_dir_path, "claes.key", item.get("id") + ".key"), mod_title=item.get("name"), key_file_path=__(install_dll_dir_path, key_file_name)) + logging.info('call claes') + # Modダウンローダーを起動 sb.call(__(install_game_dir_path, "claes.exe")) + logging.info('done') + def about(): + logging.info('open About dialog') messagebox.showinfo(_('ABOUT_BOX_TITLE'), _('ABOUT_BOX_MESSAGE')) if __name__ == '__main__': + logging.basicConfig(filename='info.log', level=logging.INFO) + logging.info('GUI setup') + + repo_url = "https://raw.githubusercontent.com/matanki-saito/SimpleInstaller/master/" + executor = concurrent.futures.ThreadPoolExecutor(max_workers=2) root = tkinter.Tk() @@ -250,21 +352,13 @@ def about(): nb = ttk.Notebook(width=300, height=200, style="BW.TLabel") # タブの作成 - tab1 = tkinter.Frame(nb, pady=0, relief='flat') tab2 = tkinter.Frame(nb, pady=0, relief='flat') tab3 = tkinter.Frame(nb, pady=0, relief='flat') - nb.add(tab1, text=_('TAB_MBDLL'), padding=0) nb.add(tab2, text=_('TAB_JPMOD'), padding=0) nb.add(tab3, text=_('TAB_INFO'), padding=0) nb.pack(expand=True, fill='both') - # Install DLL - frame1_1 = tkinter.Frame(tab1, pady=0, relief='flat') - frame1_1.pack(expand=True, fill='both') - - dl_url = "https://github.com/matanki-saito/SimpleInstaller/files/" - def on_enter(e, bg_color, font_color): e.widget['background'] = bg_color @@ -295,92 +389,105 @@ def done(self): feature.add_done_callback(done) - # EU4 - eu4DllInstallButton = tkinter.Button(frame1_1, - activebackground='#d3a243', - background='#d3a243', - fg="#8f2e14", - relief='flat', - text=_('INSTALL_EU4_MBDLL'), - command=lambda: threader(eu4DllInstallButton, lambda: dll_installer( - app_id=236850, - final_check_file='eu4.exe', - target_repository={ - "author": "matanki-saito", - "name": "eu4dll" - }, - )), - font=("sans-selif", 16, "bold")) - eu4DllInstallButton.pack(expand=True, fill='both') - eu4DllInstallButton.bind("", lambda e: on_enter(e, "#e6b422", "#9a493f")) - eu4DllInstallButton.bind("", lambda e: on_leave(e, "#d3a243", "#8f2e14")) - - # CK2 - ck2DllInstallButton = tkinter.Button(frame1_1, - activebackground='#2c4f54', - background='#2c4f54', - fg='#c1e4e9', - relief='flat', - text=_('INSTALL_CK2_MBDLL'), - command=lambda: threader(ck2DllInstallButton, lambda: dll_installer( - app_id=203770, - final_check_file='ck2game.exe', - target_repository={ - "author": "matanki-saito", - "name": "ck2dll" - }, - )), - font=("sans-selif", 16, "bold")) - ck2DllInstallButton.pack(expand=True, fill='both') - ck2DllInstallButton.bind("", lambda e: on_enter(e, "#478384", "#1f3134")) - ck2DllInstallButton.bind("", lambda e: on_leave(e, "#2c4f54", "#c1e4e9")) - - # Install downloader and jpmod + # Install dll , downloader and jpmod frame1_2 = tkinter.Frame(tab2, pady=0) frame1_2.pack(expand=True, fill='both') - repo_url = "https://raw.githubusercontent.com/matanki-saito/SimpleInstaller/master/" + + + def eu4_button_function(): + logging.info('Push a button to install eu4') + dll_installer( + app_id=236850, + final_check_file='eu4.exe', + target_repository={ + "author": "matanki-saito", + "name": "eu4dll" + }, + ) + + mod_installer( + app_id=236850, + target_repository={ + "author": "matanki-saito", + "name": "moddownloader" + }, + key_file_name='eu4.exe', + game_dir_name="Europa Universalis IV", + key_list_url=repo_url + "eu4mods.json") + + + def ck2_button_function(): + logging.info('Push a button to install ck2') + dll_installer( + app_id=203770, + final_check_file='ck2game.exe', + target_repository={ + "author": "matanki-saito", + "name": "ck2dll" + }, + ) + + mod_installer( + app_id=203770, + target_repository={ + "author": "matanki-saito", + "name": "moddownloader" + }, + key_file_name='ck2game.exe', + game_dir_name="Crusader Kings II", + key_list_url=repo_url + "ck2mods.json") + + + def ir_button_function(): + logging.info('Push a button to install I:R') + dll_installer( + app_id=859580, + final_check_file='binaries/imperator.exe', + target_repository={ + "author": "matanki-saito", + "name": "irdll" + }, + ) + # EU4 - eu4ModInstallButton = tkinter.Button(frame1_2, - activebackground='#d3a243', - background='#d3a243', - relief='flat', - fg="#8f2e14", - text=_('INSTALL_EU4_JPMOD'), - command=lambda: threader(eu4ModInstallButton, lambda: mod_installer( - app_id=236850, - target_repository={ - "author": "matanki-saito", - "name": "moddownloader" - }, - key_file_name='eu4.exe', - game_dir_name="Europa Universalis IV", - key_list_url=repo_url + "eu4mods.json")), - font=("sans-selif", 16, "bold")) - eu4ModInstallButton.pack(expand=True, fill='both') - eu4ModInstallButton.bind("", lambda e: on_enter(e, "#e6b422", "#9a493f")) - eu4ModInstallButton.bind("", lambda e: on_leave(e, "#d3a243", "#8f2e14")) + eu4InstallButton = tkinter.Button(frame1_2, + activebackground='#d3a243', + background='#d3a243', + relief='flat', + fg="#8f2e14", + text=_('INSTALL_EU4'), + command=lambda: threader(eu4InstallButton, eu4_button_function), + font=("sans-selif", 16, "bold")) + eu4InstallButton.pack(expand=True, fill='both') + eu4InstallButton.bind("", lambda e: on_enter(e, "#e6b422", "#000000")) + eu4InstallButton.bind("", lambda e: on_leave(e, "#d3a243", "#8f2e14")) # CK2 - ck2ModInstallButton = tkinter.Button(frame1_2, - activebackground='#2c4f54', - background='#2c4f54', - relief='flat', - fg='#c1e4e9', - text=_('INSTALL_CK2_JPMOD'), - command=lambda: threader(ck2ModInstallButton, lambda: mod_installer( - app_id=203770, - target_repository={ - "author": "matanki-saito", - "name": "moddownloader" - }, - key_file_name='ck2game.exe', - game_dir_name="Crusader Kings II", - key_list_url=repo_url + "ck2mods.json")), - font=("sans-selif", 16, "bold")) - ck2ModInstallButton.pack(expand=True, fill='both') - ck2ModInstallButton.bind("", lambda e: on_enter(e, "#478384", "#1f3134")) - ck2ModInstallButton.bind("", lambda e: on_leave(e, "#2c4f54", "#c1e4e9")) + ck2InstallButton = tkinter.Button(frame1_2, + activebackground='#478384', + background='#478384', + relief='flat', + fg='#1f3134', + text=_('INSTALL_CK2'), + command=lambda: threader(ck2InstallButton, ck2_button_function), + font=("sans-selif", 16, "bold")) + ck2InstallButton.pack(expand=True, fill='both') + ck2InstallButton.bind("", lambda e: on_enter(e, "#2c4f54", "#c1e4e9")) + ck2InstallButton.bind("", lambda e: on_leave(e, "#478384", "#1f3134")) + + # I:R + irInstallButton = tkinter.Button(frame1_2, + activebackground='#cca6bf', + background='#cca6bf', + relief='flat', + fg='#8f2e14', + text=_('INSTALL_IR'), + command=lambda: threader(irInstallButton, ir_button_function), + font=("sans-selif", 16, "bold")) + irInstallButton.pack(expand=True, fill='both') + irInstallButton.bind("", lambda e: on_enter(e, "#bc64a4", "#c1e4e9")) + irInstallButton.bind("", lambda e: on_leave(e, "#cca6bf", "#8f2e14")) # その他 frame1_3 = tkinter.Frame(tab3, pady=0) @@ -466,5 +573,7 @@ def done(self): font=("Helvetica", 12)) uninstall_button_ck2.pack(expand=True, fill='both') + logging.info('mainloop') + # main root.mainloop() diff --git a/eu4mods.json b/eu4mods.json index 24e30f1..87a74c0 100644 --- a/eu4mods.json +++ b/eu4mods.json @@ -10,5 +10,9 @@ { "name": "JPMOD Font Gothic", "id": "179815082" + }, + { + "name": "EUIV Sub 2: Character/history", + "id": "176494642" } ] diff --git a/github_tool.py b/github_tool.py index 2d80383..fbf4e8d 100644 --- a/github_tool.py +++ b/github_tool.py @@ -1,4 +1,5 @@ import json +import logging import urllib.request @@ -14,39 +15,58 @@ def download_asset_url_from_github(repository_author, :param file_name: :return: """ + + logging.info('download asset url from github') + api_base_url = "https://api.github.com/repos/{}/{}".format(repository_author, repository_name) if release_tag is None: + logging.info('release_tag=%s', release_tag) response = urllib.request.urlopen("{}/releases/latest".format(api_base_url)) + logging.info('finish urlopen') content = json.loads(response.read().decode('utf8')) + logging.info('content=%s', content) release_tag = content["tag_name"] + logging.info('release_tag=%s', release_tag) if file_name is None: + logging.info('file_name=%s', file_name) response = urllib.request.urlopen("{}/releases/tags/{}".format(api_base_url, release_tag)) + logging.info('finish urlopen') content = json.loads(response.read().decode('utf8')) + logging.info('content=%s', content) file_name = content["assets"][0]["name"] + logging.info('file_name=%s', file_name) + + url = "{}/{}/{}/releases/download/{}/{}".format("https://github.com", + repository_author, + repository_name, + release_tag, + file_name) + + logging.info('url=%s', url) - return "{}/{}/{}/releases/download/{}/{}".format("https://github.com", - repository_author, - repository_name, - release_tag, - file_name - ) + return url def download_asset_from_github(repository_author, repository_name, - out_file_path, + out_file, release_tag=None, file_name=None): + logging.info('download asset from github') + request_url = download_asset_url_from_github(repository_author, repository_name, release_tag, file_name) + logging.info('request_url=%s', request_url) + req = urllib.request.Request(request_url) - with open(out_file_path, "wb", errors='ignore') as my_file: - my_file.write(urllib.request.urlopen(req).read()) + logging.info('create request') + + out_file.write(urllib.request.urlopen(req).read()) - return out_file_path + logging.info('finish urlopen') diff --git a/loca.py b/loca.py index a0ba0d8..38a59cb 100644 --- a/loca.py +++ b/loca.py @@ -55,21 +55,23 @@ def get_language_windows(system_lang=True): 'default': 'Uninstall a CK2 multibyte dll', 'ja_JP': 'アンインストール\n・CK2日本語DLL\n・関連MOD', }, - 'INSTALL_CK2_MBDLL': { - 'default': 'Install CK2 Multibyte DLL', - 'ja_JP': 'CK2日本語化DLL\nインストール', - 'zh_CN': '安装\nCK2 Multibyte DLL', - 'zh_TW': '安裝\nCK2 Multibyte DLL', - 'ko_KR': 'CK2 멀티 바이트 DLL 설치', - 'de_DE': 'Installieren Sie die CK2-Multibyte-DLL' - }, - 'INSTALL_EU4_MBDLL': { - 'default': 'Install\n EU4 Multibyte DLL', - 'ja_JP': 'EU4日本語化DLL\nインストール', - 'zh_CN': '安装\nEU4 DLL', - 'zh_TW': '安裝\nEU4 DLL', - 'ko_KR': 'EU4 멀티 바이트\nDLL 설치', - 'de_DE': 'Installieren Sie die EU4-Multibyte-DLL' + 'INSTALL_CK2': { + 'default': 'CK2\nmulti-byte patch', + 'ja_JP': 'クルセイダーキングズ2\n日本語化', + 'zh_CN': '王国风云2\n本双字节补丁', + 'zh_TW': '王國風雲2\n本雙字節補丁', + 'ko_KR': '크루세이더 킹즈2\n멀티 바이트 패치' + }, + 'INSTALL_EU4': { + 'default': 'Europa Universalis IV\nmulti-byte patch', + 'ja_JP': 'ヨーロッパユニバーサリス4\n日本語化', + 'zh_CN': '欧陆风云4\n本双字节补丁', + 'zh_TW': '歐陸風雲4\n本雙字節補丁', + 'ko_KR': '유로파 유니버설리스4\n멀티 바이트 패치' + }, + 'INSTALL_IR': { + 'default': 'Imperator: Rome\ndisplay fix patch', + 'ja_JP': 'インペラトル: ローマ\n表示修正', }, 'TITLE': { 'default': 'CK2/EU4 Multibyte DLL Installer', @@ -163,8 +165,8 @@ def get_language_windows(system_lang=True): 'default': 'DLL' }, 'TAB_JPMOD': { - 'default': 'JPMOD', - 'ja_JP': '日本語化MOD' + 'default': 'Install', + 'ja_JP': 'インストール' }, 'TAB_INFO': { 'default': 'About', diff --git a/resource/image.png b/resource/image.png index 7bceeddd8d92b83e93219c0d69f1b5ae937a9bae..29fa76e79765c7b031604e12b166212d6f13cb5a 100644 GIT binary patch literal 8092 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 LBhBh-c9H)FfI!NW literal 6889 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