import os import json import tkinter.messagebox import customtkinter from winotify import Notification, audio import keyboard import subprocess import requests import hashlib import string import random import tkinter import threading import sys import shutil import uuid import time app_name = "Dislocker" dislocker_dir = os.path.dirname(os.path.abspath(sys.argv[0])) os.chdir(dislocker_dir) resource_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "resource") config_dir_path = "./config/" client_config_path = config_dir_path + "client.json" if not os.path.isfile(client_config_path): if not os.path.isdir(config_dir_path): os.mkdir(config_dir_path) client_config = { "initial": 1, "auth_host_url": "http://localhost", "pc_number": 1, "master_password_hash": "", "testing": 0, "eraser": 1, "pc_uuid": "", "pc_token": "" } elif os.path.isfile(client_config_path): with open(client_config_path, "r") as r: client_config = json.load(r) def master_password_gen(): numbers = string.digits # (1) password = ''.join(random.choice(numbers) for _ in range(10)) # (2) password_hash = hashlib.md5(password.encode()).hexdigest() result = {"password": password, "password_hash": password_hash} return result def init(**kwargs): sp_startupinfo = subprocess.STARTUPINFO() sp_startupinfo.dwFlags = subprocess.STARTF_USESHOWWINDOW sp_startupinfo.wShowWindow = subprocess.SW_HIDE task_exist = subprocess.run('tasklist /fi "IMAGENAME eq dislocker_client.exe"', startupinfo=sp_startupinfo, stdout=subprocess.PIPE, text=True) if 'dislocker_client.exe' in task_exist.stdout: task_count = task_exist.stdout.count("dislocker_client.exe") if task_count == 1: pass else: return 1 if client_config["initial"] == 1: pc_uuid = uuid.uuid4() client_config["pc_uuid"] = str(pc_uuid) if "pc_number" in kwargs: client_config["pc_number"] = int(kwargs["pc_number"]) else: tkinter.messagebox.showerror(title=f"{app_name} | 登録時にエラー", message=f"登録時にエラーが発生しました。\nPC番号が指定されていません。1個目の引数にPC番号、2個目の引数にワンタイムパスワードを指定して、もう一度お試しください。") return 2 if "onetime" in kwargs: onetime = str(kwargs["onetime"]) else: tkinter.messagebox.showerror(title=f"{app_name} | 登録時にエラー", message=f"登録時にエラーが発生しました。\nワンタイムパスワードが指定されていません。1個目の引数にPC番号、2個目の引数にワンタイムパスワードを指定して、もう一度お試しください。") return 2 register_url = client_config["auth_host_url"] + "/register" register_json = { "pc_number": int(client_config["pc_number"]), "pc_uuid": str(pc_uuid), "onetime": onetime } responce = requests.post(register_url, json=register_json) if responce.status_code == 200: print("PCの情報が登録されました。") responce_json = responce.json() pc_token = str(responce_json["pc_token"]) client_config["pc_token"] = pc_token master_password = master_password_gen() msgbox = tkinter.messagebox.showinfo(title=f"{app_name} | 初回起動を検出", message=f"初回起動のようです。\nマスターパスワードを記録しておいてください。\nこれ以降二度と表示されることはないでしょう。\n\n{master_password["password"]}\n\nまた、認証先サーバーの接続先を指定してください。ロックを解除できなくなります。") client_config["master_password_hash"] = master_password["password_hash"] client_config["initial"] = 0 with open(client_config_path, "w") as w: json.dump(client_config, w, indent=4) return 2 else: msgbox = tkinter.messagebox.showerror(title=f"{app_name} | 登録時にエラー", message=f"登録時にエラーが発生しました。\nワンタイムパスワードが間違っている可能性があります。") return 2 else: return 0 class App(customtkinter.CTk): def __init__(self): super().__init__() self.title(f"{app_name} | ロック中") self.iconbitmap(default=resource_path + '\\icon\\dislocker.ico') if client_config["testing"] == 1: pass else: self.attributes('-fullscreen', True) self.attributes('-topmost', True) self.block_taskmgr() self.block_key() self.frame = customtkinter.CTkFrame(self, corner_radius=0, fg_color='transparent') self.frame.grid(row=0, column=0, sticky='nsew') lock = Lock() def exit(self): self.unlock_taskmgr() self.toast() self.destroy() def block_key(self): block_keys = ['ctrl', 'alt', 'windows', 'shift', 'delete'] for i in block_keys: keyboard.block_key(i) def block_taskmgr(self): block = subprocess.run(['reg', 'add', 'HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System', '/v', 'DisableTaskMgr', '/t', 'REG_DWORD', '/d', '1', '/f']) print(block) def unlock_taskmgr(self): unlock = subprocess.run(['reg', 'delete', 'HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System', '/v', 'DisableTaskMgr', '/f']) print(unlock) def toast(self): success = Notification( app_id='Dislocker', title='ご協力ありがとうございます!', msg='パスワード認証に成功しました。\n現在使われたパスワードは削除されます。', icon=resource_path + r'\success.png' ) success.set_audio(audio.Default, loop=False) success.show() def handler_close(self): pass class Lock(customtkinter.CTkToplevel): def __init__(self): super().__init__() if client_config["testing"] == 1: self.title(f'{app_name} | PC番号 {client_config["pc_number"]} | テストモード') else: self.title(f'{app_name} | PC番号 {client_config["pc_number"]} | ロックされています') self.iconbitmap(default=resource_path + '\\icon\\dislocker.ico') self.window_width = 600 self.window_height = 320 self.screen_width = self.winfo_screenwidth() self.screen_height = self.winfo_screenheight() self.center_x = int(self.screen_width/2 - self.window_width/2) self.center_y = int(self.screen_height/2 - self.window_height/2) self.geometry(f"{str(self.window_width)}x{str(self.window_height)}+{str(self.center_x)}+{str(self.center_y)}") self.resizable(height=False, width=False) self.attributes('-topmost', True) self.grab_set() self.lift() self.protocol("WM_DELETE_WINDOW", self.handler_close) self.emoji_font = customtkinter.CTkFont(family="Segoe UI Emoji", size=32) self.title_font = customtkinter.CTkFont(family="meiryo", size=32, weight="bold") self.pc_number_font = customtkinter.CTkFont(family="meiryo", size=64, weight="bold") self.title_small_font = customtkinter.CTkFont(family="meiryo", size=16) self.general_font = customtkinter.CTkFont(family="meiryo", size=18) self.general_small_font = customtkinter.CTkFont(family="meiryo", size=12) self.textbox_font = customtkinter.CTkFont(family="meiryo", size=14) self.button_font = customtkinter.CTkFont(family="meiryo", size=14) self.msg_title_frame = customtkinter.CTkFrame(self, corner_radius=0, fg_color='transparent') self.msg_title_frame.grid(row=0, column=0, padx=10, pady=10, sticky="nsew") self.icon_title_1 = customtkinter.CTkLabel(self.msg_title_frame, text='😎', font=self.emoji_font, justify="left") self.icon_title_1.grid(row=0, column=0, padx=10, sticky="w") self.msg_title_1 = customtkinter.CTkLabel(self.msg_title_frame, text=f'ちょっと待って!! PC番号 | {client_config["pc_number"]}', font=self.title_font, justify="left") self.msg_title_1.grid(row=0, column=1, padx=10, sticky="w") self.msg_title_2 = customtkinter.CTkLabel(self.msg_title_frame, text="本当にあなたですか?", font=self.title_small_font, justify="left") self.msg_title_2.grid(row=1, column=1, padx=10, sticky="w") self.msg_subtitle_frame = customtkinter.CTkFrame(self, corner_radius=0, fg_color='transparent') self.msg_subtitle_frame.grid(row=1, column=0, padx=10, pady=10, sticky="nsew") self.msg_subtitle_frame.grid_columnconfigure(0, weight=1) self.msg_subtitle_1 = customtkinter.CTkLabel(self.msg_subtitle_frame, text='サインインするには、Discordのダイレクトメッセージに送信された\nパスワードを入力してください。', font=self.general_font, justify="left") self.msg_subtitle_1.grid(row=0, column=0, padx=10, sticky="ew") self.msg_subtitle_2 = customtkinter.CTkLabel(self.msg_subtitle_frame, text='※ パスワードの有効期限は23:59までです。', font=self.general_small_font, justify="left") self.msg_subtitle_2.grid(row=1, column=0, padx=10, sticky="w") self.input_frame = customtkinter.CTkFrame(self, corner_radius=0, fg_color='transparent') self.input_frame.grid(row=2, column=0, padx=10, pady=10, sticky="nsew") self.input_frame.columnconfigure(0, weight=1) self.password_entry = customtkinter.CTkEntry(self.input_frame, placeholder_text='パスワード', show='*', font=self.textbox_font) self.password_entry.grid(row=0, column=0, padx=10, sticky="ew") self.password_entry.bind("", self.auth_start_ev) self.button_frame = customtkinter.CTkFrame(self, corner_radius=0, fg_color='transparent') self.button_frame.grid(row=3, column=0, padx=10, pady=10, sticky="nsew") self.button_frame.columnconfigure(0, weight=3) self.button_frame.columnconfigure(1, weight=1) self.button_frame.columnconfigure(2, weight=1) self.signin_button = customtkinter.CTkButton(self.button_frame, text='サインイン', command=self.auth_start, font=self.button_font) self.signin_button.grid(row=0, column=2, padx=10, sticky="e") self.signout_button = customtkinter.CTkButton(self.button_frame, text='サインアウト', command=self.signout, font=self.button_font) self.signout_button.grid(row=0, column=1, padx=10, sticky="e") self.help_button = customtkinter.CTkButton(self.button_frame, text='ヘルプ', command=self.help_dummy, font=self.button_font) self.help_button.grid(row=0, column=0, padx=10, sticky="w") self.keyboard_listener_thread = threading.Thread(target=self.keyboard_listener) self.keyboard_listener_thread.daemon = True self.keyboard_listener_thread.start() def help_wakeup(self): help = Help() def keyboard_listener(self): keyboard.add_hotkey('ctrl+shift+q', self.exit) def hash_genarate(self, source): hashed = hashlib.md5(source.encode()) return hashed.hexdigest() def auth_start(self): auth_thread = threading.Thread(target=self.auth) auth_thread.daemon = True auth_thread.start() def auth_start_ev(self, event): auth_thread = threading.Thread(target=self.auth) auth_thread.daemon = True auth_thread.start() def button_disable(self): self.help_button.configure(state="disabled", fg_color="gray") self.signin_button.configure(state="disabled", fg_color="gray") self.signout_button.configure(state="disabled", fg_color="gray") def button_enable(self): self.help_button.configure(state="normal", fg_color="#3c8dd0") self.signin_button.configure(state="normal", fg_color="#3c8dd0") self.signout_button.configure(state="normal", fg_color="#3c8dd0") def auth(self): self.button_disable() password = str(self.password_entry.get()) if len(password) == 10: print("マスターパスワードで認証を試行します。") master_password_hash = self.hash_genarate(str(self.password_entry.get())) if client_config["master_password_hash"] == master_password_hash: print("マスターパスワードで認証しました。") self.exit() else: print("マスターパスワードで認証できませんでした。") self.withdraw() msgbox = tkinter.messagebox.showinfo(title=f"{app_name} | 誤ったパスワード", message=f"パスワードが間違っています!") self.msg_subtitle_1.configure(text='パスワードが間違っています! ') self.button_enable() self.deiconify() print("認証サーバーにアクセスします。") auth_url = client_config["auth_host_url"] + "/verify" auth_json = { "pc_number": int(client_config["pc_number"]), "pc_uuid": str(client_config["pc_uuid"]), "pc_token": str(client_config["pc_token"]), "password": self.hash_genarate(str(self.password_entry.get())) } try: responce = requests.post(auth_url, json=auth_json) if responce.status_code == 200: print("認証サーバー経由で認証しました。") self.exit() else: print("認証サーバー経由での認証に失敗しました。") self.withdraw() msgbox = tkinter.messagebox.showinfo(title=f"{app_name} | 誤ったパスワード", message=f"パスワードが間違っています!") self.msg_subtitle_1.configure(text='パスワードが間違っています! ') self.button_enable() self.deiconify() except: print("認証サーバーにアクセスできません。マスターパスワードで認証を試行します。") master_password_hash = self.hash_genarate(str(self.password_entry.get())) if client_config["master_password_hash"] == master_password_hash: print("マスターパスワードで認証しました。") self.exit() else: print("マスターパスワードで認証できませんでした。") self.withdraw() msgbox = tkinter.messagebox.showinfo(title=f"{app_name} | ネットワークエラー", message=f"認証サーバーにアクセスできませんでした。\n続行するには、マスターパスワードを入力してください。") self.msg_subtitle_1.configure(text='ネットワークエラーが発生しています。\n続行するには、マスターパスワードを入力して下さい。 ') self.button_enable() self.deiconify() def signout(self): app.unlock_taskmgr() self.destroy() signout_command = subprocess.run(['shutdown', '/l', '/f']) print(signout_command) def handler_close(self): pass def help_dummy(self): self.withdraw() msgbox = tkinter.messagebox.showinfo(title=f"{app_name} | 未実装", message=f"ヘルプページは製作途中です。\nDiscordサーバーの指示に従って、認証を進めてください。") self.deiconify() def exit(self): self.destroy() app.exit() class Help(customtkinter.CTkToplevel): def __init__(self): super().__init__() if client_config["testing"] == 1: self.title(f'{app_name} | ヘルプ | テストモード') else: self.title(f'{app_name} | ヘルプ') self.iconbitmap(default=resource_path + '\\icon\\dislocker.ico') self.geometry("600x400") self.resizable(height=False, width=False) self.attributes('-topmost', True) self.grab_set() self.lift() self.protocol("WM_DELETE_WINDOW", self.handler_close) msgbox = tkinter.messagebox.showinfo(title=f"{app_name} | 未実装", message=f"ヘルプページは製作途中です。\nDiscordサーバーの指示に従って、認証を進めてください。") self.destroy() def handler_close(self): self.destroy() class Stop(): def __init__(self) -> None: pass def run(self): stop_thread = threading.Thread(target=self.stop) stop_thread.run() run_notify = Notification( app_id='Dislocker', title='終了処理を実行中', msg='終了処理を実行しています。\nPCがシャットダウンするまで、そのままでお待ち下さい。', icon=resource_path + r'\success.png' ) run_notify.set_audio(audio.Default, loop=False) run_notify.show() def delete_appdata(self, **kwargs): process_name = kwargs["process_name"] dir_path = kwargs["dir_path"] if not os.path.exists(dir_path): print(f"エラー: 指定されたディレクトリ {dir_path} が存在しません。") return 1 i = 0 i_max = 10 result = 1 while i != i_max: i += 1 try: # プロセスの終了 subprocess.run(['taskkill', '/f', '/t', '/im', process_name]) print(f"{process_name} を終了しました。") time.sleep(0.1) # ディレクトリの削除 shutil.rmtree(dir_path) if os.path.isdir(dir_path): pass else: print(f"{dir_path} を削除しました。") result = 0 i = i_max except subprocess.CalledProcessError as e: print(f"プロセス終了エラー: {e}") except PermissionError as e: print(f"権限エラー: {e}") except Exception as e: print("エラーが発生しました。\nエラー内容:") print(f"エラータイプ: {e.__class__.__name__}") print(f"エラー引数: {e.args}") print(f"エラーメッセージ: {str(e)}") return result def shutdown(self): shutdown_command = subprocess.run(['shutdown', '/s', '/t', '1']) def stop(self): print("停止処理を実行。") if client_config["eraser"] == 1: appdata_local = os.path.expandvars("%LOCALAPPDATA%") appdata_roaming = os.path.expandvars("%APPDATA%") epic_del = app.delete_appdata(process_name="EpicGamesLauncher.exe", dir_path=f"{appdata_local}\\EpicGamesLauncher\\Saved") chrome_del = app.delete_appdata(process_name="chrome.exe", dir_path=f"{appdata_local}\\Google\\Chrome\\User Data") discord_del = app.delete_appdata(process_name="discord.exe", dir_path=f"{appdata_roaming}\\discord") steam_del = app.delete_appdata(process_name="steam.exe", dir_path=f"{appdata_local}\\Steam") ea_del = app.delete_appdata(process_name="EADesktop.exe", dir_path=f"{appdata_local}\\Electronic Arts") riot_del = app.delete_appdata(process_name="RiotClientServices.exe", dir_path=f"{appdata_local}\\Riot Games\\Riot Client") else: print("削除処理をスキップ。") stop_url = client_config["auth_host_url"] + "/stop" stop_json = { "pc_number": int(client_config["pc_number"]), "pc_uuid": str(client_config["pc_uuid"]), "pc_token": str(client_config["pc_token"]) } try: responce = requests.post(stop_url, json=stop_json) if responce.status_code == 200: print("停止処理は成功しました。") elif responce.status_code == 401: print("認証に失敗しました。") tkinter.messagebox.showwarning(title=f"{app_name} | エラー", message=f"認証に失敗しました。\nDiscordサーバーの指示に従って、停止処理を自身で行ってください。") else: print("内部エラーにより停止処理に失敗しました。") result_msgbox = tkinter.messagebox.showwarning(title=f"{app_name} | エラー", message=f"内部エラーにより停止処理に失敗しました。\nDiscordサーバーの指示に従って、停止処理を自身で行ってください。") except: print("ネットワークエラーにより停止処理に失敗しました。") result_msgbox = tkinter.messagebox.showwarning(title=f"{app_name} | エラー", message=f"ネットワークエラーにより停止処理に失敗しました。\nDiscordサーバーの指示に従って、停止処理を自身で行ってください。") finally: self.shutdown() if __name__ == '__main__': args = sys.argv if len(args) >= 2: if args[1] == "stop": init_result = init() if init_result == 1: warning_msgbox = tkinter.messagebox.showwarning(title=f"{app_name} | 多重起動エラー", message=f"もう終了処理を行っています。\nPCがシャットダウンするまで、もう少しお待ちください。") elif init_result == 2: pass else: stop = Stop() stop.run() elif args[1] == "setup": init_result = init(pc_number=args[2], onetime=args[3]) if init_result == 1: warning_msgbox = tkinter.messagebox.showwarning(title=f"{app_name} | 多重起動エラー", message=f"すでに {app_name} は実行されています。\n正常に起動しない場合は、既に起動しているプロセスを終了してから、もう一度起動してみてください。") elif init_result == 2: pass else: pass else: print("引数エラー。") else: init_result = init() if init_result == 1: warning_msgbox = tkinter.messagebox.showwarning(title=f"{app_name} | 多重起動エラー", message=f"すでに {app_name} は実行されています。\n正常に起動しない場合は、既に起動しているプロセスを終了してから、もう一度起動してみてください。") elif init_result == 2: pass else: app = App() app.protocol("WM_DELETE_WINDOW", app.handler_close) app.mainloop()