├── .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 | 
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 | 
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 | 
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 |
--------------------------------------------------------------------------------