├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── ja-feature-request.yml │ ├── en-feature-request.yml │ ├── ja-bug-report.yml │ └── en-bug-report.yml └── dependabot.yml ├── pyproject.toml ├── docs ├── img │ ├── steam.png │ ├── subscribe1.png │ ├── subscribe2.png │ ├── taskscheduler.png │ ├── windows_defender1.png │ ├── windows_defender2.png │ ├── device_auth_config.png │ └── DMMGamePlayerProductIdChecker1.png └── README-advance.md ├── assets ├── icons │ ├── Task.ico │ ├── Task.png │ ├── DMMGamePlayerFastLauncher.ico │ ├── DMMGamePlayerFastLauncher.png │ ├── DMMGamePlayerProductIdChecker.ico │ └── DMMGamePlayerProductIdChecker.png ├── template │ ├── shortcut.ps1 │ └── schtasks.xml ├── i18n │ ├── app.zh_CN.yml │ ├── app.zh_TW.yml │ ├── app.ja_JP.yml │ └── app.en_US.yml └── themes │ ├── blue.json │ ├── dark-blue.json │ ├── green.json │ ├── magenta.json │ ├── red.json │ ├── torquoise.json │ └── purple.json ├── requirements-lock.txt ├── DMMGamePlayerFastLauncher ├── lib │ ├── DGPSessionWrap.py │ ├── thread.py │ ├── discord.py │ ├── version.py │ ├── toast.py │ ├── process_manager.py │ └── DGPSessionV2.py ├── static │ ├── constant.py │ ├── dump.py │ ├── env.py │ ├── config.py │ └── loder.py ├── tab │ ├── __init__.py │ ├── home.py │ ├── help.py │ └── setting.py ├── component │ ├── slider.py │ ├── var.py │ ├── variable_base.py │ ├── logger.py │ ├── tab_menu.py │ └── component.py ├── models │ ├── shortcut_data.py │ └── setting_data.py ├── app.py ├── utils │ └── utils.py ├── DMMGamePlayerFastLauncher.py └── launch.py ├── .gitignore ├── requirements.txt ├── .vscode ├── extensions.json ├── settings.json └── launch.json ├── test └── test_process.py ├── tools ├── build.py ├── build.ps1 └── i18n.py ├── windows └── tools │ └── refresh.ps1 ├── LICENSE ├── README.md ├── README-en.md └── setup.iss /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.ruff] 2 | target-version = "py310" 3 | line-length = 180 -------------------------------------------------------------------------------- /docs/img/steam.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fa0311/DMMGamePlayerFastLauncher/HEAD/docs/img/steam.png -------------------------------------------------------------------------------- /assets/icons/Task.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fa0311/DMMGamePlayerFastLauncher/HEAD/assets/icons/Task.ico -------------------------------------------------------------------------------- /assets/icons/Task.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fa0311/DMMGamePlayerFastLauncher/HEAD/assets/icons/Task.png -------------------------------------------------------------------------------- /requirements-lock.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fa0311/DMMGamePlayerFastLauncher/HEAD/requirements-lock.txt -------------------------------------------------------------------------------- /docs/img/subscribe1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fa0311/DMMGamePlayerFastLauncher/HEAD/docs/img/subscribe1.png -------------------------------------------------------------------------------- /docs/img/subscribe2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fa0311/DMMGamePlayerFastLauncher/HEAD/docs/img/subscribe2.png -------------------------------------------------------------------------------- /docs/img/taskscheduler.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fa0311/DMMGamePlayerFastLauncher/HEAD/docs/img/taskscheduler.png -------------------------------------------------------------------------------- /docs/img/windows_defender1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fa0311/DMMGamePlayerFastLauncher/HEAD/docs/img/windows_defender1.png -------------------------------------------------------------------------------- /docs/img/windows_defender2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fa0311/DMMGamePlayerFastLauncher/HEAD/docs/img/windows_defender2.png -------------------------------------------------------------------------------- /docs/img/device_auth_config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fa0311/DMMGamePlayerFastLauncher/HEAD/docs/img/device_auth_config.png -------------------------------------------------------------------------------- /assets/icons/DMMGamePlayerFastLauncher.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fa0311/DMMGamePlayerFastLauncher/HEAD/assets/icons/DMMGamePlayerFastLauncher.ico -------------------------------------------------------------------------------- /assets/icons/DMMGamePlayerFastLauncher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fa0311/DMMGamePlayerFastLauncher/HEAD/assets/icons/DMMGamePlayerFastLauncher.png -------------------------------------------------------------------------------- /DMMGamePlayerFastLauncher/lib/DGPSessionWrap.py: -------------------------------------------------------------------------------- 1 | from lib.DGPSessionV2 import DgpSessionV2 2 | 3 | 4 | class DgpSessionWrap(DgpSessionV2): 5 | pass 6 | -------------------------------------------------------------------------------- /docs/img/DMMGamePlayerProductIdChecker1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fa0311/DMMGamePlayerFastLauncher/HEAD/docs/img/DMMGamePlayerProductIdChecker1.png -------------------------------------------------------------------------------- /assets/icons/DMMGamePlayerProductIdChecker.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fa0311/DMMGamePlayerFastLauncher/HEAD/assets/icons/DMMGamePlayerProductIdChecker.ico -------------------------------------------------------------------------------- /assets/icons/DMMGamePlayerProductIdChecker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fa0311/DMMGamePlayerFastLauncher/HEAD/assets/icons/DMMGamePlayerProductIdChecker.png -------------------------------------------------------------------------------- /DMMGamePlayerFastLauncher/static/constant.py: -------------------------------------------------------------------------------- 1 | from static.dump import Dump 2 | 3 | 4 | class Constant(Dump): 5 | ALWAYS_EXTRACT_FROM_DMM = "ALWAYS_EXTRACT_FROM_DMM" 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /dist 3 | /*.spec 4 | __pycache__ 5 | *.exe 6 | *.bytes 7 | /.venv 8 | 9 | 10 | 11 | /data 12 | 13 | /windows/assets 14 | 15 | assets/license/LICENSE -------------------------------------------------------------------------------- /DMMGamePlayerFastLauncher/tab/__init__.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | from .account import AccountTab 3 | from .help import HelpTab 4 | from .setting import SettingTab 5 | from .shortcut import ShortcutTab 6 | -------------------------------------------------------------------------------- /DMMGamePlayerFastLauncher/component/slider.py: -------------------------------------------------------------------------------- 1 | from customtkinter import CTkSlider 2 | 3 | 4 | class CTkFloatSlider(CTkSlider): 5 | def __init__(self, *args, **kwargs): 6 | super().__init__(*args, **kwargs) 7 | -------------------------------------------------------------------------------- /DMMGamePlayerFastLauncher/static/dump.py: -------------------------------------------------------------------------------- 1 | class Dump: 2 | @classmethod 3 | def dump(cls): 4 | item = [(k, v) for k, v in cls.__dict__.items() if not k.startswith("__") and not isinstance(v, classmethod)] 5 | return dict(item) 6 | -------------------------------------------------------------------------------- /DMMGamePlayerFastLauncher/lib/thread.py: -------------------------------------------------------------------------------- 1 | import threading 2 | 3 | 4 | def threading_wrapper(func): 5 | def _wrapper(self, *arg, **kwargs): 6 | threading.Thread(target=func, args=(self, *arg), kwargs=kwargs).start() 7 | 8 | return _wrapper 9 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | i18nice[YAML] 2 | coloredlogs 3 | customtkinter 4 | requests 5 | windows-pathlib 6 | selenium 7 | Pillow 8 | tkinter-colored-logging-handlers 9 | psutil 10 | requests 11 | pycryptodome 12 | pywin32 13 | pypresence 14 | pyinstaller 15 | pyinstaller-hooks-contrib -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "ms-python.vscode-pylance", 4 | "ms-python.python", 5 | "ms-python.debugpy", 6 | "charliermarsh.ruff", 7 | "davidanson.vscode-markdownlint", 8 | "esbenp.prettier-vscode" 9 | ] 10 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/ja-feature-request.yml: -------------------------------------------------------------------------------- 1 | name: "[ja] 💡 機能を提案する" 2 | description: DMMGamePlayerFastLauncherに欲しい機能を提案して下さい 3 | labels: ["enhancement"] 4 | body: 5 | - type: textarea 6 | id: feature-description 7 | attributes: 8 | label: 機能を説明する 9 | validations: 10 | required: true 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/en-feature-request.yml: -------------------------------------------------------------------------------- 1 | name: "[en] 💡 Propose a Feature" 2 | description: Please propose the desired features for DMMGamePlayerFastLauncher. 3 | labels: ["enhancement"] 4 | body: 5 | - type: textarea 6 | id: feature-description 7 | attributes: 8 | label: Feature Description 9 | validations: 10 | required: true 11 | -------------------------------------------------------------------------------- /assets/template/shortcut.ps1: -------------------------------------------------------------------------------- 1 | $WshShell = New-Object -ComObject WScript.Shell; 2 | $ShortCut = $WshShell.CreateShortcut("{{SOURCE}}"); 3 | $ShortCut.TargetPath = "{{TARGET}}"; 4 | $ShortCut.WorkingDirectory = "{{WORKING_DIRECTORY}}"; 5 | $ShortCut.IconLocation = "{{ICON_LOCATION}}"; 6 | $ShortCut.Arguments = "{{ARGUMENTS}}"; 7 | $ShortCut.WindowStyle = 1; 8 | $ShortCut.Description = "Made by DMMGamePlayerFastLauncher"; 9 | $ShortCut.Save(); -------------------------------------------------------------------------------- /DMMGamePlayerFastLauncher/component/var.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from tkinter import StringVar 3 | from typing import Optional 4 | 5 | 6 | class PathVar(StringVar): 7 | def __init__(self, master=None, value: Optional[Path] = None, name=None): 8 | super().__init__(master, str(value) if value else None, name) 9 | 10 | def get_path(self): 11 | return Path(super().get()) 12 | 13 | def set_path(self, path: Path): 14 | super().set(str(path)) 15 | -------------------------------------------------------------------------------- /test/test_process.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from DMMGamePlayerFastLauncher.lib.process_manager import ProcessManager 4 | 5 | 6 | class TestProcessManager(unittest.TestCase): 7 | def test_admin_check(self): 8 | value = ProcessManager.admin_check() 9 | self.assertFalse(value) 10 | 11 | def test_run(self): 12 | value = ProcessManager.run(["echo", "test"]) 13 | data = value.communicate() 14 | self.assertEqual(data, (b"test\r\n", b"")) 15 | 16 | 17 | if __name__ == "__main__": 18 | unittest.main() 19 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "pip" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /tools/build.py: -------------------------------------------------------------------------------- 1 | import glob 2 | from pathlib import Path 3 | 4 | output = "" 5 | width = 62 6 | 7 | 8 | delimiter = "\n\n" + ("=" * width) + "\n\n" 9 | 10 | Path("assets/license").mkdir(parents=True, exist_ok=True) 11 | 12 | for file in ("./../DMMGamePlayerFastLauncher/LICENSE", *glob.glob(".venv/**/*[Ll][Ii][Cc][Ee][Nn][SsCc][Ee]*", recursive=True)): 13 | path = Path(file) 14 | if path.is_file(): 15 | with open(file, "r", encoding="utf-8") as f: 16 | output += delimiter + path.parent.name.center(width * 2 - 1) + delimiter + f.read() 17 | 18 | 19 | with open("assets/license/LICENSE", "w", encoding="utf-8") as f: 20 | f.write(output) 21 | -------------------------------------------------------------------------------- /DMMGamePlayerFastLauncher/lib/discord.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import time 3 | 4 | import i18n 5 | from pypresence import Presence 6 | from static.config import DiscordConfig 7 | 8 | 9 | def start_rich_presence(pid: int, id: str, title: str): 10 | try: 11 | RPC = Presence(DiscordConfig.CLIENT_ID) 12 | RPC.connect() 13 | RPC.update( 14 | name=title, 15 | state=i18n.t("app.title"), 16 | pid=pid, 17 | start=int(time.time()), 18 | large_image=f"https://media.games.dmm.com/freegame/client/{id}/200.gif", 19 | ) 20 | except Exception as e: 21 | logging.error(f"Failed to start rich presence: {e}") 22 | -------------------------------------------------------------------------------- /windows/tools/refresh.ps1: -------------------------------------------------------------------------------- 1 | if (!([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole("Administrators")) { 2 | Start-Process powershell.exe "-File `"$PSCommandPath`" -NoNewWindow -Wait" -WorkingDirectory $PSScriptRoot -Verb RunAs; 3 | exit; 4 | } 5 | 6 | Get-ScheduledTask | Where-Object TaskPath -eq "\Microsoft\Windows\DMMGamePlayerFastLauncher\" | Unregister-ScheduledTask -Confirm:$false 7 | $schtasks = Join-Path -Path (Split-Path -Path ($PSScriptRoot) -Parent) -ChildPath "data\schtasks" 8 | Get-ChildItem -Path $schtasks | ForEach-Object { schtasks.exe /create /xml $_.FullName /tn "\Microsoft\Windows\DMMGamePlayerFastLauncher\$($_.Name -replace '\.xml$')" } 9 | Read-Host -Prompt "Press Enter to exit" -------------------------------------------------------------------------------- /tools/build.ps1: -------------------------------------------------------------------------------- 1 | pip freeze > requirements-lock.txt 2 | python .\tools\build.py 3 | 4 | 5 | pyinstaller DMMGamePlayerFastLauncher\DMMGamePlayerFastLauncher.py --noconsole --onefile --add-data ".venv\Lib\site-packages\customtkinter\;customtkinter" --icon assets\icons\DMMGamePlayerFastLauncher.ico 6 | 7 | Copy-Item -Path "dist\DMMGamePlayerFastLauncher.exe" -Destination "windows" -Force 8 | Copy-Item -Path "assets" -Destination "windows" -Force -Recurse 9 | 10 | Invoke-WebRequest -Uri "https://raw.githubusercontent.com/kira-96/Inno-Setup-Chinese-Simplified-Translation/refs/heads/main/ChineseSimplified.isl" -OutFile "C:\Users\yuki\AppData\Local\Programs\Inno Setup 6\Languages\ChineseSimplified.isl" 11 | Start-Process "C:\Users\yuki\AppData\Local\Programs\Inno Setup 6\ISCC.exe" "setup.iss" -Wait -NoNewWindow 12 | 13 | Compress-Archive -Path "windows\*" -DestinationPath "dist\DMMGamePlayerFastLauncher.zip" -Force -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "markdownlint.config": { 3 | "MD033": false 4 | }, 5 | "python.analysis.typeCheckingMode": "basic", 6 | "[python]": { 7 | "editor.defaultFormatter": "charliermarsh.ruff" 8 | }, 9 | "[json]": { 10 | "editor.defaultFormatter": "esbenp.prettier-vscode" 11 | }, 12 | "[jsonc]": { 13 | "editor.defaultFormatter": "esbenp.prettier-vscode" 14 | }, 15 | "[yaml]": { 16 | "editor.defaultFormatter": "esbenp.prettier-vscode" 17 | }, 18 | "files.exclude": { 19 | "**/__pycache__": true, 20 | "**/build": true, 21 | "**/dist": true, 22 | // "**/data": true, 23 | "**/*.spec": true, 24 | "**/.venv": true 25 | }, 26 | "python.testing.unittestArgs": ["-v", "-p", "test_*.py", "-s", "test"], 27 | "python.testing.cwd": "${workspaceRoot}", 28 | "python.testing.pytestEnabled": false, 29 | "python.testing.unittestEnabled": true, 30 | "discord.enabled": false 31 | } 32 | -------------------------------------------------------------------------------- /DMMGamePlayerFastLauncher/component/variable_base.py: -------------------------------------------------------------------------------- 1 | import json 2 | from pathlib import Path 3 | from tkinter import Variable 4 | 5 | 6 | class VariableBase: 7 | def __init__(self, *args, **kwargs): 8 | super().__init__(*args, **kwargs) 9 | 10 | def to_dict(self) -> dict[str, str]: 11 | return {k: v.get() if isinstance(v, Variable) else v for k, v in self.__dict__.items()} 12 | 13 | @classmethod 14 | def from_dict(cls, obj: dict[str, str]): 15 | default = cls().__dict__ 16 | item = [(k, v(value=obj.get(k, default[k].get()))) for k, v in cls.__annotations__.items()] 17 | return cls(**dict(item)) 18 | 19 | @classmethod 20 | def from_path(cls, path: Path): 21 | with open(path, "r", encoding="utf-8") as f: 22 | data = json.load(f) 23 | return cls.from_dict(data) 24 | 25 | def write_path(self, path: Path): 26 | with open(path, "w", encoding="utf-8") as f: 27 | json.dump(self.to_dict(), f, ensure_ascii=False, indent=4) 28 | -------------------------------------------------------------------------------- /DMMGamePlayerFastLauncher/static/env.py: -------------------------------------------------------------------------------- 1 | import os 2 | from pathlib import Path 3 | 4 | import requests 5 | from static.config import UrlConfig 6 | from static.dump import Dump 7 | from windows_pathlib import WindowsPathlib 8 | 9 | 10 | class Env(Dump): 11 | VERSION = "v6.3.4" 12 | RELEASE_VERSION = requests.get(UrlConfig.RELEASE_API).json().get("tag_name", VERSION) 13 | 14 | DEVELOP: bool = os.environ.get("ENV") == "DEVELOP" 15 | APPDATA: Path = Path(os.getenv("APPDATA", default="")) 16 | HOMEPATH: Path = Path(os.getenv("USERPROFILE", default="")) 17 | PROGURAM_FILES: Path = Path(os.getenv("PROGRAMFILES", default="")) 18 | DESKTOP: Path = WindowsPathlib.desktop() 19 | 20 | DEFAULT_DMM_GAME_PLAYER_PROGURAM_FOLDER: Path = PROGURAM_FILES.joinpath("DMMGamePlayer") 21 | DEFAULT_DMM_GAME_PLAYER_DATA_FOLDER: Path = APPDATA.joinpath("dmmgameplayer5") 22 | 23 | DMM_GAME_PLAYER_HIDDEN_FOLDER: Path = HOMEPATH.joinpath(".DMMGamePlayer") 24 | 25 | SYSTEM_ROOT = Path(os.getenv("SYSTEMROOT", default="")) 26 | SYSTEM32 = SYSTEM_ROOT.joinpath("System32") 27 | SCHTASKS = SYSTEM32.joinpath("schtasks.exe") 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 [yuki](https://yuki0311.com/) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /DMMGamePlayerFastLauncher/models/shortcut_data.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | from dataclasses import dataclass, field 3 | from tkinter import BooleanVar, StringVar 4 | 5 | from component.var import PathVar 6 | from component.variable_base import VariableBase 7 | 8 | 9 | @dataclass 10 | class ShortcutData(VariableBase): 11 | product_id: StringVar = field(default_factory=StringVar) 12 | account_path: PathVar = field(default_factory=PathVar) 13 | game_args: StringVar = field(default_factory=StringVar) 14 | auto_update: BooleanVar = field(default_factory=lambda: BooleanVar(value=True)) 15 | game_type: StringVar = field(default_factory=lambda: StringVar(value="GCL")) 16 | rich_presence: BooleanVar = field(default_factory=lambda: BooleanVar(value=True)) 17 | external_tool_path: PathVar = field(default_factory=lambda: PathVar()) 18 | 19 | 20 | @dataclass 21 | class LauncherShortcutData(VariableBase): 22 | account_path: PathVar = field(default_factory=PathVar) 23 | dgp_args: StringVar = field(default_factory=StringVar) 24 | 25 | 26 | @dataclass 27 | class BrowserConfigData(VariableBase): 28 | browser: StringVar = field(default_factory=StringVar) 29 | profile_name: StringVar = field(default_factory=lambda: StringVar(value=uuid.uuid4().hex)) 30 | -------------------------------------------------------------------------------- /DMMGamePlayerFastLauncher/component/logger.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | import customtkinter as ctk 4 | from customtkinter import CTkTextbox, CTkToplevel 5 | from tkinter_colored_logging_handlers import ColorSchemeLight, LoggingHandler, StyleSchemeBase 6 | 7 | 8 | class StyleScheme(StyleSchemeBase, ColorSchemeLight): 9 | UNDERLINE = ("UNDERLINE", "4", {"underline": True}) 10 | BLINK = ("BLINK", "5", {"overstrike": True}) 11 | REVERSE = ("REVERSE", "7", {"overstrike": True}) 12 | STRIKE = ("STRIKE", "9", {"overstrike": True}) 13 | 14 | 15 | class TkinkerLogger(CTkToplevel): 16 | box: CTkTextbox 17 | 18 | def __init__(self, master): 19 | super().__init__(master) 20 | self.title("Log") 21 | self.geometry("600x300") 22 | self.box = CTkTextbox(self, height=30) 23 | self.protocol("WM_DELETE_WINDOW", lambda: self.withdraw()) 24 | 25 | def create(self): 26 | self.box.pack(fill=ctk.BOTH, padx=10, pady=(0, 10), expand=True) 27 | return self 28 | 29 | 30 | class LoggingHandlerMask(LoggingHandler): 31 | def format(self, record): 32 | formated = super().format(record) 33 | formated = re.sub(r"(?<=\=)[0-9a-zA-Z]{32,}", lambda x: f"\033[31m[CENSORED {len(x.group())}]\033[0m", formated) 34 | return formated 35 | -------------------------------------------------------------------------------- /DMMGamePlayerFastLauncher/tab/home.py: -------------------------------------------------------------------------------- 1 | import webbrowser 2 | from tkinter import Misc 3 | 4 | import i18n 5 | from customtkinter import CTkFont, CTkFrame, CTkImage, CTkLabel 6 | from lib.toast import ToastController 7 | from PIL import Image 8 | from static.config import AssetsPathConfig, UrlConfig 9 | from static.env import Env 10 | 11 | 12 | class HomeTab(CTkFrame): 13 | toast: ToastController 14 | update_flag: bool = False 15 | 16 | def __init__(self, master: Misc): 17 | super().__init__(master, fg_color="transparent") 18 | self.toast = ToastController(self) 19 | 20 | def create(self): 21 | frame = CTkFrame(self, fg_color="transparent") 22 | frame.pack(anchor="center", expand=1) 23 | 24 | image = CTkImage(light_image=Image.open(AssetsPathConfig.ICONS.joinpath("DMMGamePlayerFastLauncher.png")), size=(240, 240)) 25 | CTkLabel(frame, image=image, text="").pack() 26 | CTkLabel(frame, text=i18n.t("app.title"), font=CTkFont(size=28)).pack(pady=20) 27 | 28 | CTkLabel(frame, text=Env.VERSION, font=CTkFont(size=18)).pack() 29 | 30 | if Env.RELEASE_VERSION != Env.VERSION and HomeTab.update_flag is False: 31 | HomeTab.update_flag = True 32 | self.toast.command_info(i18n.t("app.home.new_version"), lambda: webbrowser.open(UrlConfig.RELEASE)) 33 | 34 | return self 35 | -------------------------------------------------------------------------------- /DMMGamePlayerFastLauncher/lib/version.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | 4 | class Version: 5 | def __init__(self, version): 6 | if not re.match(r"v\d{1,}\.\d{1,}\.\d{1,}", version): 7 | raise ValueError(f"Invalid version format: {version}") 8 | self.major, self.minor, self.patch = map(int, version[1:].split(".")) 9 | 10 | def __str__(self): 11 | return f"v{self.major}.{self.minor}.{self.patch}" 12 | 13 | def __eq__(self, other: "Version"): 14 | return self.major == other.major and self.minor == other.minor and self.patch == other.patch 15 | 16 | def __ne__(self, other: "Version"): 17 | return not self.__eq__(other) 18 | 19 | def __lt__(self, other: "Version"): 20 | return self.major < other.major or self.minor < other.minor or self.patch < other.patch 21 | 22 | def __le__(self, other: "Version"): 23 | return self.__eq__(other) or self.__lt__(other) 24 | 25 | def __gt__(self, other: "Version"): 26 | return self.major > other.major or self.minor > other.minor or self.patch > other.patch 27 | 28 | def __ge__(self, other: "Version"): 29 | return self.__eq__(other) or self.__gt__(other) 30 | 31 | def __hash__(self): 32 | return hash((self.major, self.minor, self.patch)) 33 | 34 | def to_dict(self): 35 | return {"major": self.major, "minor": self.minor, "patch": self.patch} 36 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/ja-bug-report.yml: -------------------------------------------------------------------------------- 1 | name: "[ja] 🐛 バグを報告する" 2 | description: DMMGamePlayerFastLauncherの不具合を報告する。 3 | labels: ["bug"] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | ## バグ報告の前に 9 | バグを報告する前に、[Issues](https://github.com/fa0311/DMMGamePlayerFastLauncher/issues) に同様のバグがないか確認してください。 10 | 同様のバグがあり、それらに補足情報を追加できる場合は、コメントで追記してください。 11 | 12 | - type: input 13 | id: os 14 | attributes: 15 | label: OS 16 | description: バージョンも含めてください 17 | placeholder: "例: Window 10" 18 | validations: 19 | required: true 20 | 21 | - type: textarea 22 | id: bug-description 23 | attributes: 24 | label: 不具合の説明 25 | description: バグの内容を詳しく記述してください。必要があれば画像を添付して下さい。 26 | placeholder: | 27 | 設定がxxxxxxの際にプリコネRのショートカットが起動できません。 28 | ウマ娘のショートカットは起動できます。 29 | プリコネRのproduct_idはxxxxxです。 30 | ... 31 | validations: 32 | required: true 33 | 34 | - type: textarea 35 | id: error-message 36 | attributes: 37 | label: エラーメッセージ 38 | description: エラーメッセージが表示されていればそれを貼り付けてください。 39 | render: Shell 40 | validations: 41 | required: false 42 | 43 | - type: textarea 44 | id: log 45 | attributes: 46 | label: ログ 47 | description: デバッグウィンドウに表示されているログを貼り付けてください。Tokenなどの個人情報が含まれている場合は伏せてください。 48 | render: Shell 49 | validations: 50 | required: false 51 | -------------------------------------------------------------------------------- /DMMGamePlayerFastLauncher/tab/help.py: -------------------------------------------------------------------------------- 1 | import webbrowser 2 | 3 | import customtkinter as ctk 4 | import i18n 5 | from customtkinter import CTkBaseClass, CTkButton, CTkScrollableFrame, CTkTextbox 6 | from lib.toast import ToastController 7 | from static.config import AssetsPathConfig, UrlConfig 8 | 9 | 10 | class HelpTab(CTkScrollableFrame): 11 | toast: ToastController 12 | 13 | def __init__(self, master: CTkBaseClass): 14 | super().__init__(master, fg_color="transparent") 15 | self.toast = ToastController(self) 16 | 17 | def create(self): 18 | CTkButton(self, text=i18n.t("app.help.coop_in_develop"), command=self.contribution_callback).pack(fill=ctk.X, pady=10) 19 | CTkButton(self, text=i18n.t("app.help.donations_to_developer"), command=self.donation_callback).pack(fill=ctk.X, pady=10) 20 | CTkButton(self, text=i18n.t("app.help.bug_report"), command=self.report_callback).pack(fill=ctk.X, pady=10) 21 | 22 | with open(AssetsPathConfig.LICENSE, "r", encoding="utf-8") as f: 23 | license = f.read() 24 | 25 | box = CTkTextbox(self, width=590, height=400) 26 | box.pack(padx=10, pady=(0, 10)) 27 | box.insert("0.0", license) 28 | 29 | return self 30 | 31 | def contribution_callback(self): 32 | webbrowser.open(UrlConfig.CONTRIBUTION) 33 | 34 | def donation_callback(self): 35 | webbrowser.open(UrlConfig.DONATE) 36 | 37 | def report_callback(self): 38 | webbrowser.open(UrlConfig.ISSUE) 39 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // IntelliSense を使用して利用可能な属性を学べます。 3 | // 既存の属性の説明をホバーして表示します。 4 | // 詳細情報は次を確認してください: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Python: 現在のファイル", 9 | "type": "debugpy", 10 | "request": "launch", 11 | "program": "${file}", 12 | "console": "integratedTerminal", 13 | "justMyCode": true 14 | }, 15 | { 16 | "name": "DMMGamePlayerFastLauncher", 17 | "type": "debugpy", 18 | "request": "launch", 19 | "program": "DMMGamePlayerFastLauncher/DMMGamePlayerFastLauncher.py", 20 | "console": "integratedTerminal", 21 | "justMyCode": false, 22 | "env": { 23 | "ENV": "DEVELOP" 24 | } 25 | }, 26 | { 27 | "name": "GameLauncher", 28 | "type": "debugpy", 29 | "request": "launch", 30 | "program": "DMMGamePlayerFastLauncher/DMMGamePlayerFastLauncher.py", 31 | "console": "integratedTerminal", 32 | "justMyCode": false, 33 | "args": ["priconner"], 34 | "env": { 35 | "ENV": "DEVELOP" 36 | } 37 | }, 38 | { 39 | "name": "LaunchLauncher", 40 | "type": "debugpy", 41 | "request": "launch", 42 | "program": "DMMGamePlayerFastLauncher/DMMGamePlayerFastLauncher.py", 43 | "console": "integratedTerminal", 44 | "justMyCode": false, 45 | "args": ["main", "--type", "launcher"], 46 | "env": { 47 | "ENV": "DEVELOP" 48 | } 49 | } 50 | ] 51 | } 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DMMGamePlayerFastLauncher 2 | 3 | DMM Game Player のゲームを高速かつセキュアに起動できるランチャー 4 | 5 | ![img.shields.io](https://img.shields.io/github/downloads/fa0311/DMMGamePlayerFastLauncher/total) 6 | 7 | [日本語](/README.md) / [English](/README-en.md) 8 | 9 | [詳しい使い方](/docs/README-advance.md) 10 | 11 | ## 特徴 12 | 13 | - **ワンクリックでゲームを起動** 14 | - **高速** 15 | - **DMM にハードウェア情報を送信しない** 16 | - **隠されているコマンドライン引数を使用可能** 17 | - **GUI で設定可能** 18 | - **複数アカウントの管理** 19 | - **ゲームの自動アップデート** 20 | - **DRMで保護されたゲームの起動** 21 | - **Steam 経由での起動** 22 | - **Discord Rich Presence** 23 | 24 | ## インストール 25 | 26 | [Releases](https://github.com/fa0311/DMMGamePlayerFastLauncher/releases) から `DMMGamePlayerFastLauncher-Setup.exe` をダウンロード 27 | 実行してセットアップする 28 | 29 | ## 使い方 30 | 31 | `%AppData%\DMMGamePlayerFastLauncher\DMMGamePlayerFastLauncher.exe` を起動する 32 | 33 | [詳しい使い方](/docs/README-advance.md) 34 | 35 | ## 貢献 36 | 37 | ### 翻訳 38 | 39 | このツールを翻訳してくれる方はランゲージファイルを作成し、プルリクエストを送ってください。 40 | 41 | ランゲージファイルは`assets/i18n`に配置されています。 42 | 43 | ### バグ報告 44 | 45 | バグを見つけた場合は [Issues](https://github.com/fa0311/DMMGamePlayerFastLauncher/issues/new/choose) から報告してください 46 | 47 | ## 典拠 48 | 49 | - [Lutwidse/priconner_launch.py](https://gist.github.com/Lutwidse/82d8e7a20c96296bc0318f1cb6bf26ee) 50 | - [kira-96/Inno-Setup-Chinese-Simplified-Translation](https://github.com/kira-96/Inno-Setup-Chinese-Simplified-Translation) 51 | - [@takafi CustomTkinter 簡易カスタムテーマ作成ツール](https://qiita.com/takafi/items/90c17b7888263100cbbc) 52 | 53 | ## ライセンス 54 | 55 | DMMGamePlayerFastLauncher is under MIT License 56 | -------------------------------------------------------------------------------- /tools/i18n.py: -------------------------------------------------------------------------------- 1 | import glob 2 | import re 3 | from typing import Any 4 | 5 | import yaml 6 | 7 | yaml_load = {} 8 | for lang in glob.glob("assets/i18n/*.yml"): 9 | with open(lang, "r", encoding="utf-8") as f: 10 | yaml_load.update(yaml.safe_load(f.read())) 11 | 12 | 13 | def in_py(key): 14 | for file in glob.glob("**/*.py", root_dir="DMMGamePlayerFastLauncher", recursive=True): 15 | with open(f"DMMGamePlayerFastLauncher/{file}", "r", encoding="utf-8") as f: 16 | if f'i18n.t("{key}"' in f.read(): 17 | return False 18 | return True 19 | 20 | 21 | def i18n_flatten(data: dict[str, Any], parent: str) -> list[str]: 22 | res = [] 23 | for k, v in data.items(): 24 | if isinstance(v, dict): 25 | res.extend(i18n_flatten(v, f"{parent}.{k}")) 26 | elif isinstance(v, str): 27 | res.append(f"{parent}.{k}") 28 | return res 29 | 30 | 31 | def get_py(): 32 | res = [] 33 | for file in glob.glob("**/*.py", root_dir="DMMGamePlayerFastLauncher", recursive=True): 34 | with open(f"DMMGamePlayerFastLauncher/{file}", "r", encoding="utf-8") as f: 35 | match = re.findall(r'i18n.t\("([a-z\.\_]*?)"', f.read()) 36 | res.extend(match) 37 | return res 38 | 39 | 40 | i18n = i18n_flatten(yaml_load["ja_JP"], "app") 41 | 42 | for lang in yaml_load.keys(): 43 | target = i18n_flatten(yaml_load[lang], "app") 44 | for key in i18n: 45 | if key not in target: 46 | print(f"not found in {lang}: {key}") 47 | 48 | 49 | print("===") 50 | for key in i18n: 51 | if in_py(key): 52 | print(f"not found: {key}") 53 | 54 | print("===") 55 | for key in get_py(): 56 | if key not in i18n: 57 | print(f"not used: {key}") 58 | -------------------------------------------------------------------------------- /DMMGamePlayerFastLauncher/static/config.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | from static.dump import Dump 4 | 5 | 6 | class DataPathConfig(Dump): 7 | DATA = Path("data") 8 | ACCOUNT = DATA.joinpath("account") 9 | ACCOUNT_SHORTCUT = DATA.joinpath("account_shortcut") 10 | SHORTCUT = DATA.joinpath("shortcut") 11 | BROWSER_PROFILE = DATA.joinpath("browser_profile") 12 | BROWSER_CONFIG = DATA.joinpath("browser_config") 13 | LOG = DATA.joinpath("log") 14 | APP_CONFIG = DATA.joinpath("config.json") 15 | SCHTASKS = DATA.joinpath("schtasks") 16 | DEVICE = DATA.joinpath("device.json") 17 | 18 | 19 | class AssetsPathConfig(Dump): 20 | PATH = Path("assets") 21 | I18N = PATH.joinpath("i18n") 22 | ICONS = PATH.joinpath("icons") 23 | LICENSE = PATH.joinpath("license").joinpath("LICENSE") 24 | TEMPLATE = PATH.joinpath("template") 25 | THEMES = PATH.joinpath("themes") 26 | 27 | ICON_MAIN = ICONS.joinpath("DMMGamePlayerFastLauncher.ico") 28 | 29 | SCHTASKS = TEMPLATE.joinpath("schtasks.xml") 30 | SHORTCUT = TEMPLATE.joinpath("shortcut.ps1") 31 | 32 | 33 | class UrlConfig(Dump): 34 | CONTRIBUTION = "https://github.com/fa0311/DMMGamePlayerFastLauncher" 35 | RELEASE_API = "https://api.github.com/repos/fa0311/DMMGamePlayerFastLauncher/releases/latest" 36 | RELEASE = "https://github.com/fa0311/DMMGamePlayerFastLauncher/releases/latest" 37 | DONATE = "https://github.com/sponsors/fa0311" 38 | ISSUE = "https://github.com/fa0311/DMMGamePlayerFastLauncher/issues/new/choose" 39 | 40 | 41 | class SchtasksConfig(Dump): 42 | FILE = "schtasks_v1_{0}_{1}" 43 | NAME = "\\Microsoft\\Windows\\DMMGamePlayerFastLauncher\\{0}" 44 | 45 | 46 | class DiscordConfig(Dump): 47 | CLIENT_ID = "1209708526889345075" 48 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/en-bug-report.yml: -------------------------------------------------------------------------------- 1 | name: "[en] 🐛 Report a Bug" 2 | description: Report a bug in DMMGamePlayerFastLauncher. 3 | labels: ["bug"] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | ## Before Reporting a Bug 9 | Before reporting a bug, please check [Issues](https://github.com/fa0311/DMMGamePlayerFastLauncher/issues) to see if a similar bug has already been reported. If there is a similar bug and you can provide additional information, please add a comment. 10 | 11 | - type: input 12 | id: os 13 | attributes: 14 | label: OS 15 | description: Please include the version. 16 | placeholder: "Example: Windows 10" 17 | validations: 18 | required: true 19 | 20 | - type: textarea 21 | id: bug-description 22 | attributes: 23 | label: Bug Description 24 | description: Please provide detailed information about the bug. Attach images if necessary. 25 | placeholder: | 26 | The shortcut for Princess Connect Re:Dive does not launch when the setting is xxxxxx. 27 | The shortcut for Uma Musume launches successfully. 28 | The product_id for Princess Connect Re:Dive is xxxxx. 29 | ... 30 | validations: 31 | required: true 32 | 33 | - type: textarea 34 | id: error-message 35 | attributes: 36 | label: Error Message 37 | description: If an error message is displayed, please paste it here. 38 | render: Shell 39 | validations: 40 | required: false 41 | 42 | - type: textarea 43 | id: log 44 | attributes: 45 | label: Log 46 | description: Paste the logs displayed in the debug window here. If there are personal information such as tokens, please redact them. 47 | render: Shell 48 | validations: 49 | required: false 50 | -------------------------------------------------------------------------------- /assets/template/schtasks.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 2023-01-01T00:00:00 5 | fa0311 6 | uac_bypass_v1 7 | \Microsoft\Windows\DMMGamePlayerFastLauncher\{{UID}} 8 | 9 | 10 | 11 | 12 | {{SID}} 13 | InteractiveToken 14 | HighestAvailable 15 | 16 | 17 | 18 | Parallel 19 | false 20 | false 21 | false 22 | false 23 | false 24 | 25 | true 26 | false 27 | 28 | true 29 | true 30 | false 31 | false 32 | false 33 | true 34 | false 35 | PT0S 36 | 6 37 | 38 | 39 | 40 | {{COMMAND}} 41 | {{ARGUMENTS}} 42 | {{WORKING_DIRECTORY}} 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /README-en.md: -------------------------------------------------------------------------------- 1 | # DMMGamePlayerFastLauncher 2 | 3 | DMM Game Player Fast Launcher for secure and fast start-up 4 | 5 | ![img.shields.io](https://img.shields.io/github/downloads/fa0311/DMMGamePlayerFastLauncher/total) 6 | 7 | [日本語](/README.md) / [English](/README-en.md) 8 | 9 | [Detailed instructions(Japanese)](/docs/README-advance.md) 10 | 11 | [Tutorial Video](https://github.com/fa0311/DMMGamePlayerFastLauncher/issues/135) 12 | 13 | ## Features 14 | 15 | - **One click to launch the game** 16 | - **Fast** 17 | - **Do not send hardware information to DMM** 18 | - **Hidden command line arguments can be used** 19 | - **Configurable with GUI** 20 | - **Manage multiple accounts** 21 | - **Automatic game update** 22 | - **Launch DRM protected games** 23 | - **Launch via Steam** 24 | - **Discord Rich Presence** 25 | 26 | ## Installation 27 | 28 | Download `DMMGamePlayerFastLauncher-Setup.exe` from [Releases](https://github.com/fa0311/DMMGamePlayerFastLauncher/releases) 29 | Run and set up 30 | 31 | ## Using 32 | 33 | Start `%AppData%\DMMGamePlayerFastLauncher\DMMGamePlayerFastLauncher.exe`. 34 | 35 | [Detailed instructions](/docs/README-advance.md) 36 | 37 | ## Contribute 38 | 39 | ### Translations 40 | 41 | If you want to translate this tool, language file please send a pull request. 42 | 43 | language file are located in `assets/i18n`. 44 | 45 | ### Bug reports 46 | 47 | If you find a bug, please report it in [Issues](https://github.com/fa0311/DMMGamePlayerFastLauncher/issues/new/choose) 48 | 49 | ## Source 50 | 51 | - [Lutwidse/priconner_launch.py](https://gist.github.com/Lutwidse/82d8e7a20c96296bc0318f1cb6bf26ee) 52 | - [kira-96/Inno-Setup-Chinese-Simplified-Translation](https://github.com/kira-96/Inno-Setup-Chinese-Simplified-Translation) 53 | - [@takafi CustomTkinter 簡易カスタムテーマ作成ツール](https://qiita.com/takafi/items/90c17b7888263100cbbc) 54 | 55 | ## License 56 | 57 | DMMGamePlayerFastLauncher is under MIT License 58 | -------------------------------------------------------------------------------- /DMMGamePlayerFastLauncher/app.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from typing import Callable 3 | 4 | import customtkinter as ctk 5 | import i18n 6 | from component.tab_menu import TabMenuComponent 7 | from customtkinter import CTk, CTkFrame 8 | from static.config import AssetsPathConfig 9 | from tab.account import AccountTab 10 | from tab.help import HelpTab 11 | from tab.home import HomeTab 12 | from tab.setting import SettingTab 13 | from tab.shortcut import ShortcutTab 14 | 15 | 16 | class App(CTk): 17 | loder: Callable 18 | tab: TabMenuComponent 19 | 20 | def __init__(self, loder): 21 | super().__init__() 22 | 23 | self.title("DMMGamePlayer Fast Launcher") 24 | self.geometry("900x600") 25 | self.protocol("WM_DELETE_WINDOW", sys.exit) 26 | self.iconbitmap(default=str(AssetsPathConfig.ICON_MAIN)) 27 | self.loder = loder 28 | self.tab = TabMenuComponent(self) 29 | loder(self) 30 | 31 | def create(self): 32 | self.tab.create() 33 | self.tab.add(text=i18n.t("app.tab.home"), callback=self.home_callback) 34 | self.tab.add(text=i18n.t("app.tab.shortcut"), callback=self.shortcut_callback) 35 | self.tab.add(text=i18n.t("app.tab.account"), callback=self.account_callback) 36 | self.tab.add(text=i18n.t("app.tab.setting"), callback=self.setting_callback) 37 | self.tab.add(text=i18n.t("app.tab.help"), callback=self.help_callback) 38 | return self 39 | 40 | def home_callback(self, master: CTkFrame): 41 | HomeTab(master).create().pack(expand=True, fill=ctk.BOTH) 42 | 43 | def shortcut_callback(self, master: CTkFrame): 44 | ShortcutTab(master).create().pack(expand=True, fill=ctk.BOTH) 45 | 46 | def account_callback(self, master: CTkFrame): 47 | AccountTab(master).create().pack(expand=True, fill=ctk.BOTH) 48 | 49 | def setting_callback(self, master): 50 | SettingTab(master).create().pack(expand=True, fill=ctk.BOTH) 51 | 52 | def help_callback(self, master): 53 | HelpTab(master).create().pack(expand=True, fill=ctk.BOTH) 54 | -------------------------------------------------------------------------------- /DMMGamePlayerFastLauncher/static/loder.py: -------------------------------------------------------------------------------- 1 | import json 2 | import logging 3 | from pathlib import Path 4 | 5 | from lib.version import Version 6 | from models.setting_data import AppConfig, DeviceData, SettingData 7 | from static.config import AssetsPathConfig, DataPathConfig 8 | from static.env import Env 9 | from utils.utils import get_supported_lang 10 | 11 | 12 | def config_loder(): 13 | if not DataPathConfig.DATA.exists(): 14 | raise FileNotFoundError(f"{DataPathConfig.DATA} not found") 15 | if not AssetsPathConfig.PATH.exists(): 16 | raise FileNotFoundError(f"{AssetsPathConfig.PATH} not found") 17 | 18 | if DataPathConfig.APP_CONFIG.exists(): 19 | with open(DataPathConfig.APP_CONFIG, "r", encoding="utf-8") as f: 20 | AppConfig.DATA = SettingData.from_dict(json.load(f)) 21 | else: 22 | AppConfig.DATA = SettingData() 23 | with open(DataPathConfig.APP_CONFIG, "w+", encoding="utf-8") as f: 24 | json.dump(AppConfig.DATA.to_dict(), f) 25 | 26 | if DataPathConfig.DEVICE.exists(): 27 | with open(DataPathConfig.DEVICE, "r", encoding="utf-8") as f: 28 | AppConfig.DEVICE = DeviceData.from_dict(json.load(f)) 29 | else: 30 | AppConfig.DEVICE = DeviceData() 31 | with open(DataPathConfig.DEVICE, "w+", encoding="utf-8") as f: 32 | json.dump(AppConfig.DEVICE.to_dict(), f) 33 | 34 | AppConfig.DATA.update() 35 | AppConfig.DEVICE.update() 36 | 37 | 38 | def config_migrate(): 39 | if AppConfig.DATA.last_version.get() != Env.VERSION: 40 | version = Version(AppConfig.DATA.last_version.get()) 41 | logging.info(f"Migration from {version} to {Env.VERSION}") 42 | 43 | if version < Version("v5.5.2"): 44 | logging.info("Migration from v5.5.0 to v5.5.1") 45 | Path(AssetsPathConfig.I18N).joinpath("app.ja.yml").unlink(missing_ok=True) 46 | Path(AssetsPathConfig.I18N).joinpath("app.en.yml").unlink(missing_ok=True) 47 | 48 | if AppConfig.DATA.lang.get() not in [x[0] for x in get_supported_lang()]: 49 | AppConfig.DATA.lang.set("en_US") 50 | 51 | AppConfig.DATA.last_version.set(Env.VERSION) 52 | with open(DataPathConfig.APP_CONFIG, "w+", encoding="utf-8") as f: 53 | json.dump(AppConfig.DATA.to_dict(), f) 54 | -------------------------------------------------------------------------------- /DMMGamePlayerFastLauncher/component/tab_menu.py: -------------------------------------------------------------------------------- 1 | from tkinter import Misc 2 | from typing import Callable 3 | 4 | import customtkinter as ctk 5 | from customtkinter import CTkButton, CTkFrame 6 | from customtkinter import ThemeManager as CTkm 7 | from utils.utils import children_destroy 8 | 9 | 10 | class TabMenuComponent: 11 | tab_master: CTkFrame 12 | body_master: CTkFrame 13 | row: int 14 | selected: int 15 | 16 | def __init__(self, master: Misc) -> None: 17 | self.tab_master = CTkFrame(master) 18 | self.body_master = CTkFrame(master, fg_color="transparent") 19 | self.row = 0 20 | self.selected = 0 21 | 22 | def create(self): 23 | self.tab_master.pack(side=ctk.LEFT, fill=ctk.Y, padx=5, pady=5) 24 | self.body_master.pack(side=ctk.LEFT, expand=True, fill=ctk.BOTH, padx=0, pady=5) 25 | children_destroy(self.tab_master) 26 | self.row = 0 27 | return self 28 | 29 | def add(self, text: str, callback: Callable): 30 | row = self.row 31 | text_color = CTkm.theme["MenuComponent"]["text_color"] 32 | command = lambda: self.callback_wrapper(callback, row=row) # noqa E731 33 | 34 | btn = CTkButton(self.tab_master, text=text, fg_color="transparent", text_color=text_color, command=command) 35 | btn.pack(pady=2, padx=4) 36 | 37 | if self.selected == row: 38 | self.render(callback, row=row) 39 | self.row += 1 40 | 41 | def callback_wrapper(self, callback, row): 42 | if self.selected != row: 43 | self.render(callback, row) 44 | 45 | def render(self, callback, row): 46 | for key, child in enumerate(self.tab_master.winfo_children()): 47 | if key == row: 48 | self.selected = key 49 | child.configure( 50 | fg_color=CTkm.theme["CTkButton"]["fg_color"], 51 | hover_color=CTkm.theme["CTkButton"]["fg_color"], 52 | text_color=CTkm.theme["CTkButton"]["text_color"], 53 | ) 54 | else: 55 | child.configure( 56 | fg_color="transparent", 57 | hover_color=CTkm.theme["CTkButton"]["hover_color"], 58 | text_color=CTkm.theme["MenuComponent"]["text_color"], 59 | ) 60 | child.update() 61 | 62 | children_destroy(self.body_master) 63 | callback(self.body_master) 64 | 65 | def is_dark(self): 66 | return ctk.get_appearance_mode() == "Dark" 67 | -------------------------------------------------------------------------------- /DMMGamePlayerFastLauncher/models/setting_data.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass, field 2 | from tkinter import BooleanVar, DoubleVar, StringVar 3 | 4 | from component.var import PathVar 5 | from component.variable_base import VariableBase 6 | from lib.DGPSessionV2 import DgpSessionV2 7 | from static.env import Env 8 | from utils.utils import get_default_locale 9 | 10 | 11 | @dataclass 12 | class SettingData(VariableBase): 13 | last_version: StringVar = field(default_factory=lambda: StringVar(value="v0.0.0")) # field(default_factory=lambda: StringVar(value=Env.VERSION)) 14 | dmm_game_player_program_folder: PathVar = field(default_factory=lambda: PathVar(value=Env.DEFAULT_DMM_GAME_PLAYER_PROGURAM_FOLDER)) 15 | dmm_game_player_data_folder: PathVar = field(default_factory=lambda: PathVar(value=Env.DEFAULT_DMM_GAME_PLAYER_DATA_FOLDER)) 16 | proxy_all: StringVar = field(default_factory=StringVar) 17 | dmm_proxy_all: StringVar = field(default_factory=StringVar) 18 | lang: StringVar = field(default_factory=lambda: StringVar(value=get_default_locale()[0])) 19 | theme: StringVar = field(default_factory=lambda: StringVar(value="blue")) 20 | theme_font: StringVar = field(default_factory=lambda: StringVar(value="i18n")) 21 | appearance_mode: StringVar = field(default_factory=lambda: StringVar(value="dark")) 22 | window_scaling: DoubleVar = field(default_factory=lambda: DoubleVar(value=1.0)) 23 | debug_window: BooleanVar = field(default_factory=lambda: BooleanVar(value=False)) 24 | output_logfile: BooleanVar = field(default_factory=lambda: BooleanVar(value=False)) 25 | mask_token: BooleanVar = field(default_factory=lambda: BooleanVar(value=True)) 26 | 27 | def update(self): 28 | DgpSessionV2.DGP5_PATH = self.dmm_game_player_program_folder.get_path() 29 | DgpSessionV2.DGP5_DATA_PATH = self.dmm_game_player_data_folder.get_path() 30 | 31 | 32 | @dataclass 33 | class DeviceData(VariableBase): 34 | mac_address: StringVar = field(default_factory=lambda: StringVar(value=DgpSessionV2.DGP5_DEVICE_PARAMS["mac_address"])) 35 | hdd_serial: StringVar = field(default_factory=lambda: StringVar(value=DgpSessionV2.DGP5_DEVICE_PARAMS["hdd_serial"])) 36 | motherboard: StringVar = field(default_factory=lambda: StringVar(value=DgpSessionV2.DGP5_DEVICE_PARAMS["motherboard"])) 37 | user_os: StringVar = field(default_factory=lambda: StringVar(value=DgpSessionV2.DGP5_DEVICE_PARAMS["user_os"])) 38 | 39 | def update(self): 40 | DgpSessionV2.DGP5_DEVICE_PARAMS = { 41 | "mac_address": self.mac_address.get(), 42 | "hdd_serial": self.hdd_serial.get(), 43 | "motherboard": self.motherboard.get(), 44 | "user_os": self.user_os.get(), 45 | } 46 | 47 | 48 | class AppConfig: 49 | DATA: SettingData 50 | DEVICE: DeviceData 51 | -------------------------------------------------------------------------------- /DMMGamePlayerFastLauncher/utils/utils.py: -------------------------------------------------------------------------------- 1 | import locale 2 | import time 3 | import urllib.parse 4 | from pathlib import Path 5 | from tkinter import Misc 6 | from typing import Optional, Tuple, TypeVar 7 | 8 | import i18n 9 | from selenium import webdriver 10 | from selenium.webdriver.chrome.options import Options as ChromeOptions 11 | from selenium.webdriver.edge.options import Options as EdgeOptions 12 | from selenium.webdriver.firefox.options import Options as FirefoxOptions 13 | from static.config import AssetsPathConfig 14 | 15 | T = TypeVar("T") 16 | 17 | 18 | def isinstance_filter(obj, cls: type[T]) -> list[T]: 19 | return list(filter(lambda x: isinstance(x, cls), obj)) 20 | 21 | 22 | def get_isinstance(obj, cls: type[T]) -> Optional[T]: 23 | ins = isinstance_filter(obj, cls) 24 | if len(ins) > 0: 25 | return ins[0] 26 | return None 27 | 28 | 29 | def children_destroy(master: Misc): 30 | for child in master.winfo_children(): 31 | child.destroy() 32 | 33 | 34 | def file_create(path: Path, name: str): 35 | if path.exists(): 36 | raise FileExistsError(i18n.t("app.utils.file_exists", name=name)) 37 | else: 38 | path.touch() 39 | 40 | 41 | def get_supported_lang() -> list[tuple[str, str]]: 42 | return [(y, i18n.t("app.language", locale=y)) for y in [x.suffixes[0][1:] for x in AssetsPathConfig.I18N.iterdir()]] 43 | 44 | 45 | def get_default_locale() -> Tuple[str, str]: 46 | lang, encoding = locale.getdefaultlocale() 47 | if lang not in [x[0] for x in get_supported_lang()]: 48 | lang = "en" 49 | if encoding is None: 50 | encoding = "utf-8" 51 | return lang, encoding 52 | 53 | 54 | def get_driver(browser: str, path: Optional[Path]) -> webdriver.Chrome | webdriver.Edge | webdriver.Firefox: 55 | absolute_path = path.absolute() if path is not None else None 56 | if browser == "Chrome": 57 | options = ChromeOptions() 58 | if absolute_path is not None: 59 | options.add_argument(f"--user-data-dir={absolute_path}") 60 | return webdriver.Chrome(options=options) 61 | elif browser == "Edge": 62 | options = EdgeOptions() 63 | if absolute_path is not None: 64 | options.add_argument(f"--user-data-dir={absolute_path}") 65 | return webdriver.Edge(options=options) 66 | elif browser == "Firefox": 67 | options = FirefoxOptions() 68 | if absolute_path is not None: 69 | options.add_argument("-profile") 70 | options.add_argument(str(absolute_path)) 71 | return webdriver.Firefox(options=options) 72 | else: 73 | raise Exception(i18n.t("app.account.browser_not_selected")) 74 | 75 | 76 | def login_driver(url: str, driver: webdriver.Chrome | webdriver.Edge | webdriver.Firefox): 77 | driver.get(url) 78 | parsed_url = urllib.parse.urlparse(driver.current_url) 79 | while not (parsed_url.netloc == "webdgp-gameplayer.games.dmm.com" and parsed_url.path == "/login/success"): 80 | time.sleep(0.2) 81 | parsed_url = urllib.parse.urlparse(driver.current_url) 82 | 83 | code = urllib.parse.parse_qs(parsed_url.query)["code"][0] 84 | return code 85 | -------------------------------------------------------------------------------- /setup.iss: -------------------------------------------------------------------------------- 1 | ; Script generated by the Inno Setup Script Wizard. 2 | ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! 3 | 4 | #define MyAppName "DMMGamePlayerFastLauncher" 5 | #define MyAppVersion "6.3.4" 6 | #define MyAppPublisher "yuki" 7 | #define MyAppURL "https://github.com/fa0311/DMMGamePlayerFastLauncher" 8 | #define MyAppExeName "DMMGamePlayerFastLauncher.exe" 9 | #define MyAppAssocName MyAppName + " File" 10 | #define MyAppAssocExt ".myp" 11 | #define MyAppAssocKey StringChange(MyAppAssocName, " ", "") + MyAppAssocExt 12 | 13 | [Setup] 14 | ; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications. 15 | ; (To generate a new GUID, click Tools | Generate GUID inside the IDE.) 16 | AppId={{58BB9490-BCCC-4EC6-ACF7-B5A4EC8B3755} 17 | AppName={#MyAppName} 18 | AppVersion={#MyAppVersion} 19 | ;AppVerName={#MyAppName} {#MyAppVersion} 20 | AppPublisher={#MyAppPublisher} 21 | AppPublisherURL={#MyAppURL} 22 | AppSupportURL={#MyAppURL} 23 | AppUpdatesURL={#MyAppURL} 24 | DefaultDirName={userappdata}\{#MyAppName} 25 | DisableDirPage=yes 26 | ChangesAssociations=yes 27 | DefaultGroupName={#MyAppName} 28 | DisableProgramGroupPage=yes 29 | LicenseFile=E:\Project\py\DMMGamePlayerFastLauncher\LICENSE 30 | ; Uncomment the following line to run in non administrative install mode (install for current user only.) 31 | PrivilegesRequired=lowest 32 | PrivilegesRequiredOverridesAllowed=dialog 33 | OutputDir=E:\Project\py\DMMGamePlayerFastLauncher\dist 34 | OutputBaseFilename=DMMGamePlayerFastLauncher-Setup 35 | Compression=lzma 36 | SolidCompression=yes 37 | WizardStyle=modern 38 | UninstallFilesDir={userappdata}\{#MyAppName} 39 | 40 | [Languages] 41 | Name: "english"; MessagesFile: "compiler:Default.isl" 42 | Name: "japanese"; MessagesFile: "compiler:Languages\Japanese.isl" 43 | Name: "chinesesimplified"; MessagesFile: "compiler:Languages\ChineseSimplified.isl" 44 | 45 | [Files] 46 | Source: "E:\Project\py\DMMGamePlayerFastLauncher\dist\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion 47 | Source: "E:\Project\py\DMMGamePlayerFastLauncher\windows\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs 48 | ; NOTE: Don't use "Flags: ignoreversion" on any shared system files 49 | 50 | [Registry] 51 | Root: HKA; Subkey: "Software\Classes\{#MyAppAssocExt}\OpenWithProgids"; ValueType: string; ValueName: "{#MyAppAssocKey}"; ValueData: ""; Flags: uninsdeletevalue 52 | Root: HKA; Subkey: "Software\Classes\{#MyAppAssocKey}"; ValueType: string; ValueName: ""; ValueData: "{#MyAppAssocName}"; Flags: uninsdeletekey 53 | Root: HKA; Subkey: "Software\Classes\{#MyAppAssocKey}\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#MyAppExeName},0" 54 | Root: HKA; Subkey: "Software\Classes\{#MyAppAssocKey}\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#MyAppExeName}"" ""%1""" 55 | Root: HKA; Subkey: "Software\Classes\Applications\{#MyAppExeName}\SupportedTypes"; ValueType: string; ValueName: ".myp"; ValueData: "" 56 | 57 | [Icons] 58 | Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; 59 | Name: "{userdesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon 60 | 61 | [Tasks] 62 | Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: checkablealone 63 | 64 | [Run] 65 | Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent -------------------------------------------------------------------------------- /docs/README-advance.md: -------------------------------------------------------------------------------- 1 | # DMMGamePlayerFastLauncher 2 | 3 | ## 詳しい使い方 4 | 5 | [日本語](/README.md) / [English](/README-en.md) 6 | 7 | > [!WARNING] 8 | > うまくインストール出来ない場合は、ウイルス対策ソフトや Windows Defender で検知されている可能性があります。 9 | > 一時的に無効化して再度インストールを試みてください。 10 | > ただし、このツールは強力な権限を利用して外部のソフトウェアを起動するので、セキュリティには十分注意してください。 11 | 12 | > [!NOTE] 13 | > DMMGamePlayer がすでにインストールされている必要があります。 14 | > 15 | 16 | ### 簡単な使用法 17 | 18 | 1. DMMGamePlayerFastLauncher を起動します。 19 | 2. `ショートカット` 内の `ショートカットの作成` を開きます。 20 | 3. `ファイル名` は適当な名前を入力します。 21 | 4. `product_idの選択` は高速起動したいゲームの ID を選択します。 22 | 5. `アカウントの選択` は `常にDMMから抽出する` を選択します。 23 | 6. `UAC自動昇格のショートカットを作成して設定を保存する` をクリックします。 24 | 7. デスクトップに作成されたショートカットをダブルクリックしてゲームを起動します。 25 | 26 | ### 複数アカウントの管理 27 | 28 | #### アカウントをブラウザからインポートをする 29 | 30 | 1. DMMGamePlayerFastLauncher の `アカウント` 内の `ブラウザからインポート` を開きます。 31 | 2. `ファイル名` に任意の名前を付けます。 32 | 3. 好みのブラウザを選択してログインを行いインポートをします。 33 | 34 | ### 動作が不安定な場合 35 | 36 | 動作が不安定な場合は以下の手順をお試しください 37 | 38 | - ウイルス対策ソフトを一時的に無効化する。 39 | - Windows Defender を無効にする。 40 | - ゲームの権限をインストール時の状態に戻す。 41 | - DMMGamePlayer の基本設定をデフォルトに戻す。 42 | - `コンピューター起動時にDMM GAME PLAYERを実行` のチェックを外す。 43 | - `バックグラウンド実行を許可する` のチェックを外す。 44 | 45 | それでも解決しないまたは、再現性の高い不具合については [issues](https://github.com/fa0311/DMMGamePlayerFastLauncher/issues/new/choose) から報告をしてください。 46 | 47 | ### DRM で保護されたゲームを起動するには(デバイス認証が必要なゲームについて) 48 | 49 | 起動時に `Exception: failed to authenticate device` というエラーが出る場合はデバイス認証が必要なゲームです。 50 | 有料ゲームや一部のゲームはデバイス認証が必要です。 51 | 52 | 1. DMMGamePlayerFastLauncher の `アカウント` 内の `デバイスの登録` を開きます。 53 | 2. `ファイルの選択` をクリックし、認証を行なうアカウントを選択します。 54 | 3. `認証コードを送信する` をクリックするとその DMM アカウントに登録をしたメールアドレスに認証コードが届きます。 55 | 4. `デバイス名` と `デバイス認証コード` を入力後に `認証` をクリックでデバイス認証がされます。 56 | 57 | ### デバイス認証の 5 台制限を回避するには 58 | 59 | 1. 回避元のデバイスで DMMGamePlayerFastLauncher を開きます。 60 | 2. `設定` 内の `デバイスの登録` を開いてデバイス情報をメモしてください。 61 | 3. 回避をしたいデバイスで DMMGamePlayerFastLauncher を開きます。 62 | 4. `設定` 内の `デバイスの登録` を開き、メモをしたデバイス情報に変更をしてください。 63 | 64 | ### コマンドラインで実行する 65 | 66 | GUI の動作が不安定で上手くショートカットが作成されない場合や高度な自動化を行いたい場合、コマンドラインを使用して動作させることができます。 67 | 68 | ```ps1 69 | DMMGamePlayerFastLauncher.exe [ID] [--type TYPE] 70 | ``` 71 | 72 | - `ID`: 起動するゲームもしくはアカウントのファイル名。省略すると GUI が起動します。 73 | - `--type TYPE`: `game`, `launcher`, `kill-game`, `force-user-game`から選択します。 74 | 75 | #### 詳細な--type の説明 76 | 77 | `game` を指定するとゲームを起動します。 78 | 79 | `launcher` を指定するとランチャーを起動します。 80 | 81 | `kill-game` を指定するとゲームを起動したあと、直ちに終了します。管理者権限で実行する必要があります。このオプションは主に `force-user-game` の内部で利用されます。 82 | 83 | `force-user-game` を指定するとゲームを強制的にユーザー権限で実行させます。主に Cygames 製のゲームに Steam オーバーレイを表示させる際に利用します。`kill-game` と `game` を連続して動作させたような挙動を行います。 84 | 85 | Steam オーバーレイは管理者権限で実行されているゲームでは表示されません。 86 | Cygames 製のゲームはユーザー権限でも起動することができますが、1 日に 1 回程度、管理者権限で実行させる必要があります。なので `kill-game` を使用して一時的に管理者権限で起動させます。 87 | 88 | 例: 89 | 90 | ```ps1 91 | # DMMGamePlayerFastLauncher\data\shortcut\priconner.json をもとにゲームを起動します。 92 | DMMGamePlayerFastLauncher.exe priconner --type game 93 | # DMMGamePlayerFastLauncher\data\shortcut\account_shortcut\Karyl.json をもとにゲームを起動します。 94 | DMMGamePlayerFastLauncher.exe Karyl --type launcher 95 | ``` 96 | 97 | ### Steam Overlay 98 | 99 | [#コマンドラインで実行する](#コマンドラインで実行する) を参考に Steam Overlay に登録します。 100 | 101 | 権限の自動昇格は行われません。強制的にユーザー権限で実行されます。そのため、起動するかどうかはゲーム次第です。 102 | `game` と指定しても起動しますが Cygames 製のゲームは `force-user-game` と指定すると安定して起動します。 103 | 104 | ![steam](./img/steam.png) 105 | 106 | ```ps1 107 | DMMGamePlayerFastLauncher.exe priconner --type force-user-game 108 | ``` 109 | 110 | ## タスクスケジューラ 111 | 112 | UAC 自動昇格はタスクスケジューラを使用して行われるので安全です。 113 | `\Microsoft\Windows\DMMGamePlayerFastLauncher` に存在します。 114 | 115 | また、`DMMGamePlayerFastLauncher\data\schtasks` にタスクスケジューラのコピーがあります。 116 | コピーをタスクスケジューラに反映させるには `DMMGamePlayerFastLauncher\tools\refresh.ps1` を実行する必要があります。 117 | 118 | タスクスケジューラを手動でいじるのはオススメしていません。 119 | 120 | ## 技術的な話 121 | 122 | ### デバイス認証(DRM) 123 | 124 | 割れ防止のために DMMGamePlayer はデバイス認証を行っています。 125 | デバイス認証でアカウントとハードウェアの情報を紐つけています。 126 | ハードウェアの情報はリクエストを送信するたびに送信されています。 127 | 128 | DMMGamePlayer が収集するハードウェアの情報は以下の通りです。 129 | 130 | - MAC アドレス 131 | - HDD のシリアル番号 132 | - マザーボードのシリアル番号 133 | 134 | DMMGamePlayerFastLauncher はそれらを偽装して DMM に送信します。 135 | よってログに出力される MAC アドレスなどの情報はすべて偽装されたものです。 136 | 137 | DMMGamePlayer での認証が通ると `~/.DMMGamePlayer/{product_idのbase64エンコード}` に認証情報が保存されます。 138 | ゲームはそれを読み取って認証情報が誤っていないかを確認します。 139 | -------------------------------------------------------------------------------- /DMMGamePlayerFastLauncher/lib/toast.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import traceback 3 | import webbrowser 4 | from tkinter import Misc, Tk, Toplevel 5 | from typing import Callable, Union 6 | 7 | import customtkinter as ctk 8 | import i18n 9 | from customtkinter import CTkBaseClass, CTkButton, CTkFrame, CTkLabel, CTkTextbox, CTkToplevel 10 | from static.config import UrlConfig 11 | from utils.utils import get_isinstance 12 | 13 | 14 | def error_toast(func): 15 | def _wrapper(self, *arg, **kwargs): 16 | try: 17 | assert isinstance(self.toast, ToastController) 18 | return func(self, *arg, **kwargs) 19 | except Exception as e: 20 | self.toast.error(str(e)) 21 | logging.error(traceback.format_exc()) 22 | raise 23 | 24 | return _wrapper 25 | 26 | 27 | class ToastController(CTkFrame): 28 | master: Union[Tk, Toplevel] 29 | instance: "ToastController" 30 | toast_list: list["CTkBaseClass"] 31 | 32 | def __init__(self, master: Misc) -> None: 33 | self.master = master.winfo_toplevel() 34 | instance = get_isinstance(self.master.winfo_children(), ToastController) 35 | self.toast_list = [] 36 | if instance: 37 | self.instance = instance 38 | else: 39 | super().__init__(self.master) 40 | self.place() 41 | self.instance = self 42 | 43 | def info(self, text: str): 44 | widget = InfoLabel(self.instance.master, text=text).create() 45 | self.instance.show(widget) 46 | 47 | def command_info(self, text: str, command: Callable): 48 | widget = CommandInfoLabel(self.instance.master, text=text, command=command).create() 49 | self.instance.show(widget) 50 | 51 | def error(self, text: str): 52 | widget = ErrorLabel(self.instance.master, text=text).create() 53 | self.instance.show(widget) 54 | 55 | def show(self, widget: "CTkBaseClass"): 56 | self.toast_list.append(widget) 57 | self.update_state() 58 | self.after(8000, self.hide) 59 | 60 | def update_state(self): 61 | for key, x in enumerate(reversed(self.toast_list)): 62 | x.place(x=-18, y=-28 * key - 10, relx=1, rely=1, anchor=ctk.SE) 63 | 64 | def hide(self): 65 | widget = self.toast_list.pop(0) 66 | widget.destroy() 67 | self.update_state() 68 | 69 | 70 | class InfoLabel(CTkFrame): 71 | text: str 72 | 73 | def __init__(self, master: Union[Tk, Toplevel], text: str) -> None: 74 | super().__init__(master, corner_radius=10) 75 | self.text = text 76 | 77 | def create(self): 78 | CTkLabel(self, text=self.text).pack(side=ctk.LEFT, padx=10) 79 | return self 80 | 81 | 82 | class CommandInfoLabel(CTkFrame): 83 | text: str 84 | command: Callable 85 | 86 | def __init__(self, master: Union[Tk, Toplevel], text: str, command: Callable) -> None: 87 | super().__init__(master, corner_radius=10) 88 | self.text = text 89 | self.command = command 90 | 91 | def create(self): 92 | CTkLabel(self, text=self.text).pack(side=ctk.LEFT, padx=10) 93 | btn = CTkButton(self, text=i18n.t("app.toast.details"), command=self.command, width=0, height=0) 94 | btn.pack(side=ctk.LEFT, padx=10) 95 | return self 96 | 97 | 98 | class ErrorLabel(CTkFrame): 99 | text: str 100 | trace: str 101 | 102 | def __init__(self, master: Union[Tk, Toplevel], text: str) -> None: 103 | super().__init__(master, fg_color="red", corner_radius=10) 104 | self.text = text 105 | self.trace = traceback.format_exc() 106 | 107 | def create(self): 108 | CTkLabel(self, text=self.text).pack(side=ctk.LEFT, padx=10) 109 | btn = CTkButton(self, text=i18n.t("app.toast.details"), command=self.copy, width=0, height=0, fg_color="#ffaaaa", text_color="black", hover_color="white") 110 | btn.pack(side=ctk.LEFT, padx=10) 111 | return self 112 | 113 | def copy(self): 114 | ErrorWindow(self.master, self.text, self.trace).create() 115 | 116 | 117 | class ErrorWindow(CTkToplevel): 118 | text: str 119 | trace: str 120 | 121 | def __init__(self, master, text: str, trace: str, quit: bool = False): 122 | super().__init__(master) 123 | self.text = text 124 | self.trace = trace 125 | self.geometry("600x300") 126 | self.deiconify() 127 | self.lift() 128 | self.focus_force() 129 | 130 | if quit: 131 | self.protocol("WM_DELETE_WINDOW", self.quit) 132 | 133 | def create(self): 134 | ErrorFrame(self, self.text, self.trace).create().pack(fill=ctk.BOTH, padx=10, pady=10, expand=True) 135 | return self 136 | 137 | 138 | class ErrorFrame(CTkFrame): 139 | text: str 140 | trace: str 141 | 142 | def __init__(self, master, text: str, trace: str): 143 | super().__init__(master, fg_color="transparent") 144 | self.text = text 145 | self.trace = trace 146 | 147 | def create(self): 148 | CTkLabel(self, text=self.text).pack(pady=10) 149 | 150 | box = CTkTextbox(self, height=30) 151 | box.pack(fill=ctk.BOTH, padx=10, pady=(0, 10), expand=True) 152 | box.insert("0.0", self.trace) 153 | 154 | frame = CTkFrame(self, fg_color="transparent") 155 | frame.pack(fill=ctk.BOTH, padx=10, pady=(0, 10)) 156 | 157 | CTkButton(frame, text=i18n.t("app.toast.copy_to_clipboard"), command=lambda: self.clipboard(box)).pack(side=ctk.LEFT, expand=True) 158 | 159 | CTkButton(frame, text=i18n.t("app.toast.report"), command=lambda: self.report()).pack(side=ctk.LEFT, expand=True) 160 | return self 161 | 162 | def clipboard(self, box: CTkTextbox): 163 | self.clipboard_clear() 164 | self.clipboard_append(box.get("0.0", "end")) 165 | self.update() 166 | 167 | def report(self): 168 | webbrowser.open(UrlConfig.ISSUE) 169 | -------------------------------------------------------------------------------- /DMMGamePlayerFastLauncher/DMMGamePlayerFastLauncher.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import logging 3 | import os 4 | import sys 5 | import time 6 | from tkinter import font 7 | 8 | import customtkinter as ctk 9 | import i18n 10 | from app import App 11 | from coloredlogs import ColoredFormatter 12 | from component.logger import LoggingHandlerMask, StyleScheme, TkinkerLogger 13 | from customtkinter import ThemeManager 14 | from launch import GameLauncher, GameLauncherUac, LanchLauncher 15 | from lib.DGPSessionV2 import DgpSessionV2 16 | from models.setting_data import AppConfig 17 | from static.config import AssetsPathConfig, DataPathConfig, SchtasksConfig, UrlConfig 18 | from static.env import Env 19 | from static.loder import config_loder, config_migrate 20 | from tkinter_colored_logging_handlers import LoggingHandler 21 | 22 | 23 | def loder(master: LanchLauncher): 24 | DataPathConfig.ACCOUNT.mkdir(exist_ok=True, parents=True) 25 | DataPathConfig.ACCOUNT_SHORTCUT.mkdir(exist_ok=True, parents=True) 26 | DataPathConfig.SHORTCUT.mkdir(exist_ok=True, parents=True) 27 | DataPathConfig.SCHTASKS.mkdir(exist_ok=True, parents=True) 28 | DataPathConfig.BROWSER_PROFILE.mkdir(exist_ok=True, parents=True) 29 | DataPathConfig.BROWSER_CONFIG.mkdir(exist_ok=True, parents=True) 30 | 31 | config_loder() 32 | i18n.load_path.append(str(AssetsPathConfig.I18N)) 33 | i18n.set("locale", AppConfig.DATA.lang.get()) 34 | 35 | handlers = [] 36 | 37 | if AppConfig.DATA.output_logfile.get() and not any([isinstance(x, logging.FileHandler) for x in logging.getLogger().handlers]): 38 | DataPathConfig.LOG.mkdir(exist_ok=True, parents=True) 39 | handler = logging.FileHandler(DataPathConfig.LOG.joinpath(f"{time.strftime('%Y%m%d%H%M%S')}.log"), encoding="utf-8") 40 | handlers.append(handler) 41 | 42 | if AppConfig.DATA.debug_window.get() and not any([isinstance(x, LoggingHandler) for x in logging.getLogger().handlers]): 43 | handle = LoggingHandlerMask if AppConfig.DATA.mask_token.get() else LoggingHandler 44 | handler = handle(TkinkerLogger(master).create().box, scheme=StyleScheme) 45 | handler.setFormatter(ColoredFormatter("[%(levelname)s] [%(asctime)s] %(message)s")) 46 | handlers.append(handler) 47 | 48 | if not any([isinstance(x, logging.StreamHandler) for x in logging.getLogger().handlers]): 49 | handler = logging.StreamHandler() 50 | handler.setFormatter(ColoredFormatter("[%(levelname)s] [%(asctime)s] %(message)s")) 51 | handlers.append(handler) 52 | 53 | logging.basicConfig(level=logging.DEBUG, handlers=handlers) 54 | 55 | logging.debug("==================================================") 56 | logging.debug("===== DMMGamePlayerFastLauncher Environment =====") 57 | logging.debug("==================================================") 58 | logging.debug(Env.dump()) 59 | logging.debug(AppConfig.DATA.to_dict()) 60 | logging.debug(AppConfig.DEVICE.to_dict()) 61 | logging.debug(DataPathConfig.dump()) 62 | logging.debug(AssetsPathConfig.dump()) 63 | logging.debug(UrlConfig.dump()) 64 | logging.debug(SchtasksConfig.dump()) 65 | logging.debug(sys.argv) 66 | logging.debug("==================================================") 67 | logging.debug("==================================================") 68 | logging.debug("==================================================") 69 | 70 | config_migrate() 71 | 72 | if AppConfig.DATA.proxy_all.get() != "": 73 | os.environ["ALL_PROXY"] = AppConfig.DATA.proxy_all.get() 74 | if AppConfig.DATA.dmm_proxy_all.get() != "": 75 | DgpSessionV2.PROXY["http"] = AppConfig.DATA.dmm_proxy_all.get() 76 | DgpSessionV2.PROXY["https"] = AppConfig.DATA.dmm_proxy_all.get() 77 | 78 | ctk.set_default_color_theme(str(AssetsPathConfig.THEMES.joinpath(AppConfig.DATA.theme.get()).with_suffix(".json"))) 79 | 80 | additional_theme = { 81 | "MenuComponent": {"text_color": ["#000000", "#ffffff"]}, 82 | "LabelComponent": {"fg_color": ["#F9F9FA", "#343638"], "required_color": ["red", "red"]}, 83 | "CheckBoxComponent": {"checkbox_width": 16, "checkbox_height": 16, "border_width": 2}, 84 | } 85 | for key, value in additional_theme.items(): 86 | ThemeManager.theme[key] = value 87 | 88 | if AppConfig.DATA.theme_font.get() == "i18n": 89 | i18n_font = i18n.t("app.font.main") 90 | if i18n_font not in font.families(): 91 | logging.warning(f"Font {i18n_font} not found") 92 | ThemeManager.theme["CTkFont"]["family"] = i18n_font 93 | elif AppConfig.DATA.theme_font.get() == "os": 94 | os_default_font = font.nametofont("TkDefaultFont").config() 95 | if os_default_font is None: 96 | logging.warning(f"Font {os_default_font} not found") 97 | else: 98 | ThemeManager.theme["CTkFont"]["family"] = os_default_font["family"] 99 | 100 | ctk.set_appearance_mode(AppConfig.DATA.appearance_mode.get()) 101 | 102 | try: 103 | ctk.set_widget_scaling(AppConfig.DATA.window_scaling.get()) 104 | except Exception: 105 | pass 106 | 107 | 108 | argpar = argparse.ArgumentParser( 109 | prog="DMMGamePlayerFastLauncher", 110 | usage="https://github.com/fa0311/DMMGamePlayerFastLauncher", 111 | description="DMM Game Player Fast Launcher", 112 | ) 113 | argpar.add_argument("id", default=None, nargs="?") 114 | argpar.add_argument("--type", default="game") 115 | 116 | 117 | try: 118 | arg = argpar.parse_args() 119 | id = arg.id 120 | type = arg.type 121 | except Exception: 122 | exit(0) 123 | 124 | if id is None: 125 | App(loder).create().mainloop() 126 | 127 | elif type == "launcher": 128 | lanch = LanchLauncher(loder).create() 129 | lanch.thread(id) 130 | lanch.mainloop() 131 | 132 | elif type == "game": 133 | lanch = GameLauncher(loder).create() 134 | lanch.thread(id) 135 | lanch.mainloop() 136 | 137 | elif type == "force-user-game": 138 | GameLauncherUac.wait([id, "--type", "kill-game"]) 139 | lanch = GameLauncher(loder).create() 140 | lanch.thread(id, force_non_uac=True) 141 | lanch.mainloop() 142 | 143 | elif type == "kill-game": 144 | lanch = GameLauncher(loder).create() 145 | lanch.thread(id, kill=True) 146 | lanch.mainloop() 147 | 148 | else: 149 | raise Exception("type error") 150 | -------------------------------------------------------------------------------- /DMMGamePlayerFastLauncher/lib/process_manager.py: -------------------------------------------------------------------------------- 1 | import ctypes 2 | import logging 3 | import os 4 | import subprocess 5 | import sys 6 | from pathlib import Path 7 | from typing import Optional 8 | 9 | import psutil 10 | import win32security 11 | from static.config import AssetsPathConfig, DataPathConfig, SchtasksConfig 12 | from static.env import Env 13 | 14 | 15 | class ProcessManager: 16 | @staticmethod 17 | def admin_run(args: list[str], cwd: Optional[str] = None) -> int: 18 | file = args.pop(0) 19 | logging.info({"cwd": cwd, "args": args, "file": file}) 20 | return ctypes.windll.shell32.ShellExecuteW(None, "runas", str(file), " ".join([f"{arg}" for arg in args]), cwd, 1) 21 | 22 | @staticmethod 23 | def admin_check() -> bool: 24 | try: 25 | return ctypes.windll.shell32.IsUserAnAdmin() 26 | except Exception: 27 | return False 28 | 29 | @staticmethod 30 | def run(args: list[str], cwd: Optional[str] = None) -> subprocess.Popen: 31 | logging.info({"cwd": cwd, "args": args}) 32 | return subprocess.Popen(args, cwd=cwd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 33 | 34 | @staticmethod 35 | def run_ps(args: str) -> int: 36 | logging.info(args) 37 | args = args.replace('"', '\\"').replace("\n", "").replace("\r", "") 38 | text = f'powershell -Command "{args}"' 39 | return subprocess.call(text, shell=True) 40 | 41 | @staticmethod 42 | def search_process(name: str) -> psutil.Process: 43 | for process in psutil.process_iter(): 44 | if process.name() == name: 45 | return process 46 | raise Exception(f"Process not found: {name}") 47 | 48 | 49 | class ProcessIdManager: 50 | process: list[tuple[int, Optional[str]]] 51 | 52 | def __init__(self, _process: Optional[list[tuple[int, Optional[str]]]] = None) -> None: 53 | def wrapper(x: psutil.Process) -> Optional[str]: 54 | try: 55 | return x.exe() 56 | except Exception: 57 | return None 58 | 59 | if _process is None: 60 | self.process = [(x.pid, wrapper(x)) for x in psutil.process_iter()] 61 | else: 62 | self.process = _process 63 | 64 | def __sub__(self, other: "ProcessIdManager") -> "ProcessIdManager": 65 | process = [x for x in self.process if x not in other.process] 66 | return ProcessIdManager(process) 67 | 68 | def __add__(self, other: "ProcessIdManager") -> "ProcessIdManager": 69 | process = list(set(self.process + other.process)) 70 | return ProcessIdManager(process) 71 | 72 | def __repr__(self) -> str: 73 | return "\n".join([f"{x[0]}: {x[1]}" for x in self.process]) + "\n" 74 | 75 | def new_process(self) -> "ProcessIdManager": 76 | return ProcessIdManager() - self 77 | 78 | def search(self, name: str) -> int: 79 | process = [x[0] for x in self.process if x[1] == name] 80 | if len(process) == 0: 81 | raise Exception(f"Process not found: {name}") 82 | return process[0] 83 | 84 | def search_or_none(self, name: str) -> Optional[int]: 85 | process = [x[0] for x in self.process if x[1] == name] 86 | if len(process) != 1: 87 | return None 88 | return process[0] 89 | 90 | 91 | def get_sid() -> str: 92 | username = os.getlogin() 93 | sid, domain, type = win32security.LookupAccountName("", username) 94 | sidstr = win32security.ConvertSidToStringSid(sid) 95 | return sidstr 96 | 97 | 98 | class Schtasks: 99 | file: str 100 | name: str 101 | 102 | def __init__(self, args: str) -> None: 103 | self.file = SchtasksConfig.FILE.format(os.getlogin(), args) 104 | self.name = SchtasksConfig.NAME.format(self.file) 105 | self.args = args 106 | 107 | def check(self) -> bool: 108 | xml_path = DataPathConfig.SCHTASKS.joinpath(self.file).with_suffix(".xml") 109 | return not xml_path.exists() 110 | 111 | def set(self) -> None: 112 | with open(AssetsPathConfig.SCHTASKS, "r", encoding="utf-8") as f: 113 | template = f.read() 114 | 115 | if Env.DEVELOP: 116 | command = Path(sys.executable) 117 | args = [str(Path(sys.argv[0]).absolute()), self.args, "--type", "game"] 118 | else: 119 | command = Path(sys.argv[0]) 120 | args = [self.args, "--type", "game"] 121 | 122 | template = template.replace(r"{{UID}}", self.file) 123 | template = template.replace(r"{{SID}}", get_sid()) 124 | template = template.replace(r"{{COMMAND}}", str(command.absolute())) 125 | template = template.replace(r"{{ARGUMENTS}}", " ".join(f"{x}" for x in args)) 126 | template = template.replace(r"{{WORKING_DIRECTORY}}", os.getcwd()) 127 | 128 | xml_path = DataPathConfig.SCHTASKS.joinpath(self.file).with_suffix(".xml") 129 | with open(xml_path, "w", encoding="utf-8") as f: 130 | f.write(template) 131 | create_args = [Env.SCHTASKS, "/create", "/xml", str(xml_path.absolute()), "/tn", self.name] 132 | 133 | ProcessManager.admin_run(create_args) 134 | 135 | def delete(self) -> None: 136 | delete_args = [Env.SCHTASKS, "/delete", "/tn", self.name, "/f"] 137 | ProcessManager.admin_run(delete_args) 138 | 139 | 140 | class Shortcut: 141 | def create(self, source: Path, target: Optional[Path] = None, args: Optional[list[str]] = None, icon: Optional[Path] = None): 142 | with open(AssetsPathConfig.SHORTCUT, "r", encoding="utf-8") as f: 143 | template = f.read() 144 | if icon is None: 145 | icon = Path(sys.argv[0]) 146 | if args is None: 147 | args = [] 148 | 149 | if target is None: 150 | if Env.DEVELOP: 151 | target = Path(sys.executable) 152 | args.insert(0, str(Path(sys.argv[0]).absolute())) 153 | else: 154 | target = Path(sys.argv[0]) 155 | 156 | template = template.replace(r"{{SOURCE}}", str(source.absolute())) 157 | template = template.replace(r"{{TARGET}}", str(target)) 158 | template = template.replace(r"{{WORKING_DIRECTORY}}", os.getcwd()) 159 | template = template.replace(r"{{ICON_LOCATION}}", str(icon.absolute())) 160 | template = template.replace(r"{{ARGUMENTS}}", " ".join(f"{x}" for x in args)) 161 | 162 | ProcessManager.run_ps(template) 163 | -------------------------------------------------------------------------------- /assets/i18n/app.zh_CN.yml: -------------------------------------------------------------------------------- 1 | zh_CN: 2 | title: DMMGamePlayer Fast Launcher 3 | language: 简体中文 4 | 5 | font: 6 | main: "Microsoft YaHei UI" 7 | 8 | tab: 9 | home: 主页 10 | shortcut: 快捷方式 11 | account: 帐户 12 | setting: 设置 13 | other: 其他 14 | help: 帮助 15 | create: 创建快捷方式 16 | edit: 编辑快捷方式 17 | launch_create: 快速启动 GamePlayer 18 | launch_edit: 编辑快速启动 GamePlayer 19 | account_import: 导入 20 | account_edit: 编辑账户 21 | import_browser: 从浏览器导入 22 | device: 注册设备 23 | device_list: 设备列表 24 | 25 | home: 26 | new_version: DMMGamePlayer Fast Launcher有新版本可用。 27 | 28 | shortcut: 29 | filename: 文件名 30 | filename_tooltip: |- 31 | 请给这个快捷方式任意名称。 32 | 使用半角英文数字以外的字符可能会导致问题。 33 | 34 | product_id: 选择 product_id 35 | product_id_tooltip: |- 36 | product_id 是 DMM 用于识别游戏的 ID。 37 | account_create_detail: |- 38 | 创建一个用于快速启动 DMMGamePlayer 的快捷方式。 39 | 请在“帐户”选项卡中选择您创建的帐户。 40 | 41 | account_path: 选择帐户 42 | account_path_tooltip: |- 43 | 请在“帐户”选项卡中选择您创建的帐户。 44 | always_extract_from_dmm: 始终从DMM提取 45 | game_args: 游戏参数 46 | game_args_tooltip: |- 47 | 指定传递给游戏的参数。 48 | 对于基于 Unity 的游戏,您可以输入类似 '-screen-fullscreen 0 -screen-width 1280 -screen-height 720' 的参数,以窗口模式启动游戏,分辨率为 1280x720。 49 | external_tool_path: 外部工具路径 50 | external_tool_path_tooltip: 如果在启动游戏时需要使用外部工具,请在此指定路径。 51 | auto_update: 启动时自动更新游戏 52 | rich_presence: 启用 Discord 的丰富存在 53 | 54 | create_bypass_shortcut_and_save: 创建UAC自动提升的快捷方式并保存设置 55 | create_bypass_shortcut_and_save_tooltip: | 56 | 创建UAC自动提升的快捷方式并保存设置。 57 | 如果游戏运行需要管理员权限,请选择此选项。 58 | 59 | create_uac_shortcut_and_save: 创建UAC手动提升的快捷方式并保存设置 60 | create_uac_shortcut_and_save_tooltip: |- 61 | 创建并保存UAC手动提升的快捷方式。 62 | 如果游戏运行需要管理员权限,请选择此选项。 63 | 由于每次启动游戏都会出现UAC对话框,因此需要手动提升权限。 64 | 它在同一进程中执行,与其他软件的集成性更好。 65 | 66 | create_shortcut_and_save: 创建快捷方式并保存设置 67 | create_shortcut_and_save_tooltip: | 68 | 创建快捷方式并保存设置。 69 | 如果游戏运行不需要管理员权限,请选择此选项。 70 | 对于需要管理员权限的游戏选择此选项将导致错误。 71 | 72 | save_only: 仅保存设置 73 | save_only_tooltip: | 74 | 仅保存设置而不创建快捷方式。 75 | 要从快捷方式启动保存的设置,请执行命令行: 76 | `DMMGamePlayerFastLauncher.exe [文件名] --type game`。 77 | 78 | add_detail: 创建快速启动的快捷方式和设置。 79 | edit_detail: 编辑快速启动快捷方式的设置。 80 | 81 | file_select: 选择文件 82 | 83 | product_id_not_entered: 未输入 product_id。 84 | filename_not_entered: 未输入文件名。 85 | account_path_not_entered: 未选择账户。 86 | game_info_error: 无法检索游戏信息。 87 | administrator_error: 需要管理员权限。请创建“UAC 自动提升的快捷方式”。 88 | file_not_selected: 未选择文件。 89 | save_success: 已成功保存。 90 | delete: 删除 91 | 92 | account_edit_detail: 编辑快速启动 DMMGamePlayer 的设置。 93 | dgp_args: DMMGamePlayer 参数 94 | dgp_args_tooltip: |- 95 | 指定传递给 DMMGamePlayer 的参数。 96 | 例如,输入 'dmmgameplayer://play/GCL/priconner/cl/win' 将启动 '公主连结!Re:Dive' 在 DMMGamePlayer 中。留空以仅启动 DMMGamePlayer。 97 | unity_command_line_args: 了解更多关于Unity命令行参数 98 | unity_command_line_args_tooltip: |- 99 | 打开关于 Unity 命令行参数的站点。 100 | 如果您启动的游戏是使用 Unity 制作的,这可能会有所帮助。 101 | unity_command_line_args_link: https://docs.unity3d.com/cn/current/Manual/PlayerCommandLineArguments.html 102 | 103 | account: 104 | import_detail: |- 105 | 将您的 DMMGamePlayer 帐户信息导入 DMMGamePlayerFastLauncher。 106 | 此操作已被弃用。建议从浏览器导入。 107 | 108 | filename_tooltip: |- 109 | 请为此帐户提供任何名称。 110 | 非英数字字符的使用可能导致问题。 111 | import: 导入 112 | filename: 文件名 113 | filename_not_entered: 未输入文件名。 114 | filename_already_exists: 该文件名已存在。 115 | filename_reserved: 文件名已被保留。 116 | import_error: 导入失败。 117 | import_success: 导入成功。 118 | 119 | file_select: 选择文件 120 | 121 | import_browser_detail: 启动浏览器以从DMMGamePlayer导入帐户信息。 122 | browser_select: 选择浏览器 123 | browser_select_tooltip: |- 124 | 选择您喜欢的浏览器。 125 | 系统将启动指定的浏览器并打开登录页面。 126 | auto_refresh: 自动刷新登录会话 127 | browser_not_selected: 未选择浏览器。 128 | import_browser: 导入 129 | import_browser_success: 导入成功。 130 | 131 | edit_detail: |- 132 | 编辑您的帐户信息。 133 | 这是一项高级操作。 134 | 135 | save: 保存 136 | delete: 删除 137 | save_success: 已成功保存。 138 | 139 | device_detail: |- 140 | 输入发送到您电子邮件地址的“设备认证码”。 141 | 142 | send_auth_code: 发送认证码 143 | auth: 认证 144 | 145 | hardware_name: 设备名称 146 | auth_code: 设备认证码 147 | auth_code_tooltip: |- 148 | 输入您电子邮件中提供的字母数字代码。 149 | send_auth_code_success: 已成功发送认证码。 150 | auth_success: 认证成功。 151 | device_list_success: 成功检索设备列表。 152 | delete_success: 已成功删除。 153 | 154 | device_registrations: "已注册设备数: %{count} / %{limit}" 155 | 156 | setting: 157 | save: 保存 158 | reset_all_settings: 重置所有设置 159 | confirm_reset: 您确定要重置所有设置吗? 160 | save_success: 设置已成功保存。 161 | dmm_game_player_program_folder: DMMGamePlayer 程序文件夹 162 | dmm_game_player_data_folder: DMMGamePlayer 数据文件夹 163 | lang: 语言 164 | theme: 主题 165 | appearance: 外观 166 | 167 | font_preset: 字体预设 168 | font_preset_tooltip: |- 169 | 选择字体预设。 170 | i18n: 将选择针对每种语言优化的字体。 171 | os: 将选择操作系统的默认字体。 172 | theme: 将选择与主题匹配的字体。 173 | 174 | proxy_all: 代理 175 | proxy_all_tooltip: |- 176 | 设置游戏的代理。 177 | 例子:http://127.0.0.1:80 178 | https://127.0.0.1:443 179 | socks5://127.0.0.1:1080 180 | socks5://user:pass@127.0.0.1:1080 181 | 182 | dmm_proxy_all: DMM 代理 183 | dmm_proxy_all_tooltip: |- 184 | 设置 DMM 的代理。 185 | 例子:http://127.0.0.1:80 186 | https://127.0.0.1:443 187 | socks5://127.0.0.1:1080 188 | socks5://user:pass@127.0.0.1:1080 189 | 190 | window_scaling: 窗口缩放比例 191 | debug_window: 显示调试窗口 192 | output_logfile: 以文件形式记录日志 193 | mask_token: 隐藏日志上的令牌 194 | 195 | device_detail: |- 196 | 配置用于设备认证的设备信息。 197 | 这些值不需要精确。 198 | 199 | mac_address: MAC 地址 200 | hdd_serial: 硬盘序列号 201 | motherboard: 主板 ID 202 | user_os: 操作系统 203 | 204 | other_detail: 使用文本编辑器编辑工具设置。 205 | open_save_folder: 打开设置文件夹 206 | 207 | help: 208 | coop_in_develop: 参与开发 209 | donations_to_developer: 向开发者捐款 210 | bug_report: 报告问题 211 | 212 | launch: 213 | export_error: 导出失败。 214 | import_error: 导入失败。 215 | 216 | admin_error: 需要管理员权限。 217 | 218 | component: 219 | required: 此字段为必填项目。 220 | required_symbol: "*" 221 | reference: 参考 222 | 223 | "yes": 是 224 | "no": 否 225 | 226 | download: 正在下载... 227 | 228 | toast: 229 | report: 报告 230 | copy_to_clipboard: "复制到剪贴板" 231 | details: 详情 232 | 233 | lib: 234 | dmm_already_running: DMMGamePlayer 已经在运行,因此无法启动。 235 | 236 | utils: 237 | file_exists: 该文件已经存在。 238 | -------------------------------------------------------------------------------- /assets/i18n/app.zh_TW.yml: -------------------------------------------------------------------------------- 1 | zh_TW: 2 | title: DMMGamePlayer Fast Launcher 3 | language: 繁體中文 4 | 5 | font: 6 | main: "Microsoft JhengHei UI" 7 | 8 | tab: 9 | home: 首頁 10 | shortcut: 快捷方式 11 | account: 帳戶 12 | setting: 設定 13 | other: 其他 14 | help: 幫助 15 | create: 創建快捷方式 16 | edit: 編輯快捷方式 17 | launch_create: 快速啟動 GamePlayer 18 | launch_edit: 編輯快速啟動 GamePlayer 19 | account_import: 匯入 20 | account_edit: 編輯帳號 21 | import_browser: 從瀏覽器匯入 22 | device: 註冊設備 23 | device_list: 設備清單 24 | 25 | home: 26 | new_version: DMMGamePlayer Fast Launcher有新版本可用。 27 | 28 | shortcut: 29 | filename: 檔案名稱 30 | filename_tooltip: |- 31 | 請為此快捷方式提供任何名稱。 32 | 使用半形英數字以外的字元可能會導致問題。 33 | 34 | product_id: 選擇 product_id 35 | product_id_tooltip: |- 36 | product_id 是 DMM 用來識別遊戲的 ID。 37 | 38 | account_create_detail: |- 39 | 創建一個用於快速啟動 DMMGamePlayer 的快捷方式。 40 | 請在「帳戶」選項卡中選擇您創建的帳戶。 41 | 42 | account_path: 選擇帳戶 43 | account_path_tooltip: |- 44 | 請在「帳戶」選項卡中選擇您創建的帳戶。 45 | always_extract_from_dmm: 總是從DMM提取 46 | game_args: 遊戲引數 47 | game_args_tooltip: |- 48 | 指定要傳遞給遊戲的引數。 49 | 對於基於 Unity 的遊戲,您可以輸入像 '-screen-fullscreen 0 -screen-width 1280 -screen-height 720' 這樣的引數,以以 1280x720 的解析度以視窗模式啟動遊戲。 50 | external_tool_path: 外部工具路徑 51 | external_tool_path_tooltip: 如果在啟動遊戲時需要使用外部工具,請在此指定路徑。 52 | auto_update: 啟動時自動更新遊戲 53 | rich_presence: 啟用 Discord 的豐富存在 54 | 55 | create_bypass_shortcut_and_save: 創建UAC自動提升快捷方式並保存設置 56 | create_bypass_shortcut_and_save_tooltip: | 57 | 創建UAC自動提升的快捷方式並保存設置。 58 | 如果遊戲運行需要管理員權限,請選擇此選項。 59 | 60 | create_uac_shortcut_and_save: 創建UAC手動提升快捷方式並保存設置 61 | create_uac_shortcut_and_save_tooltip: |- 62 | 建立並保存UAC手動提升的快捷方式。 63 | 如果遊戲運行需要管理員權限,請選擇此選項。 64 | 由於遊戲啟動時會顯示UAC對話框,因此需要手動提升權限。 65 | 它在同一進程中執行,與其他軟件的整合性更好。 66 | 67 | create_shortcut_and_save: 創建快捷方式並保存設置 68 | create_shortcut_and_save_tooltip: | 69 | 創建快捷方式並保存設置。 70 | 如果遊戲運行不需要管理員權限,請選擇此選項。 71 | 對於需要管理員權限的遊戲選擇此選項將導致錯誤。 72 | 73 | save_only: 僅保存設置 74 | save_only_tooltip: | 75 | 僅保存設置而不創建快捷方式。 76 | 若要從快捷方式啟動保存的設置,請執行命令行: 77 | `DMMGamePlayerFastLauncher.exe [檔名] --type game`。 78 | 79 | add_detail: 創建快速啟動的快捷方式和設置。 80 | edit_detail: 編輯快速啟動快捷方式的設置。 81 | 82 | file_select: 選擇檔案 83 | 84 | product_id_not_entered: 未輸入 product_id。 85 | filename_not_entered: 未輸入檔案名稱。 86 | account_path_not_entered: 未選擇帳戶。 87 | game_info_error: 無法檢索遊戲資訊。 88 | administrator_error: 需要管理員權限。請創建「UAC 自動提升的快捷方式」。 89 | file_not_selected: 未選擇檔案。 90 | save_success: 已成功保存。 91 | delete: 刪除 92 | 93 | account_edit_detail: 編輯快速啟動 DMMGamePlayer 的設置。 94 | dgp_args: DMMGamePlayer 引數 95 | dgp_args_tooltip: |- 96 | 指定要傳遞給 DMMGamePlayer 的引數。 97 | 例如,輸入 'dmmgameplayer://play/GCL/priconner/cl/win' 將啟動 '公主連結!Re:Dive' 在 DMMGamePlayer 中。留空以僅啟動 DMMGamePlayer。 98 | unity_command_line_args: 了解更多關於Unity命令列參數 99 | unity_command_line_args_tooltip: |- 100 | 開啟有關 Unity 命令列參數的網站。 101 | 如果您啟動的遊戲是使用 Unity 製作的,這可能會有所幫助。 102 | unity_command_line_args_link: https://docs.unity3d.com/cn/current/Manual/PlayerCommandLineArguments.html 103 | 104 | account: 105 | import_detail: |- 106 | 將您的 DMMGamePlayer 帳戶資訊匯入 DMMGamePlayerFastLauncher。 107 | 此操作已被棄用。建議從瀏覽器匯入。 108 | 109 | filename_tooltip: |- 110 | 請為此帳戶提供任何名稱。 111 | 非英數字元的使用可能導致問題。 112 | import: 匯入 113 | filename: 檔案名稱 114 | filename_not_entered: 未輸入檔案名稱。 115 | filename_already_exists: 該檔案名稱已存在。 116 | filename_reserved: 檔案名稱已被保留。 117 | import_error: 匯入失敗。 118 | import_success: 匯入成功。 119 | 120 | file_select: 選擇檔案 121 | 122 | import_browser_detail: 啟動瀏覽器以從DMMGamePlayer匯入帳號資訊。 123 | browser_select: 選擇瀏覽器 124 | browser_select_tooltip: |- 125 | 選擇您偏好的瀏覽器。 126 | 系統將啟動指定的瀏覽器並開啟登入畫面。 127 | auto_refresh: 自動刷新登入會話 128 | browser_not_selected: 未選擇瀏覽器。 129 | import_browser: 匯入 130 | import_browser_success: 匯入成功。 131 | 132 | edit_detail: |- 133 | 編輯您的帳戶資訊。 134 | 這是一項高級操作。 135 | 136 | save: 保存 137 | delete: 刪除 138 | save_success: 已成功保存。 139 | 140 | device_detail: |- 141 | 輸入發送到您電子郵件地址的「設備認證碼」。 142 | 143 | send_auth_code: 發送認證碼 144 | auth: 認證 145 | 146 | hardware_name: 設備名稱 147 | auth_code: 設備認證碼 148 | auth_code_tooltip: |- 149 | 輸入您電子郵件中提供的字母數字代碼。 150 | send_auth_code_success: 已成功發送認證碼。 151 | auth_success: 認證成功。 152 | device_list_success: 成功檢索設備清單。 153 | delete_success: 已成功刪除。 154 | 155 | device_registrations: "已註冊設備數: %{count} / %{limit}" 156 | 157 | setting: 158 | save: 保存 159 | reset_all_settings: 重置所有設定 160 | confirm_reset: 您確定要重置所有設定嗎? 161 | save_success: 設定已成功保存。 162 | dmm_game_player_program_folder: DMMGamePlayer 程序文件夾 163 | dmm_game_player_data_folder: DMMGamePlayer 數據文件夾 164 | lang: 語言 165 | theme: 主題 166 | appearance: 外貌 167 | 168 | font_preset: 字型預設 169 | font_preset_tooltip: |- 170 | 選擇字型預設。 171 | i18n: 將選擇針對每種語言優化的字型。 172 | os: 將選擇操作系統的預設字型。 173 | theme: 將選擇與主題匹配的字型。 174 | 175 | proxy_all: 代理 176 | proxy_all_tooltip: |- 177 | 設定遊戲的代理。 178 | 例子:http://127.0.0.1:80 179 | https://127.0.0.1:443 180 | socks5://127.0.0.1:1080 181 | socks5://user:pass@127.0.0.1:1080 182 | 183 | dmm_proxy_all: DMM 代理 184 | dmm_proxy_all_tooltip: |- 185 | 設定 DMM 的代理。 186 | 例子:http://127.0.0.1:80 187 | https://127.0.0.1:443 188 | socks5://127.0.0.1:1080 189 | socks5://user:pass@127.0.0.1:1080 190 | 191 | window_scaling: 視窗縮放比例 192 | debug_window: 顯示除錯窗口 193 | output_logfile: 以文件形式記錄日誌 194 | mask_token: 隱藏日誌上的令牌 195 | 196 | device_detail: |- 197 | 設定用於設備認證的設備信息。 198 | 這些值不需要精確。 199 | 200 | mac_address: MAC 地址 201 | hdd_serial: 硬碟序列號 202 | motherboard: 主機板 ID 203 | user_os: 作業系統 204 | 205 | other_detail: 使用文本編輯器編輯工具設定。 206 | open_save_folder: 打開設定文件夾 207 | 208 | help: 209 | coop_in_develop: 參與開發 210 | donations_to_developer: 向開發者捐款 211 | bug_report: 報告問題 212 | 213 | launch: 214 | export_error: 匯出失敗。 215 | import_error: 匯入失敗。 216 | 217 | admin_error: 需要管理員權限。 218 | 219 | component: 220 | required: 此字段為必填項目。 221 | required_symbol: "*" 222 | reference: 參考 223 | 224 | "yes": 是 225 | "no": 否 226 | 227 | download: 正在下載... 228 | 229 | toast: 230 | report: 報告 231 | copy_to_clipboard: "複製到剪貼板" 232 | details: 詳細資訊 233 | 234 | lib: 235 | dmm_already_running: DMMGamePlayer 已經在運行,因此無法啟動。 236 | 237 | utils: 238 | file_exists: 該文件已經存在。 239 | -------------------------------------------------------------------------------- /DMMGamePlayerFastLauncher/tab/setting.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | 4 | import customtkinter as ctk 5 | import i18n 6 | from component.component import CheckBoxComponent, ConfirmWindow, DirectoryPathComponent, EntryComponent, OptionMenuComponent, OptionMenuTupleComponent, PaddingComponent 7 | from component.slider import CTkFloatSlider 8 | from component.tab_menu import TabMenuComponent 9 | from customtkinter import CTkBaseClass, CTkButton, CTkFrame, CTkLabel, CTkScrollableFrame 10 | from lib.toast import ToastController, error_toast 11 | from models.setting_data import AppConfig, DeviceData, SettingData 12 | from static.config import AssetsPathConfig, DataPathConfig 13 | from utils.utils import get_supported_lang 14 | 15 | 16 | class SettingTab(CTkFrame): 17 | tab: TabMenuComponent 18 | 19 | def __init__(self, master: CTkBaseClass): 20 | super().__init__(master, fg_color="transparent") 21 | self.tab = TabMenuComponent(self) 22 | 23 | def create(self): 24 | self.tab.create() 25 | self.tab.add(text=i18n.t("app.tab.edit"), callback=self.edit_callback) 26 | self.tab.add(text=i18n.t("app.tab.device"), callback=self.device_callback) 27 | self.tab.add(text=i18n.t("app.tab.other"), callback=self.other_callback) 28 | return self 29 | 30 | def edit_callback(self, master: CTkBaseClass): 31 | SettingEditTab(master).create().pack(expand=True, fill=ctk.BOTH) 32 | 33 | def device_callback(self, master: CTkBaseClass): 34 | SettingDeviceTab(master).create().pack(expand=True, fill=ctk.BOTH) 35 | 36 | def other_callback(self, master: CTkBaseClass): 37 | SettingOtherTab(master).create().pack(expand=True, fill=ctk.BOTH) 38 | 39 | 40 | class SettingEditTab(CTkScrollableFrame): 41 | toast: ToastController 42 | data: SettingData 43 | lang: list[tuple[str, str]] 44 | theme: list[str] 45 | 46 | def __init__(self, master: CTkBaseClass): 47 | super().__init__(master, fg_color="transparent") 48 | self.toast = ToastController(self) 49 | self.data = AppConfig.DATA 50 | self.lang = get_supported_lang() 51 | 52 | self.theme = [x.stem for x in AssetsPathConfig.THEMES.iterdir()] 53 | 54 | def create(self): 55 | DirectoryPathComponent(self, text=i18n.t("app.setting.dmm_game_player_program_folder"), variable=self.data.dmm_game_player_program_folder, required=True).create() 56 | DirectoryPathComponent(self, text=i18n.t("app.setting.dmm_game_player_data_folder"), variable=self.data.dmm_game_player_data_folder, required=True).create() 57 | OptionMenuTupleComponent(self, text=i18n.t("app.setting.lang"), values=self.lang, variable=self.data.lang).create() 58 | OptionMenuComponent(self, text=i18n.t("app.setting.theme"), values=self.theme, variable=self.data.theme).create() 59 | OptionMenuComponent(self, text=i18n.t("app.setting.appearance"), values=["light", "dark", "system"], variable=self.data.appearance_mode).create() 60 | 61 | text = i18n.t("app.setting.font_preset") 62 | OptionMenuComponent(self, text=text, tooltip=i18n.t("app.setting.font_preset_tooltip"), values=["i18n", "os", "theme"], variable=self.data.theme_font).create() 63 | 64 | text = i18n.t("app.setting.proxy_all") 65 | EntryComponent(self, text=text, tooltip=i18n.t("app.setting.proxy_all_tooltip"), variable=self.data.proxy_all).create() 66 | text = i18n.t("app.setting.dmm_proxy_all") 67 | EntryComponent(self, text=text, tooltip=i18n.t("app.setting.dmm_proxy_all_tooltip"), variable=self.data.dmm_proxy_all).create() 68 | 69 | PaddingComponent(self, height=5).create() 70 | CTkLabel(self, text=i18n.t("app.setting.window_scaling")).pack(anchor=ctk.W) 71 | CTkFloatSlider(self, from_=0.75, to=1.25, variable=self.data.window_scaling).pack(fill=ctk.X) 72 | 73 | PaddingComponent(self, height=5).create() 74 | CheckBoxComponent(self, text=i18n.t("app.setting.debug_window"), variable=self.data.debug_window).create() 75 | CheckBoxComponent(self, text=i18n.t("app.setting.output_logfile"), variable=self.data.output_logfile).create() 76 | CheckBoxComponent(self, text=i18n.t("app.setting.mask_token"), variable=self.data.mask_token).create() 77 | 78 | PaddingComponent(self, height=5).create() 79 | CTkButton(self, text=i18n.t("app.setting.save"), command=self.save_callback).pack(fill=ctk.X, pady=10) 80 | 81 | def command(): 82 | return ConfirmWindow(self, command=self.delete_callback, text=i18n.t("app.setting.confirm_reset")).create() 83 | 84 | CTkButton(self, text=i18n.t("app.setting.reset_all_settings"), command=command).pack(fill=ctk.X, pady=10) 85 | 86 | return self 87 | 88 | @error_toast 89 | def save_callback(self): 90 | with open(DataPathConfig.APP_CONFIG, "w+", encoding="utf-8") as f: 91 | json.dump(self.data.to_dict(), f) 92 | self.reload_callback() 93 | 94 | @error_toast 95 | def reload_callback(self): 96 | from app import App 97 | 98 | app = self.winfo_toplevel() 99 | assert isinstance(app, App) 100 | app.loder(app) 101 | app.create() 102 | self.toast.info(i18n.t("app.setting.save_success")) 103 | 104 | @error_toast 105 | def delete_callback(self): 106 | DataPathConfig.APP_CONFIG.unlink() 107 | self.reload_callback() 108 | 109 | 110 | class SettingDeviceTab(CTkScrollableFrame): 111 | toast: ToastController 112 | data: DeviceData 113 | 114 | def __init__(self, master: CTkBaseClass): 115 | super().__init__(master, fg_color="transparent") 116 | self.toast = ToastController(self) 117 | self.data = AppConfig.DEVICE 118 | 119 | def create(self): 120 | CTkLabel(self, text=i18n.t("app.setting.device_detail"), justify=ctk.LEFT).pack(anchor=ctk.W) 121 | EntryComponent(self, text=i18n.t("app.setting.mac_address"), variable=self.data.mac_address, required=True).create() 122 | EntryComponent(self, text=i18n.t("app.setting.hdd_serial"), variable=self.data.hdd_serial, required=True).create() 123 | EntryComponent(self, text=i18n.t("app.setting.motherboard"), variable=self.data.motherboard, required=True).create() 124 | EntryComponent(self, text=i18n.t("app.setting.user_os"), variable=self.data.user_os, required=True).create() 125 | CTkButton(self, text=i18n.t("app.setting.save"), command=self.save_callback).pack(fill=ctk.X, pady=10) 126 | return self 127 | 128 | def save_callback(self): 129 | with open(DataPathConfig.DEVICE, "w+", encoding="utf-8") as f: 130 | json.dump(AppConfig.DEVICE.to_dict(), f) 131 | AppConfig.DEVICE.update() 132 | self.toast.info(i18n.t("app.setting.save_success")) 133 | 134 | 135 | class SettingOtherTab(CTkScrollableFrame): 136 | toast: ToastController 137 | 138 | def __init__(self, master: CTkBaseClass): 139 | super().__init__(master, fg_color="transparent") 140 | self.toast = ToastController(self) 141 | 142 | def create(self): 143 | CTkLabel(self, text=i18n.t("app.setting.other_detail"), justify=ctk.LEFT).pack(anchor=ctk.W) 144 | CTkButton(self, text=i18n.t("app.setting.open_save_folder"), command=self.open_folder_callback).pack(fill=ctk.X, pady=10) 145 | return self 146 | 147 | @error_toast 148 | def open_folder_callback(self): 149 | os.startfile(DataPathConfig.DATA) 150 | -------------------------------------------------------------------------------- /assets/i18n/app.ja_JP.yml: -------------------------------------------------------------------------------- 1 | ja_JP: 2 | title: DMMGamePlayerFastLauncher 3 | language: 日本語 4 | 5 | font: 6 | main: "Yu Gothic UI" 7 | 8 | tab: 9 | home: ホーム 10 | shortcut: ショートカット 11 | account: アカウント 12 | setting: 設定 13 | other: その他 14 | help: ヘルプ 15 | create: ショートカットの作成 16 | edit: ショートカットの編集 17 | launch_create: GamePlayer の高速化 18 | launch_edit: GamePlayer 高速化の編集 19 | account_import: インポート 20 | account_edit: アカウントの編集 21 | import_browser: ブラウザーからインポート 22 | device: デバイスの登録 23 | device_list: デバイスの一覧 24 | 25 | home: 26 | new_version: DMMGamePlayerFastLauncher の新しいバージョンがあります。 27 | 28 | shortcut: 29 | filename: ファイル名 30 | filename_tooltip: |- 31 | このショートカットに任意の名前を付けてください。 32 | 半角英数字以外の文字列を使用すると問題が発生する可能性があります。 33 | 34 | product_id: product_id の選択 35 | product_id_tooltip: |- 36 | product_id とは DMM がゲームを識別する際に使用する ID です。 37 | 38 | account_create_detail: |- 39 | DMMGamePlayer を高速で起動するためのショートカットを作成します。 40 | 「アカウント」タブで作成したアカウントを選択してください。 41 | 42 | account_path: アカウントの選択 43 | account_path_tooltip: 「アカウント」タブで作成したアカウントを選択してください。 44 | always_extract_from_dmm: 常にDMMから抽出する 45 | game_args: ゲームの引数 46 | game_args_tooltip: |- 47 | ゲームに渡す引数を指定します。 48 | Unity 製のゲームであれば「-screen-fullscreen 0 -screen-width 1280 -screen-height 720」と入力すると 49 | ゲームが起動した際にウィンドウモードで 1280x720 の解像度で起動します。 50 | external_tool_path: 外部ツールのパス 51 | external_tool_path_tooltip: ゲームを起動する際に外部ツールを使用する場合はこちらにパスを指定してください。 52 | auto_update: 起動時にゲームの自動更新をする 53 | rich_presence: Discord のリッチプレゼンスを有効化する 54 | 55 | create_bypass_shortcut_and_save: UAC 自動昇格のショートカットを作成して設定を保存する 56 | create_bypass_shortcut_and_save_tooltip: |- 57 | UAC自動昇格のショートカットを作成して設定を保存します。 58 | 起動する際に管理者権限が必要なゲームの場合はこちらを選択します。 59 | create_uac_shortcut_and_save: UAC 手動昇格のショートカットを作成して設定を保存する 60 | create_uac_shortcut_and_save_tooltip: |- 61 | UAC 手動昇格のショートカットを作成して設定を保存します。 62 | 起動する際に管理者権限が必要なゲームの場合はこちらを選択します。 63 | ゲームを起動するたびに UAC のダイアログが表示されるため、手動で昇格する必要があります。 64 | 同一プロセスで実行されるため他のソフトウェアとの連携が強化されます。 65 | create_shortcut_and_save: ショートカットを作成して設定を保存する 66 | create_shortcut_and_save_tooltip: |- 67 | ショートカットを作成して設定を保存します。 68 | 起動する際に管理者権限が不要なゲームの場合はこちらを選択します。 69 | 管理者権限が必要なゲームでこのオプションを選択するとエラーが発生します。 70 | save_only: 設定の保存のみ行なう 71 | save_only_tooltip: |- 72 | ショートカットを作成せずに設定のみ保存します。 73 | 保存した設定からショートカットを起動する場合は 74 | 「DMMGamePlayerFastLauncher.exe [ファイル名] --type game」とコマンドラインで実行します。 75 | add_detail: 高速起動を行なうショートカットと設定を作成します。 76 | edit_detail: 高速起動のショートカットの設定を編集します。 77 | 78 | file_select: ファイルの選択 79 | 80 | product_id_not_entered: product_id が入力されていません。 81 | filename_not_entered: ファイル名が入力されていません。 82 | account_path_not_entered: アカウントが選択されていません。 83 | game_info_error: ゲーム情報の取得に失敗しました。 84 | administrator_error: 管理者権限が必要です。「UAC の自動昇格ショートカット」を作成してください。 85 | file_not_selected: ファイルが選択されていません。 86 | save_success: 保存しました。 87 | delete: 削除をする 88 | 89 | account_edit_detail: DMMGamePlayer を高速起動するショートカットの設定を編集します。 90 | dgp_args: DMMGamePlayer の引数 91 | dgp_args_tooltip: |- 92 | DMMGamePlayer に渡す引数を指定します。 93 | 例えば「dmmgameplayer://play/GCL/priconner/cl/win」と入力すると 94 | DMMGamePlayer が起動した際に「プリコネ Re:Dive」が起動します。 95 | 空欄の場合は「DMMGamePlayer のみ」が起動します。 96 | unity_command_line_args: Unityのコマンドライン引数についての詳しい説明を見る 97 | unity_command_line_args_tooltip: |- 98 | Unity のコマンドライン引数についてのサイトを開きます。 99 | 起動するゲームが Unity で作成されている場合なら参考になると思います。 100 | unity_command_line_args_link: https://docs.unity3d.com/ja/current/Manual/PlayerCommandLineArguments.html 101 | 102 | account: 103 | import_detail: |- 104 | DMMGamePlayer のアカウント情報を DMMGamePlayerFastLauncher にインポートします。 105 | この操作は非推奨です。ブラウザーからのインポートを推奨しています。 106 | filename_tooltip: |- 107 | このアカウントに任意の名前を付けてください。 108 | 半角英数字以外を使用すると問題が発生する可能性があります。 109 | import: インポート 110 | filename: ファイル名 111 | filename_not_entered: ファイル名が入力されていません。 112 | filename_already_exists: そのファイル名は既に存在します。 113 | filename_reserved: そのファイル名は予約されています。 114 | import_error: インポートに失敗しました。 115 | import_success: インポートに成功しました。 116 | 117 | file_select: ファイルの選択 118 | 119 | import_browser_detail: ブラウザーを起動して DMMGamePlayer のアカウント情報をインポートします。 120 | browser_select: ブラウザーの選択 121 | browser_select_tooltip: |- 122 | 使用するブラウザーを選択してください。 123 | 指定されたブラウザーを起動してログイン画面を開きます。 124 | auto_refresh: 自動でログインセッションを更新する 125 | browser_not_selected: ブラウザーが選択されていません。 126 | import_browser: インポート 127 | import_browser_success: インポートに成功しました。 128 | 129 | edit_detail: |- 130 | アカウント情報を編集します。 131 | これは非常に高度な操作になります。 132 | save: 保存 133 | delete: 削除 134 | save_success: 保存しました。 135 | 136 | device_detail: |- 137 | メールアドレスに送信された「デバイス認証コード」を入力してください。 138 | 139 | send_auth_code: 認証コードを送信する 140 | auth: 認証する 141 | 142 | hardware_name: デバイス名 143 | auth_code: デバイス認証コード 144 | auth_code_tooltip: |- 145 | メールに記載の英数字を入力してください 146 | send_auth_code_success: 認証コードを送信しました。 147 | auth_success: 認証に成功しました。 148 | device_list_success: デバイスの一覧を取得しました。 149 | delete_success: 削除しました。 150 | 151 | device_registrations: "登録されたデバイス数: %{count} / %{limit}" 152 | 153 | setting: 154 | save: 保存 155 | reset_all_settings: すべての設定をリセット 156 | confirm_reset: 本当にすべての設定をリセットしますか? 157 | save_success: 設定を保存しました。 158 | dmm_game_player_program_folder: DMMGamePlayer のプログラムフォルダー 159 | dmm_game_player_data_folder: DMMGamePlayer のデータフォルダー 160 | lang: 言語 161 | theme: テーマ 162 | appearance: 外観 163 | 164 | font_preset: フォントプリセット 165 | font_preset_tooltip: |- 166 | フォントプリセットを選択します。 167 | i18n: 言語ごとに最適化されたフォントが選択されます。 168 | os: OS の既定のフォントが選択されます。 169 | theme: テーマに合わせたフォントが選択されます。 170 | 171 | proxy_all: プロキシ 172 | proxy_all_tooltip: |- 173 | ゲームのプロキシを設定します。 174 | 例: http://127.0.0.1:80 175 | https://127.0.0.1:443 176 | socks5://127.0.0.1:1080 177 | socks5://user:pass@127.0.0.1:1080 178 | 179 | dmm_proxy_all: DMM プロキシ 180 | dmm_proxy_all_tooltip: |- 181 | DMM のプロキシを設定します。 182 | 例: http://127.0.0.1:80 183 | https://127.0.0.1:443 184 | socks5://127.0.0.1:1080 185 | socks5://user:pass@127.0.0.1:1080 186 | 187 | window_scaling: ウィンドウの拡大率 188 | debug_window: デバッグウィンドウを表示する 189 | output_logfile: ログをファイルで出力する 190 | mask_token: ログ上のトークンを隠す 191 | 192 | device_detail: |- 193 | デバイス認証に使用するデバイス情報を設定します。 194 | これらは適当で構いません。 195 | 196 | mac_address: MAC アドレス 197 | hdd_serial: HDD シリアル 198 | motherboard: マザーボード ID 199 | user_os: OS 200 | 201 | other_detail: このツールの設定をテキストエディターで編集します。 202 | open_save_folder: 設定ファイルを開く 203 | 204 | help: 205 | coop_in_develop: 開発に協力をする 206 | donations_to_developer: 開発者に寄付をする 207 | bug_report: バグを報告する 208 | 209 | launch: 210 | export_error: エクスポートに失敗しました。 211 | import_error: インポートに失敗しました。 212 | 213 | admin_error: 管理者権限が必要です。 214 | 215 | component: 216 | required: 入力必須項目です。 217 | required_symbol: "*" 218 | reference: 参照 219 | 220 | "yes": はい 221 | "no": いいえ 222 | 223 | download: ダウンロード中です... 224 | 225 | toast: 226 | report: 報告 227 | copy_to_clipboard: "クリップボードにコピー" 228 | details: 詳細 229 | 230 | lib: 231 | dmm_already_running: DMMGamePlayer が実行中のため、起動できませんでした。 232 | 233 | utils: 234 | file_exists: そのファイルは既に存在します。 235 | -------------------------------------------------------------------------------- /assets/themes/blue.json: -------------------------------------------------------------------------------- 1 | { 2 | "CTk": { 3 | "fg_color": [ 4 | "gray92", 5 | "gray14" 6 | ] 7 | }, 8 | "CTkToplevel": { 9 | "fg_color": [ 10 | "gray92", 11 | "gray14" 12 | ] 13 | }, 14 | "CTkFrame": { 15 | "corner_radius": 6, 16 | "border_width": 0, 17 | "fg_color": [ 18 | "gray86", 19 | "gray17" 20 | ], 21 | "top_fg_color": [ 22 | "gray81", 23 | "gray20" 24 | ], 25 | "border_color": [ 26 | "gray65", 27 | "gray28" 28 | ] 29 | }, 30 | "CTkButton": { 31 | "corner_radius": 6, 32 | "border_width": 0, 33 | "fg_color": [ 34 | "#3B8ED0", 35 | "#1F6AA5" 36 | ], 37 | "hover_color": [ 38 | "#36719F", 39 | "#144870" 40 | ], 41 | "border_color": [ 42 | "#3E454A", 43 | "#949A9F" 44 | ], 45 | "text_color": [ 46 | "#DCE4EE", 47 | "#DCE4EE" 48 | ], 49 | "text_color_disabled": [ 50 | "gray74", 51 | "gray60" 52 | ] 53 | }, 54 | "CTkLabel": { 55 | "corner_radius": 0, 56 | "fg_color": "transparent", 57 | "text_color": [ 58 | "gray10", 59 | "#DCE4EE" 60 | ] 61 | }, 62 | "CTkEntry": { 63 | "corner_radius": 6, 64 | "border_width": 2, 65 | "fg_color": [ 66 | "#F9F9FA", 67 | "#343638" 68 | ], 69 | "border_color": [ 70 | "#979DA2", 71 | "#565B5E" 72 | ], 73 | "text_color": [ 74 | "gray10", 75 | "#DCE4EE" 76 | ], 77 | "placeholder_text_color": [ 78 | "gray52", 79 | "gray62" 80 | ] 81 | }, 82 | "CTkCheckBox": { 83 | "corner_radius": 6, 84 | "border_width": 3, 85 | "fg_color": [ 86 | "#3B8ED0", 87 | "#1F6AA5" 88 | ], 89 | "border_color": [ 90 | "#3E454A", 91 | "#949A9F" 92 | ], 93 | "hover_color": [ 94 | "#3B8ED0", 95 | "#1F6AA5" 96 | ], 97 | "checkmark_color": [ 98 | "#DCE4EE", 99 | "gray90" 100 | ], 101 | "text_color": [ 102 | "gray10", 103 | "#DCE4EE" 104 | ], 105 | "text_color_disabled": [ 106 | "gray60", 107 | "gray45" 108 | ] 109 | }, 110 | "CTkSwitch": { 111 | "corner_radius": 1000, 112 | "border_width": 3, 113 | "button_length": 0, 114 | "fg_color": [ 115 | "#939BA2", 116 | "#4A4D50" 117 | ], 118 | "progress_color": [ 119 | "#3B8ED0", 120 | "#1F6AA5" 121 | ], 122 | "button_color": [ 123 | "gray36", 124 | "#D5D9DE" 125 | ], 126 | "button_hover_color": [ 127 | "gray20", 128 | "gray100" 129 | ], 130 | "text_color": [ 131 | "gray10", 132 | "#DCE4EE" 133 | ], 134 | "text_color_disabled": [ 135 | "gray60", 136 | "gray45" 137 | ] 138 | }, 139 | "CTkRadioButton": { 140 | "corner_radius": 1000, 141 | "border_width_checked": 6, 142 | "border_width_unchecked": 3, 143 | "fg_color": [ 144 | "#3B8ED0", 145 | "#1F6AA5" 146 | ], 147 | "border_color": [ 148 | "#3E454A", 149 | "#949A9F" 150 | ], 151 | "hover_color": [ 152 | "#36719F", 153 | "#144870" 154 | ], 155 | "text_color": [ 156 | "gray10", 157 | "#DCE4EE" 158 | ], 159 | "text_color_disabled": [ 160 | "gray60", 161 | "gray45" 162 | ] 163 | }, 164 | "CTkProgressBar": { 165 | "corner_radius": 1000, 166 | "border_width": 0, 167 | "fg_color": [ 168 | "#939BA2", 169 | "#4A4D50" 170 | ], 171 | "progress_color": [ 172 | "#3B8ED0", 173 | "#1F6AA5" 174 | ], 175 | "border_color": [ 176 | "gray", 177 | "gray" 178 | ] 179 | }, 180 | "CTkSlider": { 181 | "corner_radius": 1000, 182 | "button_corner_radius": 1000, 183 | "border_width": 6, 184 | "button_length": 0, 185 | "fg_color": [ 186 | "#939BA2", 187 | "#4A4D50" 188 | ], 189 | "progress_color": [ 190 | "gray40", 191 | "#AAB0B5" 192 | ], 193 | "button_color": [ 194 | "#3B8ED0", 195 | "#1F6AA5" 196 | ], 197 | "button_hover_color": [ 198 | "#36719F", 199 | "#144870" 200 | ] 201 | }, 202 | "CTkOptionMenu": { 203 | "corner_radius": 6, 204 | "fg_color": [ 205 | "#3B8ED0", 206 | "#1F6AA5" 207 | ], 208 | "button_color": [ 209 | "#36719F", 210 | "#144870" 211 | ], 212 | "button_hover_color": [ 213 | "#27577D", 214 | "#203A4F" 215 | ], 216 | "text_color": [ 217 | "#DCE4EE", 218 | "#DCE4EE" 219 | ], 220 | "text_color_disabled": [ 221 | "gray74", 222 | "gray60" 223 | ] 224 | }, 225 | "CTkComboBox": { 226 | "corner_radius": 6, 227 | "border_width": 2, 228 | "fg_color": [ 229 | "#F9F9FA", 230 | "#343638" 231 | ], 232 | "border_color": [ 233 | "#979DA2", 234 | "#565B5E" 235 | ], 236 | "button_color": [ 237 | "#979DA2", 238 | "#565B5E" 239 | ], 240 | "button_hover_color": [ 241 | "#6E7174", 242 | "#7A848D" 243 | ], 244 | "text_color": [ 245 | "gray10", 246 | "#DCE4EE" 247 | ], 248 | "text_color_disabled": [ 249 | "gray50", 250 | "gray45" 251 | ] 252 | }, 253 | "CTkScrollbar": { 254 | "corner_radius": 1000, 255 | "border_spacing": 4, 256 | "fg_color": "transparent", 257 | "button_color": [ 258 | "gray55", 259 | "gray41" 260 | ], 261 | "button_hover_color": [ 262 | "gray40", 263 | "gray53" 264 | ] 265 | }, 266 | "CTkSegmentedButton": { 267 | "corner_radius": 6, 268 | "border_width": 2, 269 | "fg_color": [ 270 | "#979DA2", 271 | "gray29" 272 | ], 273 | "selected_color": [ 274 | "#3B8ED0", 275 | "#1F6AA5" 276 | ], 277 | "selected_hover_color": [ 278 | "#36719F", 279 | "#144870" 280 | ], 281 | "unselected_color": [ 282 | "#979DA2", 283 | "gray29" 284 | ], 285 | "unselected_hover_color": [ 286 | "gray70", 287 | "gray41" 288 | ], 289 | "text_color": [ 290 | "#DCE4EE", 291 | "#DCE4EE" 292 | ], 293 | "text_color_disabled": [ 294 | "gray74", 295 | "gray60" 296 | ] 297 | }, 298 | "CTkTextbox": { 299 | "corner_radius": 6, 300 | "border_width": 0, 301 | "fg_color": [ 302 | "#F9F9FA", 303 | "#1D1E1E" 304 | ], 305 | "border_color": [ 306 | "#979DA2", 307 | "#565B5E" 308 | ], 309 | "text_color": [ 310 | "gray10", 311 | "#DCE4EE" 312 | ], 313 | "scrollbar_button_color": [ 314 | "gray55", 315 | "gray41" 316 | ], 317 | "scrollbar_button_hover_color": [ 318 | "gray40", 319 | "gray53" 320 | ] 321 | }, 322 | "CTkScrollableFrame": { 323 | "label_fg_color": [ 324 | "gray78", 325 | "gray23" 326 | ] 327 | }, 328 | "DropdownMenu": { 329 | "fg_color": [ 330 | "gray90", 331 | "gray20" 332 | ], 333 | "hover_color": [ 334 | "gray75", 335 | "gray28" 336 | ], 337 | "text_color": [ 338 | "gray10", 339 | "gray90" 340 | ] 341 | }, 342 | "CTkFont": { 343 | "macOS": { 344 | "family": "SF Display", 345 | "size": 13, 346 | "weight": "normal" 347 | }, 348 | "Windows": { 349 | "family": "Roboto", 350 | "size": 13, 351 | "weight": "normal" 352 | }, 353 | "Linux": { 354 | "family": "Roboto", 355 | "size": 13, 356 | "weight": "normal" 357 | } 358 | } 359 | } -------------------------------------------------------------------------------- /assets/themes/dark-blue.json: -------------------------------------------------------------------------------- 1 | { 2 | "CTk": { 3 | "fg_color": [ 4 | "gray95", 5 | "gray10" 6 | ] 7 | }, 8 | "CTkToplevel": { 9 | "fg_color": [ 10 | "gray95", 11 | "gray10" 12 | ] 13 | }, 14 | "CTkFrame": { 15 | "corner_radius": 6, 16 | "border_width": 0, 17 | "fg_color": [ 18 | "gray90", 19 | "gray13" 20 | ], 21 | "top_fg_color": [ 22 | "gray85", 23 | "gray16" 24 | ], 25 | "border_color": [ 26 | "gray65", 27 | "gray28" 28 | ] 29 | }, 30 | "CTkButton": { 31 | "corner_radius": 6, 32 | "border_width": 0, 33 | "fg_color": [ 34 | "#3a7ebf", 35 | "#1f538d" 36 | ], 37 | "hover_color": [ 38 | "#325882", 39 | "#14375e" 40 | ], 41 | "border_color": [ 42 | "#3E454A", 43 | "#949A9F" 44 | ], 45 | "text_color": [ 46 | "#DCE4EE", 47 | "#DCE4EE" 48 | ], 49 | "text_color_disabled": [ 50 | "gray74", 51 | "gray60" 52 | ] 53 | }, 54 | "CTkLabel": { 55 | "corner_radius": 0, 56 | "fg_color": "transparent", 57 | "text_color": [ 58 | "gray14", 59 | "gray84" 60 | ] 61 | }, 62 | "CTkEntry": { 63 | "corner_radius": 6, 64 | "border_width": 2, 65 | "fg_color": [ 66 | "#F9F9FA", 67 | "#343638" 68 | ], 69 | "border_color": [ 70 | "#979DA2", 71 | "#565B5E" 72 | ], 73 | "text_color": [ 74 | "gray14", 75 | "gray84" 76 | ], 77 | "placeholder_text_color": [ 78 | "gray52", 79 | "gray62" 80 | ] 81 | }, 82 | "CTkCheckBox": { 83 | "corner_radius": 6, 84 | "border_width": 3, 85 | "fg_color": [ 86 | "#3a7ebf", 87 | "#1f538d" 88 | ], 89 | "border_color": [ 90 | "#3E454A", 91 | "#949A9F" 92 | ], 93 | "hover_color": [ 94 | "#325882", 95 | "#14375e" 96 | ], 97 | "checkmark_color": [ 98 | "#DCE4EE", 99 | "gray90" 100 | ], 101 | "text_color": [ 102 | "gray14", 103 | "gray84" 104 | ], 105 | "text_color_disabled": [ 106 | "gray60", 107 | "gray45" 108 | ] 109 | }, 110 | "CTkSwitch": { 111 | "corner_radius": 1000, 112 | "border_width": 3, 113 | "button_length": 0, 114 | "fg_color": [ 115 | "#939BA2", 116 | "#4A4D50" 117 | ], 118 | "progress_color": [ 119 | "#3a7ebf", 120 | "#1f538d" 121 | ], 122 | "button_color": [ 123 | "gray36", 124 | "#D5D9DE" 125 | ], 126 | "button_hover_color": [ 127 | "gray20", 128 | "gray100" 129 | ], 130 | "text_color": [ 131 | "gray14", 132 | "gray84" 133 | ], 134 | "text_color_disabled": [ 135 | "gray60", 136 | "gray45" 137 | ] 138 | }, 139 | "CTkRadioButton": { 140 | "corner_radius": 1000, 141 | "border_width_checked": 6, 142 | "border_width_unchecked": 3, 143 | "fg_color": [ 144 | "#3a7ebf", 145 | "#1f538d" 146 | ], 147 | "border_color": [ 148 | "#3E454A", 149 | "#949A9F" 150 | ], 151 | "hover_color": [ 152 | "#325882", 153 | "#14375e" 154 | ], 155 | "text_color": [ 156 | "gray14", 157 | "gray84" 158 | ], 159 | "text_color_disabled": [ 160 | "gray60", 161 | "gray45" 162 | ] 163 | }, 164 | "CTkProgressBar": { 165 | "corner_radius": 1000, 166 | "border_width": 0, 167 | "fg_color": [ 168 | "#939BA2", 169 | "#4A4D50" 170 | ], 171 | "progress_color": [ 172 | "#3a7ebf", 173 | "#1f538d" 174 | ], 175 | "border_color": [ 176 | "gray", 177 | "gray" 178 | ] 179 | }, 180 | "CTkSlider": { 181 | "corner_radius": 1000, 182 | "button_corner_radius": 1000, 183 | "border_width": 6, 184 | "button_length": 0, 185 | "fg_color": [ 186 | "#939BA2", 187 | "#4A4D50" 188 | ], 189 | "progress_color": [ 190 | "gray40", 191 | "#AAB0B5" 192 | ], 193 | "button_color": [ 194 | "#3a7ebf", 195 | "#1f538d" 196 | ], 197 | "button_hover_color": [ 198 | "#325882", 199 | "#14375e" 200 | ] 201 | }, 202 | "CTkOptionMenu": { 203 | "corner_radius": 6, 204 | "fg_color": [ 205 | "#3a7ebf", 206 | "#1f538d" 207 | ], 208 | "button_color": [ 209 | "#325882", 210 | "#14375e" 211 | ], 212 | "button_hover_color": [ 213 | "#234567", 214 | "#1e2c40" 215 | ], 216 | "text_color": [ 217 | "#DCE4EE", 218 | "#DCE4EE" 219 | ], 220 | "text_color_disabled": [ 221 | "gray74", 222 | "gray60" 223 | ] 224 | }, 225 | "CTkComboBox": { 226 | "corner_radius": 6, 227 | "border_width": 2, 228 | "fg_color": [ 229 | "#F9F9FA", 230 | "#343638" 231 | ], 232 | "border_color": [ 233 | "#979DA2", 234 | "#565B5E" 235 | ], 236 | "button_color": [ 237 | "#979DA2", 238 | "#565B5E" 239 | ], 240 | "button_hover_color": [ 241 | "#6E7174", 242 | "#7A848D" 243 | ], 244 | "text_color": [ 245 | "gray14", 246 | "gray84" 247 | ], 248 | "text_color_disabled": [ 249 | "gray50", 250 | "gray45" 251 | ] 252 | }, 253 | "CTkScrollbar": { 254 | "corner_radius": 1000, 255 | "border_spacing": 4, 256 | "fg_color": "transparent", 257 | "button_color": [ 258 | "gray55", 259 | "gray41" 260 | ], 261 | "button_hover_color": [ 262 | "gray40", 263 | "gray53" 264 | ] 265 | }, 266 | "CTkSegmentedButton": { 267 | "corner_radius": 6, 268 | "border_width": 2, 269 | "fg_color": [ 270 | "#979DA2", 271 | "gray29" 272 | ], 273 | "selected_color": [ 274 | "#3a7ebf", 275 | "#1f538d" 276 | ], 277 | "selected_hover_color": [ 278 | "#325882", 279 | "#14375e" 280 | ], 281 | "unselected_color": [ 282 | "#979DA2", 283 | "gray29" 284 | ], 285 | "unselected_hover_color": [ 286 | "gray70", 287 | "gray41" 288 | ], 289 | "text_color": [ 290 | "#DCE4EE", 291 | "#DCE4EE" 292 | ], 293 | "text_color_disabled": [ 294 | "gray74", 295 | "gray60" 296 | ] 297 | }, 298 | "CTkTextbox": { 299 | "corner_radius": 6, 300 | "border_width": 0, 301 | "fg_color": [ 302 | "gray100", 303 | "gray20" 304 | ], 305 | "border_color": [ 306 | "#979DA2", 307 | "#565B5E" 308 | ], 309 | "text_color": [ 310 | "gray14", 311 | "gray84" 312 | ], 313 | "scrollbar_button_color": [ 314 | "gray55", 315 | "gray41" 316 | ], 317 | "scrollbar_button_hover_color": [ 318 | "gray40", 319 | "gray53" 320 | ] 321 | }, 322 | "CTkScrollableFrame": { 323 | "label_fg_color": [ 324 | "gray80", 325 | "gray21" 326 | ] 327 | }, 328 | "DropdownMenu": { 329 | "fg_color": [ 330 | "gray90", 331 | "gray20" 332 | ], 333 | "hover_color": [ 334 | "gray75", 335 | "gray28" 336 | ], 337 | "text_color": [ 338 | "gray14", 339 | "gray84" 340 | ] 341 | }, 342 | "CTkFont": { 343 | "macOS": { 344 | "family": "SF Display", 345 | "size": 13, 346 | "weight": "normal" 347 | }, 348 | "Windows": { 349 | "family": "Roboto", 350 | "size": 13, 351 | "weight": "normal" 352 | }, 353 | "Linux": { 354 | "family": "Roboto", 355 | "size": 13, 356 | "weight": "normal" 357 | } 358 | } 359 | } -------------------------------------------------------------------------------- /assets/themes/green.json: -------------------------------------------------------------------------------- 1 | { 2 | "CTk": { 3 | "fg_color": [ 4 | "gray92", 5 | "gray14" 6 | ] 7 | }, 8 | "CTkToplevel": { 9 | "fg_color": [ 10 | "gray92", 11 | "gray14" 12 | ] 13 | }, 14 | "CTkFrame": { 15 | "corner_radius": 6, 16 | "border_width": 0, 17 | "fg_color": [ 18 | "gray86", 19 | "gray17" 20 | ], 21 | "top_fg_color": [ 22 | "gray81", 23 | "gray20" 24 | ], 25 | "border_color": [ 26 | "gray65", 27 | "gray28" 28 | ] 29 | }, 30 | "CTkButton": { 31 | "corner_radius": 6, 32 | "border_width": 0, 33 | "fg_color": [ 34 | "#2CC985", 35 | "#2FA572" 36 | ], 37 | "hover_color": [ 38 | "#0C955A", 39 | "#106A43" 40 | ], 41 | "border_color": [ 42 | "#3E454A", 43 | "#949A9F" 44 | ], 45 | "text_color": [ 46 | "gray98", 47 | "#DCE4EE" 48 | ], 49 | "text_color_disabled": [ 50 | "gray78", 51 | "gray68" 52 | ] 53 | }, 54 | "CTkLabel": { 55 | "corner_radius": 0, 56 | "fg_color": "transparent", 57 | "text_color": [ 58 | "gray10", 59 | "#DCE4EE" 60 | ] 61 | }, 62 | "CTkEntry": { 63 | "corner_radius": 6, 64 | "border_width": 2, 65 | "fg_color": [ 66 | "#F9F9FA", 67 | "#343638" 68 | ], 69 | "border_color": [ 70 | "#979DA2", 71 | "#565B5E" 72 | ], 73 | "text_color": [ 74 | "gray10", 75 | "#DCE4EE" 76 | ], 77 | "placeholder_text_color": [ 78 | "gray52", 79 | "gray62" 80 | ] 81 | }, 82 | "CTkCheckBox": { 83 | "corner_radius": 6, 84 | "border_width": 3, 85 | "fg_color": [ 86 | "#2CC985", 87 | "#2FA572" 88 | ], 89 | "border_color": [ 90 | "#3E454A", 91 | "#949A9F" 92 | ], 93 | "hover_color": [ 94 | "#0C955A", 95 | "#106A43" 96 | ], 97 | "checkmark_color": [ 98 | "#DCE4EE", 99 | "gray90" 100 | ], 101 | "text_color": [ 102 | "gray10", 103 | "#DCE4EE" 104 | ], 105 | "text_color_disabled": [ 106 | "gray60", 107 | "gray45" 108 | ] 109 | }, 110 | "CTkSwitch": { 111 | "corner_radius": 1000, 112 | "border_width": 3, 113 | "button_length": 0, 114 | "fg_color": [ 115 | "#939BA2", 116 | "#4A4D50" 117 | ], 118 | "progress_color": [ 119 | "#2CC985", 120 | "#2FA572" 121 | ], 122 | "button_color": [ 123 | "gray36", 124 | "#D5D9DE" 125 | ], 126 | "button_hover_color": [ 127 | "gray20", 128 | "gray100" 129 | ], 130 | "text_color": [ 131 | "gray10", 132 | "#DCE4EE" 133 | ], 134 | "text_color_disabled": [ 135 | "gray60", 136 | "gray45" 137 | ] 138 | }, 139 | "CTkRadioButton": { 140 | "corner_radius": 1000, 141 | "border_width_checked": 6, 142 | "border_width_unchecked": 3, 143 | "fg_color": [ 144 | "#2CC985", 145 | "#2FA572" 146 | ], 147 | "border_color": [ 148 | "#3E454A", 149 | "#949A9F" 150 | ], 151 | "hover_color": [ 152 | "#0C955A", 153 | "#106A43" 154 | ], 155 | "text_color": [ 156 | "gray10", 157 | "#DCE4EE" 158 | ], 159 | "text_color_disabled": [ 160 | "gray60", 161 | "gray45" 162 | ] 163 | }, 164 | "CTkProgressBar": { 165 | "corner_radius": 1000, 166 | "border_width": 0, 167 | "fg_color": [ 168 | "#939BA2", 169 | "#4A4D50" 170 | ], 171 | "progress_color": [ 172 | "#2CC985", 173 | "#2FA572" 174 | ], 175 | "border_color": [ 176 | "gray", 177 | "gray" 178 | ] 179 | }, 180 | "CTkSlider": { 181 | "corner_radius": 1000, 182 | "button_corner_radius": 1000, 183 | "border_width": 6, 184 | "button_length": 0, 185 | "fg_color": [ 186 | "#939BA2", 187 | "#4A4D50" 188 | ], 189 | "progress_color": [ 190 | "gray40", 191 | "#AAB0B5" 192 | ], 193 | "button_color": [ 194 | "#2CC985", 195 | "#2FA572" 196 | ], 197 | "button_hover_color": [ 198 | "#0C955A", 199 | "#106A43" 200 | ] 201 | }, 202 | "CTkOptionMenu": { 203 | "corner_radius": 6, 204 | "fg_color": [ 205 | "#2cbe79", 206 | "#2FA572" 207 | ], 208 | "button_color": [ 209 | "#0C955A", 210 | "#106A43" 211 | ], 212 | "button_hover_color": [ 213 | "#0b6e3d", 214 | "#17472e" 215 | ], 216 | "text_color": [ 217 | "gray98", 218 | "#DCE4EE" 219 | ], 220 | "text_color_disabled": [ 221 | "gray78", 222 | "gray68" 223 | ] 224 | }, 225 | "CTkComboBox": { 226 | "corner_radius": 6, 227 | "border_width": 2, 228 | "fg_color": [ 229 | "#F9F9FA", 230 | "#343638" 231 | ], 232 | "border_color": [ 233 | "#979DA2", 234 | "#565B5E" 235 | ], 236 | "button_color": [ 237 | "#979DA2", 238 | "#565B5E" 239 | ], 240 | "button_hover_color": [ 241 | "#6E7174", 242 | "#7A848D" 243 | ], 244 | "text_color": [ 245 | "gray10", 246 | "#DCE4EE" 247 | ], 248 | "text_color_disabled": [ 249 | "gray50", 250 | "gray45" 251 | ] 252 | }, 253 | "CTkScrollbar": { 254 | "corner_radius": 1000, 255 | "border_spacing": 4, 256 | "fg_color": "transparent", 257 | "button_color": [ 258 | "gray55", 259 | "gray41" 260 | ], 261 | "button_hover_color": [ 262 | "gray40", 263 | "gray53" 264 | ] 265 | }, 266 | "CTkSegmentedButton": { 267 | "corner_radius": 6, 268 | "border_width": 2, 269 | "fg_color": [ 270 | "#979DA2", 271 | "gray29" 272 | ], 273 | "selected_color": [ 274 | "#2CC985", 275 | "#2FA572" 276 | ], 277 | "selected_hover_color": [ 278 | "#0C955A", 279 | "#106A43" 280 | ], 281 | "unselected_color": [ 282 | "#979DA2", 283 | "gray29" 284 | ], 285 | "unselected_hover_color": [ 286 | "gray70", 287 | "gray41" 288 | ], 289 | "text_color": [ 290 | "gray98", 291 | "#DCE4EE" 292 | ], 293 | "text_color_disabled": [ 294 | "gray78", 295 | "gray68" 296 | ] 297 | }, 298 | "CTkTextbox": { 299 | "corner_radius": 6, 300 | "border_width": 0, 301 | "fg_color": [ 302 | "#F9F9FA", 303 | "gray23" 304 | ], 305 | "border_color": [ 306 | "#979DA2", 307 | "#565B5E" 308 | ], 309 | "text_color": [ 310 | "gray10", 311 | "#DCE4EE" 312 | ], 313 | "scrollbar_button_color": [ 314 | "gray55", 315 | "gray41" 316 | ], 317 | "scrollbar_button_hover_color": [ 318 | "gray40", 319 | "gray53" 320 | ] 321 | }, 322 | "CTkScrollableFrame": { 323 | "label_fg_color": [ 324 | "gray78", 325 | "gray23" 326 | ] 327 | }, 328 | "DropdownMenu": { 329 | "fg_color": [ 330 | "gray90", 331 | "gray20" 332 | ], 333 | "hover_color": [ 334 | "gray75", 335 | "gray28" 336 | ], 337 | "text_color": [ 338 | "gray10", 339 | "gray90" 340 | ] 341 | }, 342 | "CTkFont": { 343 | "macOS": { 344 | "family": "SF Display", 345 | "size": 13, 346 | "weight": "normal" 347 | }, 348 | "Windows": { 349 | "family": "Roboto", 350 | "size": 13, 351 | "weight": "normal" 352 | }, 353 | "Linux": { 354 | "family": "Roboto", 355 | "size": 13, 356 | "weight": "normal" 357 | } 358 | } 359 | } -------------------------------------------------------------------------------- /assets/themes/magenta.json: -------------------------------------------------------------------------------- 1 | { 2 | "CTk": { 3 | "fg_color": [ 4 | "gray95", 5 | "gray10" 6 | ] 7 | }, 8 | "CTkToplevel": { 9 | "fg_color": [ 10 | "gray95", 11 | "gray10" 12 | ] 13 | }, 14 | "CTkFrame": { 15 | "corner_radius": 6, 16 | "border_width": 0, 17 | "fg_color": [ 18 | "gray90", 19 | "gray13" 20 | ], 21 | "top_fg_color": [ 22 | "gray85", 23 | "gray16" 24 | ], 25 | "border_color": [ 26 | "gray65", 27 | "gray28" 28 | ] 29 | }, 30 | "CTkButton": { 31 | "corner_radius": 6, 32 | "border_width": 0, 33 | "fg_color": [ 34 | "#c01d6f", 35 | "#8b0648" 36 | ], 37 | "hover_color": [ 38 | "#8d1c55", 39 | "#620533" 40 | ], 41 | "border_color": [ 42 | "grey27", 43 | "grey60" 44 | ], 45 | "text_color": [ 46 | "grey89", 47 | "grey89" 48 | ], 49 | "text_color_disabled": [ 50 | "gray74", 51 | "gray60" 52 | ] 53 | }, 54 | "CTkLabel": { 55 | "corner_radius": 0, 56 | "fg_color": "transparent", 57 | "text_color": [ 58 | "gray14", 59 | "gray84" 60 | ] 61 | }, 62 | "CTkEntry": { 63 | "corner_radius": 6, 64 | "border_width": 2, 65 | "fg_color": [ 66 | "gray98", 67 | "gray21" 68 | ], 69 | "border_color": [ 70 | "gray61", 71 | "grey35" 72 | ], 73 | "text_color": [ 74 | "gray14", 75 | "gray84" 76 | ], 77 | "placeholder_text_color": [ 78 | "gray52", 79 | "gray62" 80 | ] 81 | }, 82 | "CTkCheckbox": { 83 | "corner_radius": 6, 84 | "border_width": 3, 85 | "fg_color": [ 86 | "#c01d6f", 87 | "#8b0648" 88 | ], 89 | "border_color": [ 90 | "grey27", 91 | "grey60" 92 | ], 93 | "hover_color": [ 94 | "#8d1c55", 95 | "#620533" 96 | ], 97 | "checkmark_color": [ 98 | "grey89", 99 | "gray90" 100 | ], 101 | "text_color": [ 102 | "gray14", 103 | "gray84" 104 | ], 105 | "text_color_disabled": [ 106 | "gray60", 107 | "gray45" 108 | ] 109 | }, 110 | "CTkSwitch": { 111 | "corner_radius": 1000, 112 | "border_width": 3, 113 | "button_length": 0, 114 | "fg_Color": [ 115 | "gray60", 116 | "grey30" 117 | ], 118 | "progress_color": [ 119 | "#c01d6f", 120 | "#8b0648" 121 | ], 122 | "button_color": [ 123 | "gray36", 124 | "gray85" 125 | ], 126 | "button_hover_color": [ 127 | "gray20", 128 | "gray100" 129 | ], 130 | "text_color": [ 131 | "gray14", 132 | "gray84" 133 | ], 134 | "text_color_disabled": [ 135 | "gray60", 136 | "gray45" 137 | ] 138 | }, 139 | "CTkRadiobutton": { 140 | "corner_radius": 1000, 141 | "border_width_checked": 6, 142 | "border_width_unchecked": 3, 143 | "fg_color": [ 144 | "#c01d6f", 145 | "#8b0648" 146 | ], 147 | "border_color": [ 148 | "grey27", 149 | "grey60" 150 | ], 151 | "hover_color": [ 152 | "#8d1c55", 153 | "#620533" 154 | ], 155 | "text_color": [ 156 | "gray14", 157 | "gray84" 158 | ], 159 | "text_color_disabled": [ 160 | "gray60", 161 | "gray45" 162 | ] 163 | }, 164 | "CTkProgressBar": { 165 | "corner_radius": 1000, 166 | "border_width": 0, 167 | "fg_color": [ 168 | "gray60", 169 | "grey30" 170 | ], 171 | "progress_color": [ 172 | "#c01d6f", 173 | "#8b0648" 174 | ], 175 | "border_color": [ 176 | "gray", 177 | "gray" 178 | ] 179 | }, 180 | "CTkSlider": { 181 | "corner_radius": 1000, 182 | "button_corner_radius": 1000, 183 | "border_width": 6, 184 | "button_length": 0, 185 | "fg_color": [ 186 | "gray60", 187 | "grey30" 188 | ], 189 | "progress_color": [ 190 | "gray40", 191 | "gray69" 192 | ], 193 | "button_color": [ 194 | "#c01d6f", 195 | "#8b0648" 196 | ], 197 | "button_hover_color": [ 198 | "#8d1c55", 199 | "#620533" 200 | ] 201 | }, 202 | "CTkOptionMenu": { 203 | "corner_radius": 6, 204 | "fg_color": [ 205 | "#c01d6f", 206 | "#8b0648" 207 | ], 208 | "button_color": [ 209 | "#8d1c55", 210 | "#620533" 211 | ], 212 | "button_hover_color": [ 213 | "#741444", 214 | "#4e1331" 215 | ], 216 | "text_color": [ 217 | "grey89", 218 | "grey89" 219 | ], 220 | "text_color_disabled": [ 221 | "gray74", 222 | "gray60" 223 | ] 224 | }, 225 | "CTkComboBox": { 226 | "corner_radius": 6, 227 | "border_width": 2, 228 | "fg_color": [ 229 | "gray98", 230 | "gray21" 231 | ], 232 | "border_color": [ 233 | "gray61", 234 | "grey35" 235 | ], 236 | "button_color": [ 237 | "gray61", 238 | "grey35" 239 | ], 240 | "button_hover_color": [ 241 | "grey44", 242 | "grey52" 243 | ], 244 | "text_color": [ 245 | "gray14", 246 | "gray84" 247 | ], 248 | "text_color_disabled": [ 249 | "gray50", 250 | "gray45" 251 | ] 252 | }, 253 | "CTkScrollbar": { 254 | "corner_radius": 1000, 255 | "border_spacing": 4, 256 | "fg_color": "transparent", 257 | "button_color": [ 258 | "gray55", 259 | "gray41" 260 | ], 261 | "button_hover_color": [ 262 | "gray40", 263 | "gray53" 264 | ] 265 | }, 266 | "CTkSegmentedButton": { 267 | "corner_radius": 6, 268 | "border_width": 2, 269 | "fg_color": [ 270 | "gray61", 271 | "gray29" 272 | ], 273 | "selected_color": [ 274 | "#c01d6f", 275 | "#8b0648" 276 | ], 277 | "selected_hover_color": [ 278 | "#8d1c55", 279 | "#620533" 280 | ], 281 | "unselected_color": [ 282 | "gray61", 283 | "gray29" 284 | ], 285 | "unselected_hover_color": [ 286 | "gray70", 287 | "gray41" 288 | ], 289 | "text_color": [ 290 | "grey89", 291 | "grey89" 292 | ], 293 | "text_color_disabled": [ 294 | "gray74", 295 | "gray60" 296 | ] 297 | }, 298 | "CTkTextbox": { 299 | "corner_radius": 6, 300 | "border_width": 0, 301 | "fg_color": [ 302 | "gray100", 303 | "gray20" 304 | ], 305 | "border_color": [ 306 | "gray61", 307 | "grey35" 308 | ], 309 | "text_color": [ 310 | "gray14", 311 | "gray84" 312 | ], 313 | "scrollbar_button_color": [ 314 | "gray55", 315 | "gray41" 316 | ], 317 | "scrollbar_button_hover_color": [ 318 | "gray40", 319 | "gray53" 320 | ] 321 | }, 322 | "CTkScrollableFrame": { 323 | "label_fg_color": [ 324 | "gray80", 325 | "gray21" 326 | ] 327 | }, 328 | "DropdownMenu": { 329 | "fg_color": [ 330 | "gray90", 331 | "gray20" 332 | ], 333 | "hover_color": [ 334 | "gray75", 335 | "gray28" 336 | ], 337 | "text_color": [ 338 | "gray14", 339 | "gray84" 340 | ] 341 | }, 342 | "CTkFont": { 343 | "macOS": { 344 | "family": "SF Display", 345 | "size": 13, 346 | "weight": "normal" 347 | }, 348 | "Windows": { 349 | "family": "Roboto", 350 | "size": 13, 351 | "weight": "normal" 352 | }, 353 | "Linux": { 354 | "family": "Roboto", 355 | "size": 13, 356 | "weight": "normal" 357 | } 358 | } 359 | } -------------------------------------------------------------------------------- /assets/themes/red.json: -------------------------------------------------------------------------------- 1 | { 2 | "CTk": { 3 | "fg_color": [ 4 | "gray95", 5 | "gray10" 6 | ] 7 | }, 8 | "CTkToplevel": { 9 | "fg_color": [ 10 | "gray95", 11 | "gray10" 12 | ] 13 | }, 14 | "CTkFrame": { 15 | "corner_radius": 6, 16 | "border_width": 0, 17 | "fg_color": [ 18 | "gray90", 19 | "gray13" 20 | ], 21 | "top_fg_color": [ 22 | "gray85", 23 | "gray16" 24 | ], 25 | "border_color": [ 26 | "gray65", 27 | "gray28" 28 | ] 29 | }, 30 | "CTkButton": { 31 | "corner_radius": 6, 32 | "border_width": 0, 33 | "fg_color": [ 34 | "#ca2f2f", 35 | "#941212" 36 | ], 37 | "hover_color": [ 38 | "#972a2a", 39 | "#6b0e0e" 40 | ], 41 | "border_color": [ 42 | "grey27", 43 | "grey60" 44 | ], 45 | "text_color": [ 46 | "grey89", 47 | "grey89" 48 | ], 49 | "text_color_disabled": [ 50 | "gray74", 51 | "gray60" 52 | ] 53 | }, 54 | "CTkLabel": { 55 | "corner_radius": 0, 56 | "fg_color": "transparent", 57 | "text_color": [ 58 | "gray14", 59 | "gray84" 60 | ] 61 | }, 62 | "CTkEntry": { 63 | "corner_radius": 6, 64 | "border_width": 2, 65 | "fg_color": [ 66 | "gray98", 67 | "gray21" 68 | ], 69 | "border_color": [ 70 | "gray61", 71 | "grey35" 72 | ], 73 | "text_color": [ 74 | "gray14", 75 | "gray84" 76 | ], 77 | "placeholder_text_color": [ 78 | "gray52", 79 | "gray62" 80 | ] 81 | }, 82 | "CTkCheckbox": { 83 | "corner_radius": 6, 84 | "border_width": 3, 85 | "fg_color": [ 86 | "#ca2f2f", 87 | "#941212" 88 | ], 89 | "border_color": [ 90 | "grey27", 91 | "grey60" 92 | ], 93 | "hover_color": [ 94 | "#972a2a", 95 | "#6b0e0e" 96 | ], 97 | "checkmark_color": [ 98 | "grey89", 99 | "gray90" 100 | ], 101 | "text_color": [ 102 | "gray14", 103 | "gray84" 104 | ], 105 | "text_color_disabled": [ 106 | "gray60", 107 | "gray45" 108 | ] 109 | }, 110 | "CTkSwitch": { 111 | "corner_radius": 1000, 112 | "border_width": 3, 113 | "button_length": 0, 114 | "fg_Color": [ 115 | "gray60", 116 | "grey30" 117 | ], 118 | "progress_color": [ 119 | "#ca2f2f", 120 | "#941212" 121 | ], 122 | "button_color": [ 123 | "gray36", 124 | "gray85" 125 | ], 126 | "button_hover_color": [ 127 | "gray20", 128 | "gray100" 129 | ], 130 | "text_color": [ 131 | "gray14", 132 | "gray84" 133 | ], 134 | "text_color_disabled": [ 135 | "gray60", 136 | "gray45" 137 | ] 138 | }, 139 | "CTkRadiobutton": { 140 | "corner_radius": 1000, 141 | "border_width_checked": 6, 142 | "border_width_unchecked": 3, 143 | "fg_color": [ 144 | "#ca2f2f", 145 | "#941212" 146 | ], 147 | "border_color": [ 148 | "grey27", 149 | "grey60" 150 | ], 151 | "hover_color": [ 152 | "#972a2a", 153 | "#6b0e0e" 154 | ], 155 | "text_color": [ 156 | "gray14", 157 | "gray84" 158 | ], 159 | "text_color_disabled": [ 160 | "gray60", 161 | "gray45" 162 | ] 163 | }, 164 | "CTkProgressBar": { 165 | "corner_radius": 1000, 166 | "border_width": 0, 167 | "fg_color": [ 168 | "gray60", 169 | "grey30" 170 | ], 171 | "progress_color": [ 172 | "#ca2f2f", 173 | "#941212" 174 | ], 175 | "border_color": [ 176 | "gray", 177 | "gray" 178 | ] 179 | }, 180 | "CTkSlider": { 181 | "corner_radius": 1000, 182 | "button_corner_radius": 1000, 183 | "border_width": 6, 184 | "button_length": 0, 185 | "fg_color": [ 186 | "gray60", 187 | "grey30" 188 | ], 189 | "progress_color": [ 190 | "gray40", 191 | "gray69" 192 | ], 193 | "button_color": [ 194 | "#ca2f2f", 195 | "#941212" 196 | ], 197 | "button_hover_color": [ 198 | "#972a2a", 199 | "#6b0e0e" 200 | ] 201 | }, 202 | "CTkOptionMenu": { 203 | "corner_radius": 6, 204 | "fg_color": [ 205 | "#ca2f2f", 206 | "#941212" 207 | ], 208 | "button_color": [ 209 | "#972a2a", 210 | "#6b0e0e" 211 | ], 212 | "button_hover_color": [ 213 | "#7d2020", 214 | "#571d1d" 215 | ], 216 | "text_color": [ 217 | "grey89", 218 | "grey89" 219 | ], 220 | "text_color_disabled": [ 221 | "gray74", 222 | "gray60" 223 | ] 224 | }, 225 | "CTkComboBox": { 226 | "corner_radius": 6, 227 | "border_width": 2, 228 | "fg_color": [ 229 | "gray98", 230 | "gray21" 231 | ], 232 | "border_color": [ 233 | "gray61", 234 | "grey35" 235 | ], 236 | "button_color": [ 237 | "gray61", 238 | "grey35" 239 | ], 240 | "button_hover_color": [ 241 | "grey44", 242 | "grey52" 243 | ], 244 | "text_color": [ 245 | "gray14", 246 | "gray84" 247 | ], 248 | "text_color_disabled": [ 249 | "gray50", 250 | "gray45" 251 | ] 252 | }, 253 | "CTkScrollbar": { 254 | "corner_radius": 1000, 255 | "border_spacing": 4, 256 | "fg_color": "transparent", 257 | "button_color": [ 258 | "gray55", 259 | "gray41" 260 | ], 261 | "button_hover_color": [ 262 | "gray40", 263 | "gray53" 264 | ] 265 | }, 266 | "CTkSegmentedButton": { 267 | "corner_radius": 6, 268 | "border_width": 2, 269 | "fg_color": [ 270 | "gray61", 271 | "gray29" 272 | ], 273 | "selected_color": [ 274 | "#ca2f2f", 275 | "#941212" 276 | ], 277 | "selected_hover_color": [ 278 | "#972a2a", 279 | "#6b0e0e" 280 | ], 281 | "unselected_color": [ 282 | "gray61", 283 | "gray29" 284 | ], 285 | "unselected_hover_color": [ 286 | "gray70", 287 | "gray41" 288 | ], 289 | "text_color": [ 290 | "grey89", 291 | "grey89" 292 | ], 293 | "text_color_disabled": [ 294 | "gray74", 295 | "gray60" 296 | ] 297 | }, 298 | "CTkTextbox": { 299 | "corner_radius": 6, 300 | "border_width": 0, 301 | "fg_color": [ 302 | "gray100", 303 | "gray20" 304 | ], 305 | "border_color": [ 306 | "gray61", 307 | "grey35" 308 | ], 309 | "text_color": [ 310 | "gray14", 311 | "gray84" 312 | ], 313 | "scrollbar_button_color": [ 314 | "gray55", 315 | "gray41" 316 | ], 317 | "scrollbar_button_hover_color": [ 318 | "gray40", 319 | "gray53" 320 | ] 321 | }, 322 | "CTkScrollableFrame": { 323 | "label_fg_color": [ 324 | "gray80", 325 | "gray21" 326 | ] 327 | }, 328 | "DropdownMenu": { 329 | "fg_color": [ 330 | "gray90", 331 | "gray20" 332 | ], 333 | "hover_color": [ 334 | "gray75", 335 | "gray28" 336 | ], 337 | "text_color": [ 338 | "gray14", 339 | "gray84" 340 | ] 341 | }, 342 | "CTkFont": { 343 | "macOS": { 344 | "family": "SF Display", 345 | "size": 13, 346 | "weight": "normal" 347 | }, 348 | "Windows": { 349 | "family": "Roboto", 350 | "size": 13, 351 | "weight": "normal" 352 | }, 353 | "Linux": { 354 | "family": "Roboto", 355 | "size": 13, 356 | "weight": "normal" 357 | } 358 | } 359 | } -------------------------------------------------------------------------------- /assets/themes/torquoise.json: -------------------------------------------------------------------------------- 1 | { 2 | "CTk": { 3 | "fg_color": [ 4 | "gray95", 5 | "gray10" 6 | ] 7 | }, 8 | "CTkToplevel": { 9 | "fg_color": [ 10 | "gray95", 11 | "gray10" 12 | ] 13 | }, 14 | "CTkFrame": { 15 | "corner_radius": 6, 16 | "border_width": 0, 17 | "fg_color": [ 18 | "gray90", 19 | "gray13" 20 | ], 21 | "top_fg_color": [ 22 | "gray85", 23 | "gray16" 24 | ], 25 | "border_color": [ 26 | "gray65", 27 | "gray28" 28 | ] 29 | }, 30 | "CTkButton": { 31 | "corner_radius": 6, 32 | "border_width": 0, 33 | "fg_color": [ 34 | "#2fcaca", 35 | "#129494" 36 | ], 37 | "hover_color": [ 38 | "#2a9797", 39 | "#0e6b6b" 40 | ], 41 | "border_color": [ 42 | "grey27", 43 | "grey60" 44 | ], 45 | "text_color": [ 46 | "grey89", 47 | "grey89" 48 | ], 49 | "text_color_disabled": [ 50 | "gray74", 51 | "gray60" 52 | ] 53 | }, 54 | "CTkLabel": { 55 | "corner_radius": 0, 56 | "fg_color": "transparent", 57 | "text_color": [ 58 | "gray14", 59 | "gray84" 60 | ] 61 | }, 62 | "CTkEntry": { 63 | "corner_radius": 6, 64 | "border_width": 2, 65 | "fg_color": [ 66 | "gray98", 67 | "gray21" 68 | ], 69 | "border_color": [ 70 | "gray61", 71 | "grey35" 72 | ], 73 | "text_color": [ 74 | "gray14", 75 | "gray84" 76 | ], 77 | "placeholder_text_color": [ 78 | "gray52", 79 | "gray62" 80 | ] 81 | }, 82 | "CTkCheckbox": { 83 | "corner_radius": 6, 84 | "border_width": 3, 85 | "fg_color": [ 86 | "#2fcaca", 87 | "#129494" 88 | ], 89 | "border_color": [ 90 | "grey27", 91 | "grey60" 92 | ], 93 | "hover_color": [ 94 | "#2a9797", 95 | "#0e6b6b" 96 | ], 97 | "checkmark_color": [ 98 | "grey89", 99 | "gray90" 100 | ], 101 | "text_color": [ 102 | "gray14", 103 | "gray84" 104 | ], 105 | "text_color_disabled": [ 106 | "gray60", 107 | "gray45" 108 | ] 109 | }, 110 | "CTkSwitch": { 111 | "corner_radius": 1000, 112 | "border_width": 3, 113 | "button_length": 0, 114 | "fg_Color": [ 115 | "gray60", 116 | "grey30" 117 | ], 118 | "progress_color": [ 119 | "#2fcaca", 120 | "#129494" 121 | ], 122 | "button_color": [ 123 | "gray36", 124 | "gray85" 125 | ], 126 | "button_hover_color": [ 127 | "gray20", 128 | "gray100" 129 | ], 130 | "text_color": [ 131 | "gray14", 132 | "gray84" 133 | ], 134 | "text_color_disabled": [ 135 | "gray60", 136 | "gray45" 137 | ] 138 | }, 139 | "CTkRadiobutton": { 140 | "corner_radius": 1000, 141 | "border_width_checked": 6, 142 | "border_width_unchecked": 3, 143 | "fg_color": [ 144 | "#2fcaca", 145 | "#129494" 146 | ], 147 | "border_color": [ 148 | "grey27", 149 | "grey60" 150 | ], 151 | "hover_color": [ 152 | "#2a9797", 153 | "#0e6b6b" 154 | ], 155 | "text_color": [ 156 | "gray14", 157 | "gray84" 158 | ], 159 | "text_color_disabled": [ 160 | "gray60", 161 | "gray45" 162 | ] 163 | }, 164 | "CTkProgressBar": { 165 | "corner_radius": 1000, 166 | "border_width": 0, 167 | "fg_color": [ 168 | "gray60", 169 | "grey30" 170 | ], 171 | "progress_color": [ 172 | "#2fcaca", 173 | "#129494" 174 | ], 175 | "border_color": [ 176 | "gray", 177 | "gray" 178 | ] 179 | }, 180 | "CTkSlider": { 181 | "corner_radius": 1000, 182 | "button_corner_radius": 1000, 183 | "border_width": 6, 184 | "button_length": 0, 185 | "fg_color": [ 186 | "gray60", 187 | "grey30" 188 | ], 189 | "progress_color": [ 190 | "gray40", 191 | "gray69" 192 | ], 193 | "button_color": [ 194 | "#2fcaca", 195 | "#129494" 196 | ], 197 | "button_hover_color": [ 198 | "#2a9797", 199 | "#0e6b6b" 200 | ] 201 | }, 202 | "CTkOptionMenu": { 203 | "corner_radius": 6, 204 | "fg_color": [ 205 | "#2fcaca", 206 | "#129494" 207 | ], 208 | "button_color": [ 209 | "#2a9797", 210 | "#0e6b6b" 211 | ], 212 | "button_hover_color": [ 213 | "#207d7d", 214 | "#1d5757" 215 | ], 216 | "text_color": [ 217 | "grey89", 218 | "grey89" 219 | ], 220 | "text_color_disabled": [ 221 | "gray74", 222 | "gray60" 223 | ] 224 | }, 225 | "CTkComboBox": { 226 | "corner_radius": 6, 227 | "border_width": 2, 228 | "fg_color": [ 229 | "gray98", 230 | "gray21" 231 | ], 232 | "border_color": [ 233 | "gray61", 234 | "grey35" 235 | ], 236 | "button_color": [ 237 | "gray61", 238 | "grey35" 239 | ], 240 | "button_hover_color": [ 241 | "grey44", 242 | "grey52" 243 | ], 244 | "text_color": [ 245 | "gray14", 246 | "gray84" 247 | ], 248 | "text_color_disabled": [ 249 | "gray50", 250 | "gray45" 251 | ] 252 | }, 253 | "CTkScrollbar": { 254 | "corner_radius": 1000, 255 | "border_spacing": 4, 256 | "fg_color": "transparent", 257 | "button_color": [ 258 | "gray55", 259 | "gray41" 260 | ], 261 | "button_hover_color": [ 262 | "gray40", 263 | "gray53" 264 | ] 265 | }, 266 | "CTkSegmentedButton": { 267 | "corner_radius": 6, 268 | "border_width": 2, 269 | "fg_color": [ 270 | "gray61", 271 | "gray29" 272 | ], 273 | "selected_color": [ 274 | "#2fcaca", 275 | "#129494" 276 | ], 277 | "selected_hover_color": [ 278 | "#2a9797", 279 | "#0e6b6b" 280 | ], 281 | "unselected_color": [ 282 | "gray61", 283 | "gray29" 284 | ], 285 | "unselected_hover_color": [ 286 | "gray70", 287 | "gray41" 288 | ], 289 | "text_color": [ 290 | "grey89", 291 | "grey89" 292 | ], 293 | "text_color_disabled": [ 294 | "gray74", 295 | "gray60" 296 | ] 297 | }, 298 | "CTkTextbox": { 299 | "corner_radius": 6, 300 | "border_width": 0, 301 | "fg_color": [ 302 | "gray100", 303 | "gray20" 304 | ], 305 | "border_color": [ 306 | "gray61", 307 | "grey35" 308 | ], 309 | "text_color": [ 310 | "gray14", 311 | "gray84" 312 | ], 313 | "scrollbar_button_color": [ 314 | "gray55", 315 | "gray41" 316 | ], 317 | "scrollbar_button_hover_color": [ 318 | "gray40", 319 | "gray53" 320 | ] 321 | }, 322 | "CTkScrollableFrame": { 323 | "label_fg_color": [ 324 | "gray80", 325 | "gray21" 326 | ] 327 | }, 328 | "DropdownMenu": { 329 | "fg_color": [ 330 | "gray90", 331 | "gray20" 332 | ], 333 | "hover_color": [ 334 | "gray75", 335 | "gray28" 336 | ], 337 | "text_color": [ 338 | "gray14", 339 | "gray84" 340 | ] 341 | }, 342 | "CTkFont": { 343 | "macOS": { 344 | "family": "SF Display", 345 | "size": 13, 346 | "weight": "normal" 347 | }, 348 | "Windows": { 349 | "family": "Roboto", 350 | "size": 13, 351 | "weight": "normal" 352 | }, 353 | "Linux": { 354 | "family": "Roboto", 355 | "size": 13, 356 | "weight": "normal" 357 | } 358 | } 359 | } -------------------------------------------------------------------------------- /assets/themes/purple.json: -------------------------------------------------------------------------------- 1 | { 2 | "CTk": { 3 | "fg_color": [ 4 | "gray95", 5 | "gray10" 6 | ] 7 | }, 8 | "CTkToplevel": { 9 | "fg_color": [ 10 | "gray95", 11 | "gray10" 12 | ] 13 | }, 14 | "CTkFrame": { 15 | "corner_radius": 6, 16 | "border_width": 0, 17 | "fg_color": [ 18 | "gray90", 19 | "gray13" 20 | ], 21 | "top_fg_color": [ 22 | "gray85", 23 | "gray16" 24 | ], 25 | "border_color": [ 26 | "gray65", 27 | "gray28" 28 | ] 29 | }, 30 | "CTkButton": { 31 | "corner_radius": 6, 32 | "border_width": 0, 33 | "fg_color": [ 34 | "#cb45e5", 35 | "#9821b0" 36 | ], 37 | "hover_color": [ 38 | "#9f3eb2", 39 | "#631773" 40 | ], 41 | "border_color": [ 42 | "grey27", 43 | "grey60" 44 | ], 45 | "text_color": [ 46 | "#eadded", 47 | "#eadded" 48 | ], 49 | "text_color_disabled": [ 50 | "gray74", 51 | "gray60" 52 | ] 53 | }, 54 | "CTkLabel": { 55 | "corner_radius": 0, 56 | "fg_color": "transparent", 57 | "text_color": [ 58 | "gray14", 59 | "gray84" 60 | ] 61 | }, 62 | "CTkEntry": { 63 | "corner_radius": 6, 64 | "border_width": 2, 65 | "fg_color": [ 66 | "gray98", 67 | "gray21" 68 | ], 69 | "border_color": [ 70 | "gray61", 71 | "grey35" 72 | ], 73 | "text_color": [ 74 | "gray14", 75 | "gray84" 76 | ], 77 | "placeholder_text_color": [ 78 | "gray52", 79 | "gray62" 80 | ] 81 | }, 82 | "CTkCheckbox": { 83 | "corner_radius": 6, 84 | "border_width": 3, 85 | "fg_color": [ 86 | "#cb45e5", 87 | "#9821b0" 88 | ], 89 | "border_color": [ 90 | "grey27", 91 | "grey60" 92 | ], 93 | "hover_color": [ 94 | "#9f3eb2", 95 | "#631773" 96 | ], 97 | "checkmark_color": [ 98 | "#eadded", 99 | "gray90" 100 | ], 101 | "text_color": [ 102 | "gray14", 103 | "gray84" 104 | ], 105 | "text_color_disabled": [ 106 | "gray60", 107 | "gray45" 108 | ] 109 | }, 110 | "CTkSwitch": { 111 | "corner_radius": 1000, 112 | "border_width": 3, 113 | "button_length": 0, 114 | "fg_Color": [ 115 | "gray60", 116 | "grey30" 117 | ], 118 | "progress_color": [ 119 | "#cb45e5", 120 | "#9821b0" 121 | ], 122 | "button_color": [ 123 | "gray36", 124 | "gray85" 125 | ], 126 | "button_hover_color": [ 127 | "gray20", 128 | "gray100" 129 | ], 130 | "text_color": [ 131 | "gray14", 132 | "gray84" 133 | ], 134 | "text_color_disabled": [ 135 | "gray60", 136 | "gray45" 137 | ] 138 | }, 139 | "CTkRadiobutton": { 140 | "corner_radius": 1000, 141 | "border_width_checked": 6, 142 | "border_width_unchecked": 3, 143 | "fg_color": [ 144 | "#cb45e5", 145 | "#9821b0" 146 | ], 147 | "border_color": [ 148 | "grey27", 149 | "grey60" 150 | ], 151 | "hover_color": [ 152 | "#9f3eb2", 153 | "#631773" 154 | ], 155 | "text_color": [ 156 | "gray14", 157 | "gray84" 158 | ], 159 | "text_color_disabled": [ 160 | "gray60", 161 | "gray45" 162 | ] 163 | }, 164 | "CTkProgressBar": { 165 | "corner_radius": 1000, 166 | "border_width": 0, 167 | "fg_color": [ 168 | "gray60", 169 | "grey30" 170 | ], 171 | "progress_color": [ 172 | "#cb45e5", 173 | "#9821b0" 174 | ], 175 | "border_color": [ 176 | "gray", 177 | "gray" 178 | ] 179 | }, 180 | "CTkSlider": { 181 | "corner_radius": 1000, 182 | "button_corner_radius": 1000, 183 | "border_width": 6, 184 | "button_length": 0, 185 | "fg_color": [ 186 | "gray60", 187 | "grey30" 188 | ], 189 | "progress_color": [ 190 | "gray40", 191 | "gray69" 192 | ], 193 | "button_color": [ 194 | "#cb45e5", 195 | "#9821b0" 196 | ], 197 | "button_hover_color": [ 198 | "#9f3eb2", 199 | "#631773" 200 | ] 201 | }, 202 | "CTkOptionMenu": { 203 | "corner_radius": 6, 204 | "fg_color": [ 205 | "#cb45e5", 206 | "#9821b0" 207 | ], 208 | "button_color": [ 209 | "#9f3eb2", 210 | "#631773" 211 | ], 212 | "button_hover_color": [ 213 | "#883199", 214 | "#683473" 215 | ], 216 | "text_color": [ 217 | "#eadded", 218 | "#eadded" 219 | ], 220 | "text_color_disabled": [ 221 | "gray74", 222 | "gray60" 223 | ] 224 | }, 225 | "CTkComboBox": { 226 | "corner_radius": 6, 227 | "border_width": 2, 228 | "fg_color": [ 229 | "gray98", 230 | "gray21" 231 | ], 232 | "border_color": [ 233 | "gray61", 234 | "grey35" 235 | ], 236 | "button_color": [ 237 | "gray61", 238 | "grey35" 239 | ], 240 | "button_hover_color": [ 241 | "grey44", 242 | "grey52" 243 | ], 244 | "text_color": [ 245 | "gray14", 246 | "gray84" 247 | ], 248 | "text_color_disabled": [ 249 | "gray50", 250 | "gray45" 251 | ] 252 | }, 253 | "CTkScrollbar": { 254 | "corner_radius": 1000, 255 | "border_spacing": 4, 256 | "fg_color": "transparent", 257 | "button_color": [ 258 | "gray55", 259 | "gray41" 260 | ], 261 | "button_hover_color": [ 262 | "gray40", 263 | "gray53" 264 | ] 265 | }, 266 | "CTkSegmentedButton": { 267 | "corner_radius": 6, 268 | "border_width": 2, 269 | "fg_color": [ 270 | "gray61", 271 | "gray29" 272 | ], 273 | "selected_color": [ 274 | "#cb45e5", 275 | "#9821b0" 276 | ], 277 | "selected_hover_color": [ 278 | "#9f3eb2", 279 | "#631773" 280 | ], 281 | "unselected_color": [ 282 | "gray61", 283 | "gray29" 284 | ], 285 | "unselected_hover_color": [ 286 | "gray70", 287 | "gray41" 288 | ], 289 | "text_color": [ 290 | "#eadded", 291 | "#eadded" 292 | ], 293 | "text_color_disabled": [ 294 | "gray74", 295 | "gray60" 296 | ] 297 | }, 298 | "CTkTextbox": { 299 | "corner_radius": 6, 300 | "border_width": 0, 301 | "fg_color": [ 302 | "gray100", 303 | "gray20" 304 | ], 305 | "border_color": [ 306 | "gray61", 307 | "grey35" 308 | ], 309 | "text_color": [ 310 | "gray14", 311 | "gray84" 312 | ], 313 | "scrollbar_button_color": [ 314 | "gray55", 315 | "gray41" 316 | ], 317 | "scrollbar_button_hover_color": [ 318 | "gray40", 319 | "gray53" 320 | ] 321 | }, 322 | "CTkScrollableFrame": { 323 | "label_fg_color": [ 324 | "gray80", 325 | "gray21" 326 | ] 327 | }, 328 | "DropdownMenu": { 329 | "fg_color": [ 330 | "gray90", 331 | "gray20" 332 | ], 333 | "hover_color": [ 334 | "gray75", 335 | "gray28" 336 | ], 337 | "text_color": [ 338 | "gray14", 339 | "gray84" 340 | ] 341 | }, 342 | "CTkFont": { 343 | "macOS": { 344 | "family": "SF Display", 345 | "size": 13, 346 | "weight": "normal" 347 | }, 348 | "Windows": { 349 | "family": "Roboto", 350 | "size": 13, 351 | "weight": "normal" 352 | }, 353 | "Linux": { 354 | "family": "Roboto", 355 | "size": 13, 356 | "weight": "normal" 357 | } 358 | } 359 | } -------------------------------------------------------------------------------- /assets/i18n/app.en_US.yml: -------------------------------------------------------------------------------- 1 | en_US: 2 | title: DMMGamePlayer Fast Launcher 3 | language: English 4 | 5 | font: 6 | main: "Segoe UI" 7 | 8 | tab: 9 | home: Home 10 | shortcut: Shortcut 11 | account: Account 12 | setting: Setting 13 | other: Other 14 | help: Help 15 | create: Create Shortcut 16 | edit: Edit Shortcut 17 | launch_create: Fast Launch GamePlayer 18 | launch_edit: Edit Fast Launch GamePlayer 19 | account_import: Import 20 | account_edit: Edit Account 21 | import_browser: Import from Browser 22 | device: Register Device 23 | device_list: Device List 24 | 25 | home: 26 | new_version: There is a new version of DMMGamePlayer Fast Launcher available. 27 | 28 | shortcut: 29 | filename: File Name 30 | filename_tooltip: | 31 | Please give this shortcut any name you like. 32 | Using characters other than half-width alphanumeric characters may cause issues. 33 | 34 | product_id: Select product_id 35 | product_id_tooltip: | 36 | The product_id is the ID used by DMM to identify games. 37 | 38 | account_create_detail: |- 39 | Create a shortcut for fast launching DMMGamePlayer. 40 | Please select the account you created in the 'Account' tab. 41 | 42 | account_path: Select Account 43 | account_path_tooltip: |- 44 | Please select the account you created in the 'Account' tab. 45 | always_extract_from_dmm: Always extract from DMM 46 | game_args: Game Arguments 47 | game_args_tooltip: |- 48 | Specify the arguments to pass to the game. 49 | For Unity-based games, you can input something like '-screen-fullscreen 0 -screen-width 1280 -screen-height 720' to launch the game in windowed mode with a resolution of 1280x720. 50 | external_tool_path: External tool path 51 | external_tool_path_tooltip: Specify the path here if you want to use an external tool when launching the game. 52 | auto_update: Automatically update the game on launch 53 | rich_presence: Enable Discord Rich Presence 54 | 55 | create_bypass_shortcut_and_save: Create UAC Auto-Elevation Shortcut and Save Settings 56 | create_bypass_shortcut_and_save_tooltip: | 57 | Create a shortcut for UAC auto-elevation and save settings. 58 | Select this option if the game requires administrator privileges to run. 59 | 60 | create_uac_shortcut_and_save: Create UAC Manual-Elevation Shortcut and Save Settings 61 | create_uac_shortcut_and_save_tooltip: |- 62 | Create and save a shortcut for UAC manual-elevation. 63 | Select this option if the game requires administrator privileges to run. 64 | Since the UAC dialog will appear every time the game is launched, manual elevation is required. 65 | It is executed in the same process, enhancing integration with other software. 66 | 67 | create_shortcut_and_save: Create Shortcut and Save Settings 68 | create_shortcut_and_save_tooltip: | 69 | Create a shortcut and save settings. 70 | Select this option if the game does not require administrator privileges to run. 71 | Selecting this option for a game that requires administrator privileges will result in an error. 72 | 73 | save_only: Save Settings Only 74 | save_only_tooltip: | 75 | Save settings without creating a shortcut. 76 | To launch the saved settings from a shortcut, execute the command line: 77 | `DMMGamePlayerFastLauncher.exe [File Name] --type game`. 78 | 79 | add_detail: Create a shortcut and settings for fast launch. 80 | edit_detail: Edit the settings of the fast launch shortcut. 81 | 82 | file_select: Select File 83 | 84 | product_id_not_entered: product_id is not entered. 85 | filename_not_entered: File name is not entered. 86 | account_path_not_entered: Account not selected. 87 | game_info_error: Failed to retrieve game information. 88 | administrator_error: Administrator privileges are required. Please create a 'UAC auto-elevated shortcut'. 89 | file_not_selected: No file selected. 90 | save_success: Saved successfully. 91 | delete: Delete 92 | 93 | account_edit_detail: Edit settings for fast launching DMMGamePlayer. 94 | dgp_args: DMMGamePlayer Arguments 95 | dgp_args_tooltip: |- 96 | Specify the arguments to pass to DMMGamePlayer. 97 | For example, entering 'dmmgameplayer://play/GCL/priconner/cl/win' will launch 'Princess Connect! Re:Dive' in DMMGamePlayer. 98 | Leave it blank to launch only DMMGamePlayer. 99 | unity_command_line_args: Learn more about Unity command line arguments 100 | unity_command_line_args_tooltip: |- 101 | Opens a site about Unity command-line arguments. 102 | This may be helpful if the game you are launching is made with Unity. 103 | unity_command_line_args_link: https://docs.unity3d.com/Manual/PlayerCommandLineArguments.html 104 | 105 | account: 106 | import_detail: |- 107 | Import your DMMGamePlayer account information into DMMGamePlayerFastLauncher. 108 | This operation is deprecated. It is recommended to import from the browser. 109 | 110 | filename_tooltip: |- 111 | Please provide any name for this account. 112 | Using non-alphanumeric characters may cause problems. 113 | import: Import 114 | filename: File Name 115 | filename_not_entered: File name is not entered. 116 | filename_already_exists: That file name already exists. 117 | filename_reserved: The filename is reserved. 118 | import_error: Import failed. 119 | import_success: Import succeeded. 120 | 121 | file_select: Select File 122 | 123 | import_browser_detail: Launch the browser to import account information from DMMGamePlayer. 124 | browser_select: Select Browser 125 | auto_refresh: Automatically refresh login session 126 | browser_select_tooltip: |- 127 | Choose your preferred browser. 128 | The specified browser will launch and open the login screen. 129 | browser_not_selected: Browser not selected. 130 | import_browser: Import 131 | import_browser_success: Imported successfully. 132 | 133 | edit_detail: |- 134 | Edit your account information. 135 | This is an advanced operation. 136 | 137 | save: Save 138 | delete: Delete 139 | save_success: Saved successfully. 140 | 141 | device_detail: |- 142 | Enter the 'Device Authentication Code' sent to your email address. 143 | 144 | send_auth_code: Send Authentication Code 145 | auth: Authenticate 146 | 147 | hardware_name: Device Name 148 | auth_code: Device Authentication Code 149 | auth_code_tooltip: |- 150 | Enter the alphanumeric code provided in your email. 151 | send_auth_code_success: Authentication code sent successfully. 152 | auth_success: Authentication succeeded. 153 | device_list_success: Device list retrieved successfully. 154 | delete_success: Deleted successfully. 155 | 156 | device_registrations: "Registered Devices: %{count} / %{limit}" 157 | 158 | setting: 159 | save: Save 160 | reset_all_settings: Reset All Settings 161 | confirm_reset: Are you sure you want to reset all settings? 162 | save_success: Settings saved successfully. 163 | dmm_game_player_program_folder: DMMGamePlayer Program Folder 164 | dmm_game_player_data_folder: DMMGamePlayer Data Folder 165 | lang: Language 166 | theme: Theme 167 | appearance: Appearance 168 | 169 | font_preset: Font Preset 170 | font_preset_tooltip: |- 171 | Select a font preset. 172 | i18n: Optimized fonts for each language will be selected. 173 | os: The default font of the operating system will be selected. 174 | theme: Fonts matching the theme will be selected. 175 | 176 | proxy_all: Proxy 177 | proxy_all_tooltip: |- 178 | Set up the game's proxy. 179 | Example: http://127.0.0.1:80 180 | https://127.0.0.1:443 181 | socks5://127.0.0.1:1080 182 | socks5://user:pass@127.0.0.1:1080 183 | 184 | dmm_proxy_all: DMM Proxy 185 | dmm_proxy_all_tooltip: |- 186 | Set up DMM's proxy. 187 | Example: http://127.0.0.1:80 188 | https://127.0.0.1:443 189 | socks5://127.0.0.1:1080 190 | socks5://user:pass@127.0.0.1:1080 191 | 192 | window_scaling: Window Scaling 193 | debug_window: Show Debug Window 194 | output_logfile: Log to File 195 | mask_token: Hide tokens on the log 196 | 197 | device_detail: |- 198 | Configure device information for device authentication. 199 | These values do not need to be accurate. 200 | 201 | mac_address: MAC Address 202 | hdd_serial: HDD Serial 203 | motherboard: Motherboard ID 204 | user_os: OS 205 | 206 | other_detail: Edit tool settings in a text editor. 207 | open_save_folder: Open Settings Folder 208 | 209 | help: 210 | coop_in_develop: Contribute to Development 211 | donations_to_developer: Donate to Developer 212 | bug_report: Report a Bug 213 | 214 | launch: 215 | export_error: Export failed. 216 | import_error: Import failed. 217 | 218 | admin_error: Administrator privileges required. 219 | 220 | component: 221 | required: This field is required. 222 | required_symbol: "*" 223 | reference: Reference 224 | 225 | "yes": "Yes" 226 | "no": "No" 227 | 228 | download: Downloading... 229 | 230 | toast: 231 | report: Report 232 | copy_to_clipboard: "Copy to Clipboard" 233 | details: Details 234 | 235 | lib: 236 | dmm_already_running: DMMGamePlayer is already running, so it could not be launched. 237 | 238 | utils: 239 | file_exists: That file already exists. 240 | -------------------------------------------------------------------------------- /DMMGamePlayerFastLauncher/launch.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import subprocess 3 | import sys 4 | import time 5 | import traceback 6 | from base64 import b64encode 7 | from pathlib import Path 8 | from typing import Callable 9 | 10 | import customtkinter as ctk 11 | import i18n 12 | import psutil 13 | from component.component import CTkProgressWindow 14 | from customtkinter import CTk 15 | from lib.DGPSessionWrap import DgpSessionWrap 16 | from lib.discord import start_rich_presence 17 | from lib.process_manager import ProcessIdManager, ProcessManager 18 | from lib.thread import threading_wrapper 19 | from lib.toast import ErrorWindow 20 | from models.setting_data import AppConfig 21 | from models.shortcut_data import BrowserConfigData, LauncherShortcutData, ShortcutData 22 | from static.config import DataPathConfig 23 | from static.constant import Constant 24 | from static.env import Env 25 | from tab.home import HomeTab 26 | from utils.utils import get_driver, login_driver 27 | 28 | 29 | class GameLauncher(CTk): 30 | loder: Callable 31 | 32 | def __init__(self, loder): 33 | super().__init__() 34 | 35 | self.title("DMMGamePlayer Fast Launcher") 36 | self.geometry("900x600") 37 | self.withdraw() 38 | loder(self) 39 | 40 | def create(self): 41 | HomeTab(self).create().pack(expand=True, fill=ctk.BOTH) 42 | return self 43 | 44 | @threading_wrapper 45 | def thread(self, id: str, kill: bool = False, force_non_uac: bool = False): 46 | try: 47 | self.launch(id, kill, force_non_uac) 48 | self.quit() 49 | except Exception as e: 50 | if Env.DEVELOP: 51 | self.iconify() 52 | raise 53 | else: 54 | self.iconify() 55 | ErrorWindow(self, str(e), traceback.format_exc(), quit=True).create() 56 | 57 | def launch(self, id: str, kill: bool = False, force_non_uac: bool = False): 58 | path = DataPathConfig.SHORTCUT.joinpath(id).with_suffix(".json") 59 | data = ShortcutData.from_path(path) 60 | 61 | if data.account_path.get() == Constant.ALWAYS_EXTRACT_FROM_DMM: 62 | session = DgpSessionWrap.read_dgp() 63 | else: 64 | account_path = DataPathConfig.ACCOUNT.joinpath(data.account_path.get()).with_suffix(".bytes") 65 | browser_config_path = DataPathConfig.BROWSER_CONFIG.joinpath(data.account_path.get()).with_suffix(".json") 66 | session = DgpSessionWrap.read_cookies(account_path) 67 | if browser_config_path.exists(): 68 | browser_config = BrowserConfigData.from_path(browser_config_path) 69 | profile_path = DataPathConfig.BROWSER_PROFILE.joinpath(browser_config.profile_name.get()).absolute() 70 | userdata = session.post_dgp(DgpSessionWrap.USER_INFO).json() 71 | if userdata["result_code"] != 100: 72 | res = session.post_dgp(DgpSessionWrap.LOGIN_URL, json={"prompt": ""}).json() 73 | if res["result_code"] != 100: 74 | raise Exception(res["error"]) 75 | driver = get_driver(browser_config.browser.get(), profile_path) 76 | code = login_driver(res["data"]["url"], driver) 77 | driver.quit() 78 | res = session.post_dgp(DgpSessionWrap.ACCESS_TOKEN, json={"code": code}).json() 79 | if res["result_code"] != 100: 80 | raise Exception(res["error"]) 81 | session.actauth = {"accessToken": res["data"]["access_token"]} 82 | session.write_bytes(str(account_path)) 83 | 84 | dgp_config = session.get_config() 85 | game = [x for x in dgp_config["contents"] if x["productId"] == data.product_id.get()][0] 86 | 87 | response = session.lunch(data.product_id.get(), game["gameType"]).json() 88 | 89 | if response["result_code"] != 100: 90 | raise Exception(response["error"]) 91 | 92 | if response["data"].get("drm_auth_token") is not None: 93 | filename = b64encode(data.product_id.get().encode("utf-8")).decode("utf-8") 94 | drm_path = Env.DMM_GAME_PLAYER_HIDDEN_FOLDER.joinpath(filename) 95 | drm_path.parent.mkdir(parents=True, exist_ok=True) 96 | with open(drm_path.absolute(), "w+") as f: 97 | f.write(response["data"]["drm_auth_token"]) 98 | 99 | game_file = Path(game["detail"]["path"]) 100 | game_path = game_file.joinpath(response["data"]["exec_file_name"]) 101 | 102 | if response["data"]["latest_version"] != game["detail"]["version"]: 103 | if data.auto_update.get(): 104 | download = session.download(response["data"]["sign"], response["data"]["file_list_url"], game_file) 105 | box = CTkProgressWindow(self).create() 106 | for progress, file in download: 107 | box.set(progress) 108 | box.destroy() 109 | game["detail"]["version"] = response["data"]["latest_version"] 110 | session.set_config(dgp_config) 111 | 112 | dmm_args = response["data"]["execute_args"].split(" ") + data.game_args.get().split(" ") 113 | game_path = str(game_path.relative_to(game_file)) 114 | game_full_path = str(game_file.joinpath(game_path)) 115 | is_admin = ProcessManager.admin_check() 116 | if kill: 117 | process = ProcessManager.run([game_path] + dmm_args, cwd=str(game_file)) 118 | try: 119 | process.wait(2) 120 | except subprocess.TimeoutExpired: 121 | for child in psutil.Process(process.pid).children(recursive=True): 122 | child.kill() 123 | else: 124 | pid_manager = ProcessIdManager() 125 | timer = time.time() 126 | if response["data"]["is_administrator"] and (not is_admin) and (not force_non_uac): 127 | process = ProcessManager.admin_run([game_path] + dmm_args, cwd=str(game_file)) 128 | game_pid = pid_manager.new_process().search(game_full_path) 129 | if data.external_tool_path.get() != "": 130 | external_tool_pid_manager = ProcessIdManager() 131 | ProcessManager.admin_run([data.external_tool_path.get()], cwd=str(game_file)) 132 | external_tool_pid = external_tool_pid_manager.new_process().search_or_none(data.external_tool_path.get()) 133 | if data.rich_presence.get(): 134 | start_rich_presence(game_pid, data.product_id.get(), response["data"]["title"]) 135 | while psutil.pid_exists(game_pid): 136 | time.sleep(1) 137 | else: 138 | process = ProcessManager.run([game_path] + dmm_args, cwd=str(game_file)) 139 | if data.external_tool_path.get() != "": 140 | external_tool_process = ProcessManager.run([data.external_tool_path.get()], cwd=str(game_file)) 141 | external_tool_pid = external_tool_process.pid 142 | if data.rich_presence.get(): 143 | start_rich_presence(process.pid, data.product_id.get(), response["data"]["title"]) 144 | assert process.stdout is not None 145 | for line in process.stdout: 146 | logging.debug(decode(line)) 147 | if time.time() - timer < 10: 148 | logging.warning("Unexpected process termination") 149 | time.sleep(10 - (time.time() - timer)) 150 | logging.warning("Restarting the process") 151 | game_pid = pid_manager.new_process().search_or_none(game_full_path) 152 | if game_pid is not None: 153 | if data.rich_presence.get(): 154 | start_rich_presence(game_pid, data.product_id.get(), response["data"]["title"]) 155 | while psutil.pid_exists(game_pid): 156 | time.sleep(1) 157 | if data.external_tool_path.get() != "" and external_tool_pid is not None: 158 | for child in psutil.Process(external_tool_pid).children(recursive=True): 159 | child.kill() 160 | 161 | 162 | class LanchLauncher(CTk): 163 | loder: Callable 164 | 165 | def __init__(self, loder): 166 | super().__init__() 167 | 168 | self.title("DMMGamePlayer Fast Launcher") 169 | self.geometry("900x600") 170 | self.withdraw() 171 | loder(self) 172 | 173 | def create(self): 174 | HomeTab(self).create().pack(expand=True, fill=ctk.BOTH) 175 | return self 176 | 177 | @threading_wrapper 178 | def thread(self, id: str): 179 | try: 180 | self.launch(id) 181 | self.quit() 182 | except Exception as e: 183 | if not Env.DEVELOP: 184 | self.iconify() 185 | ErrorWindow(self, str(e), traceback.format_exc(), quit=True).create() 186 | raise 187 | 188 | def launch(self, id: str): 189 | if DgpSessionWrap.is_running_dmm(): 190 | raise Exception(i18n.t("app.lib.dmm_already_running")) 191 | 192 | path = DataPathConfig.ACCOUNT_SHORTCUT.joinpath(id).with_suffix(".json") 193 | data = LauncherShortcutData.from_path(path) 194 | 195 | account_path = DataPathConfig.ACCOUNT.joinpath(data.account_path.get()).with_suffix(".bytes") 196 | 197 | before_session = DgpSessionWrap.read_dgp() 198 | 199 | session = DgpSessionWrap.read_cookies(Path(account_path)) 200 | if session.get_access_token() is None: 201 | raise Exception(i18n.t("app.launch.export_error")) 202 | session.write() 203 | 204 | dgp = AppConfig.DATA.dmm_game_player_program_folder.get_path() 205 | 206 | dmm_args = data.dgp_args.get().split(" ") 207 | process = ProcessManager.run(["DMMGamePlayer.exe"] + dmm_args, cwd=str(dgp.absolute())) 208 | 209 | assert process.stdout is not None 210 | for line in process.stdout: 211 | logging.debug(decode(line)) 212 | 213 | session = DgpSessionWrap.read_dgp() 214 | if session.get_access_token() is None: 215 | raise Exception(i18n.t("app.launch.import_error")) 216 | session.write_bytes(str(account_path)) 217 | before_session.write() 218 | 219 | 220 | class GameLauncherUac(CTk): 221 | @staticmethod 222 | def wait(args: list[str]): 223 | if not ProcessManager.admin_check(): 224 | pid_manager = ProcessIdManager() 225 | ProcessManager.admin_run([sys.executable, *args]) 226 | print(sys.executable) 227 | game_pid = pid_manager.new_process().search(sys.executable) 228 | while psutil.pid_exists(game_pid): 229 | time.sleep(1) 230 | 231 | 232 | def decode(s: bytes) -> str: 233 | try: 234 | return s.decode("utf-8").strip() 235 | except Exception: 236 | pass 237 | try: 238 | return s.decode("cp932").strip() 239 | except Exception: 240 | pass 241 | return str(s) 242 | -------------------------------------------------------------------------------- /DMMGamePlayerFastLauncher/lib/DGPSessionV2.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import concurrent.futures 3 | import hashlib 4 | import json 5 | import logging 6 | import os 7 | import random 8 | from pathlib import Path 9 | from urllib.parse import parse_qsl 10 | 11 | import psutil 12 | import requests 13 | import requests.cookies 14 | import urllib3 15 | from Crypto.Cipher import AES 16 | from Crypto.Random import get_random_bytes 17 | from win32 import win32crypt 18 | 19 | urllib3.disable_warnings() 20 | 21 | 22 | def text_factory(x: bytes): 23 | try: 24 | return x.decode("utf-8") 25 | except Exception: 26 | return x 27 | 28 | 29 | class DgpSessionUtils: 30 | @staticmethod 31 | def gen_rand_hex(): 32 | return hashlib.sha256(str(random.random()).encode()).hexdigest() 33 | 34 | @staticmethod 35 | def gen_rand_address(): 36 | hex = DgpSessionUtils.gen_rand_hex() 37 | address = "" 38 | for x in range(12): 39 | address += hex[x] 40 | if x % 2 == 1: 41 | address += ":" 42 | return address[:-1] 43 | 44 | 45 | class DMMAlreadyRunningException(Exception): 46 | pass 47 | 48 | 49 | class DgpSessionV2: 50 | DGP5_PATH = Path(os.environ["PROGRAMFILES"]).joinpath("DMMGamePlayer") 51 | DGP5_DATA_PATH = Path(os.environ["APPDATA"]).joinpath("dmmgameplayer5") 52 | 53 | HEADERS: dict[str, str] = { 54 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", 55 | "Upgrade-Insecure-Requests": "1", 56 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36", 57 | } 58 | DGP5_HEADERS: dict[str, str] = { 59 | "Connection": "keep-alive", 60 | "User-Agent": "DMMGamePlayer5-Win/5.3.25 Electron/34.3.0", 61 | "Client-App": "DMMGamePlayer5", 62 | "Client-Version": "5.3.25", 63 | "Sec-Fetch-Site": "none", 64 | "Sec-Fetch-Mode": "no-cors", 65 | "Sec-Fetch-Dest": "empty", 66 | "Accept-Encoding": "gzip, deflate, br, zstd", 67 | "Accept-Language": "ja", 68 | "Priority": "u=1, i", 69 | } 70 | DGP5_DEVICE_PARAMS: dict[str, str] = { 71 | "mac_address": DgpSessionUtils.gen_rand_address(), 72 | "hdd_serial": DgpSessionUtils.gen_rand_hex(), 73 | "motherboard": DgpSessionUtils.gen_rand_hex(), 74 | "user_os": "win", 75 | } 76 | DATA_DESCR: str = "DMMGamePlayerFastLauncher" 77 | LOGGER = logging.getLogger("DgpSessionV2") 78 | 79 | API_DGP = "https://apidgp-gameplayer.games.dmm.com{0}" 80 | LAUNCH_CL = API_DGP.format("/v5/r2/launch/cl") 81 | LAUNCH_PKG = API_DGP.format("/v5/launch/pkg") 82 | HARDWARE_CODE = API_DGP.format("/v5/hardwarecode") 83 | HARDWARE_CONF = API_DGP.format("/v5/hardwareconf") 84 | HARDWARE_LIST = API_DGP.format("/v5/hardwarelist") 85 | HARDWARE_REJECT = API_DGP.format("/v5/hardwarereject") 86 | USER_INFO = API_DGP.format("/v5/userinfo") 87 | CHECK_ACCESS_TOKEN = API_DGP.format("/v5/auth/accesstoken/check") 88 | ACCESS_TOKEN = API_DGP.format("/v5/auth/accesstoken/issue") 89 | LOGIN_URL = API_DGP.format("/v5/auth/login/url") 90 | SIGNED_URL = "https://cdn-gameplayer.games.dmm.com/product/*" 91 | WEB_LOGIN_URL = "https://accounts.dmm.com/service/oauth/=/path=" 92 | PROXY = {} 93 | 94 | actauth: dict[str, str] 95 | session: requests.Session 96 | 97 | def __init__(self): 98 | self.actauth = {} 99 | self.session = requests.Session() 100 | self.session.cookies = requests.cookies.RequestsCookieJar() 101 | self.session.cookies.set("age_check_done", "0", domain=".dmm.com", path="/") 102 | self.session.proxies = self.PROXY 103 | 104 | def write_safe(self, data: bytes): 105 | file = self.DGP5_DATA_PATH.joinpath("authAccessTokenData.enc") 106 | with open(file, "wb") as f: 107 | f.write(data) 108 | 109 | def read_safe(self): 110 | file = self.DGP5_DATA_PATH.joinpath("authAccessTokenData.enc") 111 | if file.exists(): 112 | with open(file, "rb") as f: 113 | return f.read() 114 | return None 115 | 116 | def write(self): 117 | aes_key = self.get_aes_key() 118 | v10 = "v10".encode() 119 | nonce = get_random_bytes(12) 120 | value = json.dumps(self.actauth).encode() 121 | cipher = AES.new(aes_key, AES.MODE_GCM, nonce) 122 | data, mac = cipher.encrypt_and_digest(value) 123 | enc = self.join_encrypted_data(v10, nonce, data, mac) 124 | self.write_safe(enc) 125 | 126 | def read(self): 127 | aes_key = self.get_aes_key() 128 | enc = self.read_safe() 129 | if enc: 130 | v10, nonce, data, mac = self.split_encrypted_data(enc) 131 | cipher = AES.new(aes_key, AES.MODE_GCM, nonce) 132 | value = cipher.decrypt_and_verify(data, mac) 133 | self.actauth = json.loads(value.decode()) 134 | else: 135 | self.actauth = {} 136 | 137 | def write_bytes(self, file: str): 138 | data = win32crypt.CryptProtectData( 139 | json.dumps(self.actauth).encode(), 140 | self.DATA_DESCR, 141 | ) 142 | with open(file, "wb") as f: 143 | f.write(data) 144 | 145 | def read_bytes(self, file: str): 146 | with open(file, "rb") as f: 147 | data = f.read() 148 | _, contents = win32crypt.CryptUnprotectData(data) 149 | self.actauth = json.loads(contents.decode()) 150 | 151 | def get_access_token(self): 152 | return self.actauth.get("accessToken") 153 | 154 | def get_headers(self): 155 | return self.DGP5_HEADERS | {"actauth": self.get_access_token()} 156 | 157 | def get(self, url: str, params=None, **kwargs) -> requests.Response: 158 | self.LOGGER.info("params %s", params) 159 | res = self.session.get(url, headers=self.HEADERS, params=params, **kwargs) 160 | return self.logger(res) 161 | 162 | def post(self, url: str, json=None, **kwargs) -> requests.Response: 163 | self.LOGGER.info("json %s", json) 164 | res = self.session.post(url, headers=self.HEADERS, json=json, **kwargs) 165 | return self.logger(res) 166 | 167 | def get_dgp(self, url: str, params=None, **kwargs) -> requests.Response: 168 | self.LOGGER.info("params %s", params) 169 | res = self.session.get(url, headers=self.get_headers(), params=params, **kwargs) 170 | return self.logger(res) 171 | 172 | def post_dgp(self, url: str, json=None, **kwargs) -> requests.Response: 173 | self.LOGGER.info("json %s", json) 174 | res = self.session.post(url, headers=self.get_headers(), json=json, **kwargs) 175 | return self.logger(res) 176 | 177 | def post_device_dgp(self, url: str, json=None, **kwargs) -> requests.Response: 178 | json = (json or {}) | self.DGP5_DEVICE_PARAMS 179 | return self.post_dgp(url, json=json, **kwargs) 180 | 181 | def logger(self, res: requests.Response) -> requests.Response: 182 | if res.headers.get("Content-Type") == "application/json": 183 | self.LOGGER.info("application/json %s", res.text) 184 | return res 185 | 186 | def lunch(self, product_id: str, game_type: str) -> requests.Response: 187 | if game_type == "GCL": 188 | url = self.LAUNCH_CL 189 | elif game_type == "ACL": 190 | url = self.LAUNCH_CL 191 | elif game_type == "AMAIN": 192 | url = self.LAUNCH_PKG 193 | elif game_type == "GMAIN": 194 | url = self.LAUNCH_PKG 195 | else: 196 | raise Exception("Unknown game_type: " + game_type + " " + product_id) 197 | json = { 198 | "product_id": product_id, 199 | "game_type": game_type, 200 | "game_os": "win", 201 | "launch_type": "LIB", 202 | } 203 | return self.post_device_dgp(url, json=json, verify=False) 204 | 205 | def download(self, sign: str, filelist_url: str, output: Path): 206 | sign_dict = dict(parse_qsl(sign.replace(";", "&"))) 207 | signed = { 208 | "Policy": sign_dict["CloudFront-Policy"], 209 | "Signature": sign_dict["CloudFront-Signature"], 210 | "Key-Pair-Id": sign_dict["CloudFront-Key-Pair-Id"], 211 | } 212 | url = self.API_DGP.format(filelist_url) 213 | data = self.get_dgp(url).json() 214 | 215 | def download_save(file: dict) -> tuple[int, dict]: 216 | path = output.joinpath(file["local_path"][1:]) 217 | content = self.get(data["data"]["domain"] + "/" + file["path"], params=signed).content 218 | path.parent.mkdir(parents=True, exist_ok=True) 219 | with open(path, "wb") as f: 220 | f.write(content) 221 | return file["size"], file 222 | 223 | def check_sum(file: dict) -> tuple[bool, dict]: 224 | path = output.joinpath(file["local_path"][1:]) 225 | if not file["check_hash_flg"]: 226 | return True, file 227 | if file["force_delete_flg"]: 228 | path.unlink() 229 | return True, file 230 | if not path.exists(): 231 | return False, file 232 | try: 233 | with open(path, "rb") as f: 234 | content = f.read() 235 | return file["hash"] == hashlib.md5(content).hexdigest(), file 236 | except Exception: 237 | return False, file 238 | 239 | check_count = 0 240 | check_failed_list: list[dict] = [] 241 | with concurrent.futures.ThreadPoolExecutor(max_workers=10) as pool: 242 | tasks = [pool.submit(lambda x: check_sum(x), x) for x in data["data"]["file_list"]] 243 | for task in concurrent.futures.as_completed(tasks): 244 | res, file = task.result() 245 | check_count += 1 246 | if not res: 247 | check_failed_list.append(file) 248 | yield check_count / len(data["data"]["file_list"]), file 249 | 250 | yield 0, None 251 | 252 | check_failed_list = sorted(check_failed_list, key=lambda x: x["size"], reverse=True) 253 | download_max_size = sum([x["size"] for x in check_failed_list]) 254 | download_size: int = 0 255 | 256 | with concurrent.futures.ThreadPoolExecutor(max_workers=10) as pool: 257 | tasks = [pool.submit(lambda x: download_save(x), x) for x in check_failed_list] 258 | for task in concurrent.futures.as_completed(tasks): 259 | size, file = task.result() 260 | download_size += size 261 | yield download_size / download_max_size, file 262 | 263 | def get_config(self): 264 | with open(self.DGP5_DATA_PATH.joinpath("dmmgame.cnf"), "r", encoding="utf-8") as f: 265 | config = f.read() 266 | res = json.loads(config) 267 | self.LOGGER.info("READ dmmgame.cnf %s", res) 268 | return res 269 | 270 | def set_config(self, config): 271 | with open(self.DGP5_DATA_PATH.joinpath("dmmgame.cnf"), "w", encoding="utf-8") as f: 272 | f.write(json.dumps(config, indent=4)) 273 | 274 | def get_aes_key(self): 275 | with open(self.DGP5_DATA_PATH.joinpath("Local State"), "r", encoding="utf-8") as f: 276 | local_state = json.load(f) 277 | encrypted_key = base64.b64decode(local_state["os_crypt"]["encrypted_key"].encode())[5:] 278 | key = win32crypt.CryptUnprotectData(encrypted_key, None, None, None, 0)[1] 279 | return key 280 | 281 | def split_encrypted_data(self, encrypted_data: bytes) -> tuple[bytes, bytes, bytes, bytes]: 282 | return ( 283 | encrypted_data[0:3], 284 | encrypted_data[3:15], 285 | encrypted_data[15:-16], 286 | encrypted_data[-16:], 287 | ) 288 | 289 | def join_encrypted_data(self, v10: bytes, nonce: bytes, data: bytes, mac: bytes) -> bytes: 290 | return v10 + nonce + data + mac 291 | 292 | @staticmethod 293 | def read_dgp() -> "DgpSessionV2": 294 | session = DgpSessionV2() 295 | session.read() 296 | return session 297 | 298 | @staticmethod 299 | def read_cookies(path: Path) -> "DgpSessionV2": 300 | session = DgpSessionV2() 301 | session.read_bytes(str(path)) 302 | return session 303 | 304 | @staticmethod 305 | def is_running_dmm() -> bool: 306 | for proc in psutil.process_iter(): 307 | try: 308 | if Path(proc.exe()) == DgpSessionV2.DGP5_PATH.joinpath("DMMGamePlayer.exe"): 309 | return True 310 | except Exception: 311 | pass 312 | return False 313 | -------------------------------------------------------------------------------- /DMMGamePlayerFastLauncher/component/component.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | from pathlib import Path 3 | from tkinter import Frame, Misc, StringVar, filedialog 4 | from typing import Any, Callable, Optional 5 | 6 | import customtkinter as ctk 7 | import i18n 8 | from component.var import PathVar 9 | from customtkinter import CTkBaseClass, CTkButton, CTkCheckBox, CTkEntry, CTkFrame, CTkLabel, CTkOptionMenu, CTkProgressBar, CTkToplevel, Variable 10 | from customtkinter import ThemeManager as CTkm 11 | 12 | 13 | class LabelComponent(CTkFrame): 14 | text: str 15 | frame: CTkFrame 16 | tooltip: Optional[str] 17 | required: bool 18 | 19 | def __init__(self, master: Misc, text: str, tooltip: Optional[str] = None, required: bool = False) -> None: 20 | super().__init__(master, fg_color="transparent") 21 | self.pack(fill=ctk.X, expand=True) 22 | self.text = text 23 | self.tooltip = tooltip 24 | self.frame = CTkFrame(self.winfo_toplevel(), fg_color=CTkm.theme["LabelComponent"]["fg_color"]) 25 | self.required = required 26 | if self.required: 27 | if self.tooltip: 28 | self.tooltip = i18n.t("app.component.required") + "\n" + self.tooltip 29 | else: 30 | self.tooltip = i18n.t("app.component.required") 31 | 32 | def create(self): 33 | label = CTkLabel(self, text=self.text) 34 | label.pack(side=ctk.LEFT) 35 | if self.tooltip is not None: 36 | label.bind("", self.enter_event) 37 | label.bind("", self.leave_event) 38 | CTkLabel(self.frame, text=self.tooltip, fg_color=CTkm.theme["LabelComponent"]["fg_color"], justify=ctk.LEFT).pack(padx=5, pady=0) 39 | 40 | if self.required: 41 | CTkLabel(self, text=i18n.t("app.component.required_symbol"), text_color=CTkm.theme["LabelComponent"]["required_color"], justify=ctk.LEFT).pack(side=ctk.LEFT) 42 | 43 | return self 44 | 45 | def enter_event(self, event): 46 | assert self.tooltip is not None 47 | self.frame.place(x=self.winfo_rootx() - self.frame.master.winfo_rootx(), y=self.winfo_rooty() - self.frame.master.winfo_rooty() + 28) 48 | 49 | def leave_event(self, event): 50 | assert self.tooltip is not None 51 | self.frame.place_forget() 52 | 53 | def destroy(self): 54 | self.frame.destroy() 55 | return super().destroy() 56 | 57 | 58 | class CheckBoxComponent(CTkFrame): 59 | variable: Variable 60 | text: str 61 | 62 | def __init__(self, master: Frame, text: str, variable: Variable): 63 | super().__init__(master, fg_color="transparent") 64 | self.pack(fill=ctk.X, expand=True) 65 | self.text = text 66 | self.variable = variable 67 | 68 | def create(self): 69 | CTkCheckBox( 70 | self, 71 | height=40, 72 | checkbox_width=CTkm.theme["CheckBoxComponent"]["checkbox_width"], 73 | checkbox_height=CTkm.theme["CheckBoxComponent"]["checkbox_height"], 74 | border_width=CTkm.theme["CheckBoxComponent"]["border_width"], 75 | text=self.text, 76 | variable=self.variable, 77 | ).pack(fill=ctk.X) 78 | return self 79 | 80 | 81 | class EntryComponent(CTkFrame): 82 | variable: Variable 83 | text: str 84 | required: bool 85 | tooltip: Optional[str] 86 | required: bool 87 | command: list[tuple[str, Callable[[Variable], None]]] 88 | state: str 89 | alnum_only: bool 90 | 91 | def __init__( 92 | self, 93 | master: Frame, 94 | text: str, 95 | variable: Variable, 96 | tooltip: Optional[str] = None, 97 | required: bool = False, 98 | command: Optional[list[tuple[str, Callable[[Variable], None]]]] = None, 99 | state: Optional[str] = None, 100 | alnum_only: bool = False, 101 | ) -> None: 102 | super().__init__(master, fg_color="transparent") 103 | self.pack(fill=ctk.X, expand=True) 104 | self.text = text 105 | self.variable = variable 106 | self.tooltip = tooltip 107 | self.required = required 108 | self.command = command or [] 109 | self.state = state or tk.NORMAL 110 | self.alnum_only = alnum_only 111 | 112 | def create(self): 113 | LabelComponent(self, text=self.text, required=self.required, tooltip=self.tooltip).create() 114 | entry = CTkEntry(self, textvariable=self.variable, state=self.state) 115 | entry.pack(side=ctk.LEFT, fill=ctk.BOTH, expand=True) 116 | 117 | if self.alnum_only: 118 | entry.bind("", self.alnum_only_callback) 119 | 120 | for cmd in self.command: 121 | CTkButton(self, text=cmd[0], command=self.call(cmd[1]), width=0).pack(side=ctk.LEFT, padx=2) 122 | return self 123 | 124 | def call(self, cmd): 125 | return lambda: cmd(self.variable) 126 | 127 | def alnum_only_callback(self, event): 128 | char = event.char.encode("utf-8") 129 | if event.keysym in ["backslash", "colon", "slash", "asterisk", "question", "quote", "less", "greater", "pipe"]: 130 | return "break" 131 | if char.isascii(): 132 | return 133 | return "break" 134 | 135 | 136 | class ButtonComponent(CTkFrame): 137 | frame: CTkFrame 138 | text: str 139 | tooltip: Optional[str] 140 | command: Callable[[], None] 141 | state: str 142 | 143 | def __init__( 144 | self, 145 | master: Frame, 146 | text: str, 147 | command: Callable[[], None], 148 | tooltip: Optional[str] = None, 149 | ) -> None: 150 | super().__init__(master, fg_color="transparent") 151 | self.frame = CTkFrame(self.winfo_toplevel(), fg_color=CTkm.theme["LabelComponent"]["fg_color"]) 152 | self.pack(fill=ctk.X, expand=True) 153 | self.text = text 154 | self.tooltip = tooltip 155 | self.command = command 156 | 157 | def create(self): 158 | button = CTkButton(self, text=self.text, command=self.command) 159 | button.pack(fill=ctk.X, pady=5) 160 | if self.tooltip is not None: 161 | button.bind("", self.enter_event) 162 | button.bind("", self.leave_event) 163 | CTkLabel(self.frame, text=self.tooltip, fg_color=CTkm.theme["LabelComponent"]["fg_color"], justify=ctk.LEFT).pack(padx=5, pady=0) 164 | 165 | return self 166 | 167 | def enter_event(self, event): 168 | assert self.tooltip is not None 169 | self.frame.place(x=self.winfo_rootx() - self.frame.master.winfo_rootx(), y=self.winfo_rooty() - self.frame.master.winfo_rooty() + 35) 170 | 171 | def leave_event(self, event): 172 | assert self.tooltip is not None 173 | self.frame.place_forget() 174 | 175 | def destroy(self): 176 | self.frame.destroy() 177 | return super().destroy() 178 | 179 | 180 | class PathComponentBase(EntryComponent): 181 | variable: PathVar 182 | 183 | def __init__(self, *args, **kwargs): 184 | super().__init__(*args, **kwargs) 185 | self.command.append((i18n.t("app.component.reference"), lambda v: self.reference_callback(v))) 186 | 187 | def reference_callback(self, variable: Variable): 188 | raise NotImplementedError 189 | 190 | 191 | class FilePathComponent(PathComponentBase): 192 | def reference_callback(self, variable: PathVar): 193 | path = filedialog.askopenfilename(title=self.text, initialdir=variable.get()) 194 | if path != "": 195 | variable.set_path(Path(path)) 196 | 197 | 198 | class DirectoryPathComponent(PathComponentBase): 199 | def reference_callback(self, variable: PathVar): 200 | path = filedialog.askdirectory(title=self.text, initialdir=variable.get()) 201 | if path != "": 202 | variable.set_path(Path(path)) 203 | 204 | 205 | class OptionMenuComponent(CTkFrame): 206 | variable: StringVar 207 | text: str 208 | values: list[str] 209 | command: Optional[Callable[[str], None]] 210 | tooltip: Optional[str] 211 | 212 | def __init__(self, master: Frame, text: str, variable: StringVar, values: list[str], command: Optional[Callable[[str], None]] = None, tooltip: Optional[str] = None): 213 | super().__init__(master, fg_color="transparent") 214 | self.pack(fill=ctk.X, expand=True) 215 | self.text = text 216 | self.variable = variable 217 | self.values = values 218 | self.command = command 219 | self.tooltip = tooltip 220 | 221 | def create(self): 222 | required = self.variable.get() not in self.values 223 | LabelComponent(self, text=self.text, tooltip=self.tooltip, required=required).create() 224 | CTkOptionMenu(self, values=self.values, variable=self.variable, command=self.command).pack(side=ctk.LEFT, fill=ctk.BOTH, expand=True) 225 | return self 226 | 227 | 228 | class OptionMenuTupleComponent(CTkFrame): 229 | variable: StringVar 230 | shadow_variable: StringVar 231 | text: str 232 | values: list[tuple[Any, str]] 233 | command: Optional[Callable[[str], None]] 234 | tooltip: Optional[str] 235 | 236 | def __init__( 237 | self, master: Frame, text: str, variable: StringVar, values: list[tuple[Any, str]], command: Optional[Callable[[str], None]] = None, tooltip: Optional[str] = None 238 | ): 239 | super().__init__(master, fg_color="transparent") 240 | self.pack(fill=ctk.X, expand=True) 241 | self.text = text 242 | self.variable = variable 243 | default = [x[1] for x in values if x[0] == variable.get()] 244 | self.shadow_variable = StringVar(value=default[0] if len(default) else None) 245 | self.values = values 246 | self.command = command 247 | self.tooltip = tooltip 248 | 249 | def create(self): 250 | required = self.shadow_variable.get() not in [x[1] for x in self.values] 251 | LabelComponent(self, text=self.text, tooltip=self.tooltip, required=required).create() 252 | values = [x[1] for x in self.values] 253 | 254 | CTkOptionMenu(self, values=values, variable=self.shadow_variable, command=self.callback).pack(side=ctk.LEFT, fill=ctk.BOTH, expand=True) 255 | return self 256 | 257 | def callback(self, text: str): 258 | var = [x[0] for x in self.values if x[1] == text][0] 259 | self.variable.set(var) 260 | if self.command is not None: 261 | self.command(var) 262 | 263 | 264 | class PaddingComponent(CTkFrame): 265 | def __init__(self, master: Frame, width: int = 0, height: int = 0): 266 | super().__init__(master, fg_color="transparent") 267 | self.pack(fill=ctk.X, expand=True) 268 | self.width = width 269 | self.height = height 270 | 271 | def create(self): 272 | CTkBaseClass(self, width=self.width, height=self.height).pack(side=ctk.LEFT, fill=ctk.BOTH, expand=True) 273 | return self 274 | 275 | 276 | class ConfirmWindow(CTkToplevel): 277 | command: Callable 278 | text: str 279 | 280 | def __init__(self, master: Frame, command: Callable, text: str): 281 | super().__init__(master) 282 | self.geometry("300x100") 283 | self.command = command 284 | self.text = text 285 | 286 | def create(self): 287 | CTkLabel(self, text=self.text).pack(side=ctk.TOP, fill=ctk.X) 288 | CTkButton(self, text=i18n.t("app.component.yes"), command=self.yes).pack(side=ctk.LEFT, fill=ctk.X, expand=True, padx=10) 289 | CTkButton(self, text=i18n.t("app.component.no"), command=self.no).pack(side=ctk.LEFT, fill=ctk.X, expand=True, padx=10) 290 | 291 | def yes(self): 292 | try: 293 | self.command() 294 | except Exception: 295 | self.destroy() 296 | raise 297 | self.destroy() 298 | 299 | def no(self): 300 | self.destroy() 301 | 302 | 303 | class CTkProgressWindow(CTkToplevel): 304 | label: CTkLabel 305 | progress: CTkProgressBar 306 | now: float 307 | max: float 308 | 309 | def __init__(self, master: Misc, now: float = 0, max: float = 1): 310 | super().__init__(master) 311 | self.geometry("300x100") 312 | self.label = CTkLabel(self, text="0.00%", justify=ctk.LEFT, anchor=ctk.W) 313 | self.progress = CTkProgressBar(self, width=300) 314 | 315 | self.deiconify() 316 | self.lift() 317 | self.focus_force() 318 | 319 | self.now = now 320 | self.max = max 321 | 322 | def create(self): 323 | CTkLabel(self, text=i18n.t("app.component.download")).pack(fill=ctk.X, expand=True, padx=10, pady=(10, 0)) 324 | self.label.pack(fill=ctk.X, expand=True, padx=10, pady=0) 325 | self.progress.pack(fill=ctk.X, expand=True, padx=10, pady=(0, 10)) 326 | 327 | self.progress.set(self.now / self.max) 328 | return self 329 | 330 | def add(self, value: float): 331 | self.now += value 332 | self.progress.set(self.now / self.max) 333 | self.label.configure(text=f"{(self.now / self.max * 100):.2f}%") 334 | 335 | def set(self, value: float): 336 | self.now = value 337 | self.progress.set(self.now / self.max) 338 | self.label.configure(text=f"{(self.now / self.max * 100):.2f}%") 339 | --------------------------------------------------------------------------------