cml-launcher 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,78 @@
1
+ Metadata-Version: 2.4
2
+ Name: cml-launcher
3
+ Version: 1.0.0
4
+ Summary: CML - cm Minecraft Launcher - 纯代码 Minecraft 启动器
5
+ Author: cm
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/your-repo/cml-launcher
8
+ Requires-Python: >=3.8
9
+ Description-Content-Type: text/markdown
10
+ Requires-Dist: minecraft-launcher-lib>=1.4.0
11
+ Provides-Extra: gui
12
+ Requires-Dist: customtkinter>=5.2.0; extra == "gui"
13
+ Requires-Dist: Pillow>=10.0.0; extra == "gui"
14
+
15
+ # CML - cm Minecraft Launcher
16
+
17
+ 简洁高效的 Minecraft 启动器,基于 CustomTkinter 构建。
18
+
19
+ ## 功能特性
20
+
21
+ - 账户管理(离线登录)
22
+ - 版本管理(安装/删除/选择)
23
+ - 模组管理(扫描/启用/禁用/删除)
24
+ - Modrinth 模组/光影搜索下载
25
+ - 自动 Java 检测
26
+ - 深色主题 UI
27
+
28
+ ## 安装
29
+
30
+ ### 方式一:pip 安装
31
+
32
+ ```bash
33
+ pip install cml-launcher
34
+ ```
35
+
36
+ 安装后,在命令行运行:
37
+
38
+ ```bash
39
+ cml
40
+ ```
41
+
42
+ 或使用模块方式:
43
+
44
+ ```bash
45
+ python -m cml
46
+ ```
47
+
48
+ ### 方式二:开发模式
49
+
50
+ ```bash
51
+ git clone <repo-url>
52
+ cd cml-launcher
53
+ pip install -e .
54
+ ```
55
+
56
+ ## 使用
57
+
58
+ 安装后,运行以下命令启动:
59
+
60
+ ```bash
61
+ cml
62
+ ```
63
+
64
+ ## 系统要求
65
+
66
+ - Python 3.8+
67
+ - Windows/Linux/macOS
68
+ - Minecraft 游戏文件(首次启动会自动下载)
69
+
70
+ ## 依赖
71
+
72
+ - customtkinter >= 5.2.0
73
+ - minecraft-launcher-lib >= 1.4.0
74
+ - Pillow >= 10.0.0
75
+
76
+ ## License
77
+
78
+ MIT
@@ -0,0 +1,64 @@
1
+ # CML - cm Minecraft Launcher
2
+
3
+ 简洁高效的 Minecraft 启动器,基于 CustomTkinter 构建。
4
+
5
+ ## 功能特性
6
+
7
+ - 账户管理(离线登录)
8
+ - 版本管理(安装/删除/选择)
9
+ - 模组管理(扫描/启用/禁用/删除)
10
+ - Modrinth 模组/光影搜索下载
11
+ - 自动 Java 检测
12
+ - 深色主题 UI
13
+
14
+ ## 安装
15
+
16
+ ### 方式一:pip 安装
17
+
18
+ ```bash
19
+ pip install cml-launcher
20
+ ```
21
+
22
+ 安装后,在命令行运行:
23
+
24
+ ```bash
25
+ cml
26
+ ```
27
+
28
+ 或使用模块方式:
29
+
30
+ ```bash
31
+ python -m cml
32
+ ```
33
+
34
+ ### 方式二:开发模式
35
+
36
+ ```bash
37
+ git clone <repo-url>
38
+ cd cml-launcher
39
+ pip install -e .
40
+ ```
41
+
42
+ ## 使用
43
+
44
+ 安装后,运行以下命令启动:
45
+
46
+ ```bash
47
+ cml
48
+ ```
49
+
50
+ ## 系统要求
51
+
52
+ - Python 3.8+
53
+ - Windows/Linux/macOS
54
+ - Minecraft 游戏文件(首次启动会自动下载)
55
+
56
+ ## 依赖
57
+
58
+ - customtkinter >= 5.2.0
59
+ - minecraft-launcher-lib >= 1.4.0
60
+ - Pillow >= 10.0.0
61
+
62
+ ## License
63
+
64
+ MIT
@@ -0,0 +1,39 @@
1
+ """
2
+ CML - cm Minecraft Launcher
3
+
4
+ 无 GUI 版本:通过代码启动/下载游戏
5
+
6
+ 用法:
7
+ import cml
8
+
9
+ # 启动游戏
10
+ cml.launch_game("1.20.1") # 通过版本名启动
11
+ cml.launch_game(jar_path="C:/MC/versions/1.20.1/1.20.1.jar") # 通过 jar 绝对路径启动
12
+
13
+ # 下载游戏
14
+ cml.download_version("1.20.1") # 下载到默认目录
15
+ cml.download_version("1.20.1", "D:/MC") # 下载到指定目录
16
+
17
+ # 其他
18
+ cml.list_versions() # 获取可下载版本
19
+ cml.get_versions() # 获取已安装版本
20
+ cml.set_version("1.20.1") # 设置当前版本
21
+ cml.get_java_path() # 获取 Java 路径
22
+ """
23
+
24
+ __version__ = "1.0.0"
25
+ __author__ = "cm"
26
+
27
+ from .core import CMLCore, launch_game, get_versions, set_version, get_java_path, download_version, list_versions, search_versions
28
+
29
+ __all__ = [
30
+ "CMLCore",
31
+ "launch_game",
32
+ "get_versions",
33
+ "set_version",
34
+ "get_java_path",
35
+ "download_version",
36
+ "list_versions",
37
+ "search_versions",
38
+ "__version__",
39
+ ]
@@ -0,0 +1,8 @@
1
+ """
2
+ 支持 python -m cml 运行
3
+ """
4
+
5
+ from .app import main
6
+
7
+ if __name__ == "__main__":
8
+ main()
@@ -0,0 +1,174 @@
1
+ """
2
+ CML - cm Minecraft Launcher
3
+ 主入口模块
4
+ """
5
+
6
+ import customtkinter as ctk
7
+ import os
8
+ import subprocess
9
+ import threading
10
+ import minecraft_launcher_lib as mll
11
+
12
+ from .modules.sidebar import Sidebar
13
+ from .modules.pages.home_page import HomePage
14
+ from .modules.pages.versions_page import VersionsPage
15
+ from .modules.pages.download_page import DownloadPage
16
+ from .modules.pages.mods_page import ModsPage
17
+ from .modules.pages.settings_page import SettingsPage
18
+ from .modules.config_manager import ConfigManager
19
+
20
+
21
+ class CMLauncher(ctk.CTk):
22
+ def __init__(self):
23
+ super().__init__()
24
+
25
+ self.title("CML - cm Minecraft Launcher")
26
+ self.geometry("1000x650")
27
+ self.minsize(900, 600)
28
+
29
+ ctk.set_appearance_mode("dark")
30
+ ctk.set_default_color_theme("blue")
31
+ self.configure(bg_color="#1a1a2e")
32
+
33
+ self.config = ConfigManager()
34
+ self.pages = {}
35
+ self.current_page = None
36
+
37
+ self._setup_ui()
38
+
39
+ # 启动后台线程自动检测 Java
40
+ threading.Thread(target=self._auto_detect_java, daemon=True).start()
41
+
42
+ def _setup_ui(self):
43
+ self.sidebar = Sidebar(self, self._switch_page)
44
+ self.sidebar.pack(side="left", fill="y")
45
+
46
+ self.content = ctk.CTkFrame(self, fg_color="#16213e", corner_radius=0)
47
+ self.content.pack(side="right", fill="both", expand=True)
48
+
49
+ self.pages["home"] = HomePage(self.content, self.config)
50
+ self.pages["versions"] = VersionsPage(self.content, self.config)
51
+ self.pages["download"] = DownloadPage(self.content)
52
+ self.pages["mods"] = ModsPage(self.content, self.config)
53
+ self.pages["settings"] = SettingsPage(self.content, self.config)
54
+
55
+ self._switch_page("home")
56
+
57
+ def _switch_page(self, page_name):
58
+ if self.current_page:
59
+ self.current_page.pack_forget()
60
+ page = self.pages.get(page_name)
61
+ if page:
62
+ page.pack(fill="both", expand=True, padx=20, pady=20)
63
+ self.current_page = page
64
+ self.sidebar.set_active(page_name)
65
+
66
+ def _auto_detect_java(self):
67
+ """启动时后台检测 Java 路径(已有有效路径则跳过)"""
68
+ saved = self.config.get("java_path", "")
69
+ if saved and os.path.exists(saved):
70
+ return # 已有有效路径,不重复扫描
71
+
72
+ found = self._scan_java()
73
+ if found:
74
+ self.config.set("java_path", found)
75
+ # 通知首页刷新 Java 显示
76
+ self.after(0, self._notify_java_found, found)
77
+
78
+ def _scan_java(self):
79
+ """多策略扫描 Java,返回第一个找到的 javaw.exe 路径"""
80
+
81
+ # 策略1:mll 内置检测
82
+ try:
83
+ path = mll.utils.get_java_executable()
84
+ if path and path != "java" and os.path.exists(path):
85
+ return path
86
+ except Exception:
87
+ pass
88
+
89
+ # 策略2:Minecraft 自带 runtime(jre-legacy / java-runtime-gamma 等)
90
+ mc_dir = mll.utils.get_minecraft_directory()
91
+ runtime_base = os.path.join(mc_dir, "runtime")
92
+ if os.path.exists(runtime_base):
93
+ for runtime_name in os.listdir(runtime_base):
94
+ for sub in ["bin", os.path.join("bin", "java")]:
95
+ candidate = os.path.join(runtime_base, runtime_name, "windows", runtime_name, sub, "javaw.exe")
96
+ if os.path.exists(candidate):
97
+ return candidate
98
+ # 另一种路径结构
99
+ candidate2 = os.path.join(runtime_base, runtime_name, sub, "javaw.exe")
100
+ if os.path.exists(candidate2):
101
+ return candidate2
102
+
103
+ # 策略3:注册表
104
+ reg_keys = [
105
+ r"HKLM\SOFTWARE\JavaSoft\Java Runtime Environment",
106
+ r"HKLM\SOFTWARE\JavaSoft\JDK",
107
+ r"HKLM\SOFTWARE\JavaSoft\Java Development Kit",
108
+ r"HKLM\SOFTWARE\WOW6432Node\JavaSoft\Java Runtime Environment",
109
+ ]
110
+ for key in reg_keys:
111
+ try:
112
+ result = subprocess.run(
113
+ ["reg", "query", key, "/v", "JavaHome"],
114
+ capture_output=True, text=True, timeout=5,
115
+ )
116
+ if result.returncode == 0:
117
+ for line in result.stdout.splitlines():
118
+ if "REG_SZ" in line:
119
+ java_home = line.split("REG_SZ")[-1].strip()
120
+ exe = os.path.join(java_home, "bin", "javaw.exe")
121
+ if os.path.exists(exe):
122
+ return exe
123
+ except Exception:
124
+ continue
125
+
126
+ # 策略4:常见安装目录扫描
127
+ common_bases = [
128
+ r"C:\Program Files\Java",
129
+ r"C:\Program Files\Eclipse Adoptium",
130
+ r"C:\Program Files\Microsoft",
131
+ r"C:\Program Files (x86)\Java",
132
+ r"D:\Java",
133
+ r"D:\Program Files\Java",
134
+ ]
135
+ for base in common_bases:
136
+ if not os.path.exists(base):
137
+ continue
138
+ try:
139
+ for entry in os.listdir(base):
140
+ exe = os.path.join(base, entry, "bin", "javaw.exe")
141
+ if os.path.exists(exe):
142
+ return exe
143
+ except Exception:
144
+ continue
145
+
146
+ # 策略5:where java(PATH 里找)
147
+ try:
148
+ result = subprocess.run(
149
+ ["where", "javaw"], capture_output=True, text=True, timeout=5
150
+ )
151
+ if result.returncode == 0:
152
+ path = result.stdout.strip().splitlines()[0]
153
+ if os.path.exists(path):
154
+ return path
155
+ except Exception:
156
+ pass
157
+
158
+ return None
159
+
160
+ def _notify_java_found(self, path):
161
+ """检测到 Java 后刷新首页显示"""
162
+ home = self.pages.get("home")
163
+ if home and hasattr(home, "_refresh_java_info"):
164
+ home._refresh_java_info()
165
+
166
+
167
+ def main():
168
+ """命令行入口(用于 python -m cml 或安装后 cml 命令)"""
169
+ app = CMLauncher()
170
+ app.mainloop()
171
+
172
+
173
+ if __name__ == "__main__":
174
+ main()
@@ -0,0 +1,297 @@
1
+ """
2
+ CML - cm Minecraft Launcher
3
+ 核心启动模块(无 GUI)
4
+ """
5
+
6
+ import os
7
+ import subprocess
8
+ import minecraft_launcher_lib as mll
9
+ from .modules.config_manager import ConfigManager
10
+
11
+
12
+ class CMLCore:
13
+ """CML 核心类(无 GUI)"""
14
+
15
+ def __init__(self):
16
+ self.config = ConfigManager()
17
+ self.minecraft_dir = mll.utils.get_minecraft_directory()
18
+
19
+ def get_java_path(self):
20
+ """获取 Java 路径(自动检测)"""
21
+ saved = self.config.get("java_path", "")
22
+ if saved and os.path.exists(saved):
23
+ return saved
24
+
25
+ # 自动检测
26
+ java_path = self._detect_java()
27
+ if java_path:
28
+ self.config.set("java_path", java_path)
29
+ return java_path
30
+
31
+ def _detect_java(self):
32
+ """多策略扫描 Java"""
33
+ # 策略1:mll 内置检测
34
+ try:
35
+ path = mll.utils.get_java_executable()
36
+ if path and path != "java" and os.path.exists(path):
37
+ return path
38
+ except Exception:
39
+ pass
40
+
41
+ # 策略2:Minecraft 自带 runtime
42
+ runtime_base = os.path.join(self.minecraft_dir, "runtime")
43
+ if os.path.exists(runtime_base):
44
+ for runtime_name in os.listdir(runtime_base):
45
+ for sub in ["bin", os.path.join("bin", "java")]:
46
+ candidate = os.path.join(runtime_base, runtime_name, "windows", runtime_name, sub, "javaw.exe")
47
+ if os.path.exists(candidate):
48
+ return candidate
49
+ candidate2 = os.path.join(runtime_base, runtime_name, sub, "javaw.exe")
50
+ if os.path.exists(candidate2):
51
+ return candidate2
52
+
53
+ # 策略3:注册表
54
+ reg_keys = [
55
+ r"HKLM\SOFTWARE\JavaSoft\Java Runtime Environment",
56
+ r"HKLM\SOFTWARE\JavaSoft\JDK",
57
+ r"HKLM\SOFTWARE\WOW6432Node\JavaSoft\Java Runtime Environment",
58
+ ]
59
+ for key in reg_keys:
60
+ try:
61
+ result = subprocess.run(
62
+ ["reg", "query", key, "/v", "JavaHome"],
63
+ capture_output=True, text=True, timeout=5,
64
+ )
65
+ if result.returncode == 0:
66
+ for line in result.stdout.splitlines():
67
+ if "REG_SZ" in line:
68
+ java_home = line.split("REG_SZ")[-1].strip()
69
+ exe = os.path.join(java_home, "bin", "javaw.exe")
70
+ if os.path.exists(exe):
71
+ return exe
72
+ except Exception:
73
+ continue
74
+
75
+ # 策略4:常见安装目录
76
+ common_bases = [
77
+ r"C:\Program Files\Java",
78
+ r"C:\Program Files\Eclipse Adoptium",
79
+ r"C:\Program Files\Microsoft",
80
+ r"D:\Java",
81
+ ]
82
+ for base in common_bases:
83
+ if not os.path.exists(base):
84
+ continue
85
+ try:
86
+ for entry in os.listdir(base):
87
+ exe = os.path.join(base, entry, "bin", "javaw.exe")
88
+ if os.path.exists(exe):
89
+ return exe
90
+ except Exception:
91
+ continue
92
+
93
+ # 策略5:PATH
94
+ try:
95
+ result = subprocess.run(
96
+ ["where", "javaw"], capture_output=True, text=True, timeout=5
97
+ )
98
+ if result.returncode == 0:
99
+ path = result.stdout.strip().splitlines()[0]
100
+ if os.path.exists(path):
101
+ return path
102
+ except Exception:
103
+ pass
104
+
105
+ return None
106
+
107
+ def get_installed_versions(self):
108
+ """获取已安装的版本列表"""
109
+ try:
110
+ return mll.utils.get_installed_versions(self.minecraft_dir)
111
+ except Exception:
112
+ return []
113
+
114
+ def get_current_version(self):
115
+ """获取当前选中的版本"""
116
+ return self.config.get("current_version")
117
+
118
+ def set_current_version(self, version_name):
119
+ """设置当前版本"""
120
+ self.config.set("current_version", version_name)
121
+
122
+ def launch_game(self, version=None, username=None, jar_path=None):
123
+ """
124
+ 启动 Minecraft 游戏
125
+
126
+ 参数:
127
+ version: 版本名(如 "1.20.1"),为 None 则使用当前选中版本
128
+ username: 用户名,为 None 则使用配置中的用户名
129
+ jar_path: .jar 文件绝对路径,指定后忽略 version 参数
130
+
131
+ 返回:
132
+ 成功返回 True,失败返回 False
133
+ """
134
+ if username is None:
135
+ username = self.config.get("username", "Player")
136
+
137
+ java_path = self.get_java_path()
138
+ if not java_path:
139
+ print("错误:未找到 Java,请手动设置 Java 路径")
140
+ return False
141
+
142
+ try:
143
+ if jar_path:
144
+ # 指定 jar 路径启动
145
+ if not os.path.exists(jar_path):
146
+ print(f"错误:jar 文件不存在 {jar_path}")
147
+ return False
148
+
149
+ # 获取版本目录作为游戏目录
150
+ game_dir = os.path.dirname(os.path.dirname(jar_path))
151
+ assets_dir = os.path.join(game_dir, "assets")
152
+ libs_dir = os.path.join(game_dir, "libraries")
153
+
154
+ command = [
155
+ java_path,
156
+ f"-Djava.library.path={libs_dir}",
157
+ "-cp", jar_path,
158
+ "net.minecraft.client.Main",
159
+ "--username", username,
160
+ "--version", os.path.basename(os.path.dirname(jar_path)),
161
+ "--gameDir", game_dir,
162
+ "--assetsDir", assets_dir,
163
+ ]
164
+
165
+ print(f"启动 jar:{jar_path}")
166
+ else:
167
+ # 使用版本名启动
168
+ if version is None:
169
+ version = self.get_current_version()
170
+
171
+ if not version:
172
+ print("错误:未指定版本,且未设置当前版本")
173
+ return False
174
+
175
+ options = {
176
+ "username": username,
177
+ "uuid": self.config.get("uuid", ""),
178
+ "token": self.config.get("token", ""),
179
+ }
180
+
181
+ command = mll.command.get_minecraft_command(
182
+ version, self.minecraft_dir, options
183
+ )
184
+
185
+ # 替换 java 路径
186
+ if command and command[0] == "java":
187
+ command[0] = java_path
188
+
189
+ print(f"启动版本:{version}")
190
+
191
+ print(f"Java 路径:{java_path}")
192
+ print(f"用户名:{username}")
193
+
194
+ # 启动游戏
195
+ subprocess.Popen(command, cwd=self.minecraft_dir)
196
+ return True
197
+
198
+ except Exception as e:
199
+ print(f"启动失败:{e}")
200
+ return False
201
+
202
+ def install_version(self, version, minecraft_version=None, path=None):
203
+ """
204
+ 安装新版本
205
+
206
+ 参数:
207
+ version: 版本名(如 "1.20.1")
208
+ minecraft_version: 对应的 MC 版本(通常和 version 相同)
209
+ path: 安装目录路径,为 None 则使用默认 .minecraft 目录
210
+ """
211
+ if minecraft_version is None:
212
+ minecraft_version = version
213
+
214
+ target_dir = path if path else self.minecraft_dir
215
+
216
+ try:
217
+ print(f"正在安装版本 {version} 到 {target_dir}...")
218
+ mll.install.install_minecraft_version(
219
+ minecraft_version, target_dir
220
+ )
221
+ print(f"版本 {version} 安装成功")
222
+ return True
223
+ except Exception as e:
224
+ print(f"安装失败:{e}")
225
+ return False
226
+
227
+ def get_available_versions(self):
228
+ """获取可下载的版本列表"""
229
+ try:
230
+ return mll.utils.get_available_versions(self.minecraft_dir)
231
+ except Exception:
232
+ return []
233
+
234
+ def search_versions(self, keyword):
235
+ """搜索版本"""
236
+ all_versions = self.get_available_versions()
237
+ keyword = keyword.lower()
238
+ return [v for v in all_versions if keyword in v.get("id", "").lower()]
239
+
240
+
241
+ def launch_game(version=None, username=None, jar_path=None):
242
+ """
243
+ 快捷函数:启动 Minecraft 游戏
244
+
245
+ 用法:
246
+ import cml
247
+ cml.launch_game("1.20.1") # 通过版本名启动
248
+ cml.launch_game(jar_path="C:/MC/versions/1.20.1/1.20.1.jar") # 通过 jar 路径启动
249
+ """
250
+ core = CMLCore()
251
+ return core.launch_game(version, username, jar_path)
252
+
253
+
254
+ def get_versions():
255
+ """获取已安装版本列表"""
256
+ core = CMLCore()
257
+ return core.get_installed_versions()
258
+
259
+
260
+ def set_version(version_name):
261
+ """设置当前版本"""
262
+ core = CMLCore()
263
+ core.set_current_version(version_name)
264
+
265
+
266
+ def get_java_path():
267
+ """获取 Java 路径"""
268
+ core = CMLCore()
269
+ return core.get_java_path()
270
+
271
+
272
+ def download_version(version, path=None):
273
+ """
274
+ 快捷函数:下载安装 Minecraft 版本
275
+
276
+ 用法:
277
+ import cml
278
+ cml.download_version("1.20.1") # 下载到默认目录
279
+ cml.download_version("1.20.1", "D:/MC") # 下载到指定目录
280
+ """
281
+ core = CMLCore()
282
+ return core.install_version(version, path=path)
283
+
284
+
285
+ def list_versions():
286
+ """快捷函数:获取可下载版本列表"""
287
+ core = CMLCore()
288
+ return core.get_available_versions()
289
+
290
+
291
+ def search_versions(keyword):
292
+ """快捷函数:搜索可下载版本"""
293
+ core = CMLCore()
294
+ return core.search_versions(keyword)
295
+
296
+
297
+ __all__ = ["CMLCore", "launch_game", "get_versions", "set_version", "get_java_path", "download_version", "list_versions", "search_versions"]