docs: 为源码添加完整中文注释

- 文件头说明项目功能与用法
- 每个类前注释模块职责
- 每个方法添加docstring说明用途
- 方便开源社区开发者理解和使用
This commit is contained in:
MCLauncher committed 2026-08-06 01:53:03 +08:00
1 parent e4cc14cb9e
commit b6e67db02f
2 files changed
+75

No files matched your search

+70
View File
@@ -1,6 +1,20 @@
"""
MeowLauncher - 跨平台 Minecraft 启动器 (图形界面版)
功能特性:
- 图形界面 (Tkinter), Windows / Linux / macOS 三端界面一致
- 离线账号与第三方 Yggdrasil 登录 (LittleSkin 等 authlib-injector 皮肤站)
- 资源中心: 启动器内一键下载游戏版本 / Mod / 插件 (Modrinth 开放 API)
- 自动下载依赖库 / 原生库 / 资源文件, SHA1 校验 + 断点续传
- 保留命令行模式: python launcher.py --cli
用法:
python launcher.py 启动图形界面
python launcher.py --cli ... 命令行模式
"""
import argparse
import hashlib
import json
@@ -138,10 +152,16 @@ def library_allowed(lib: dict) -> bool:
# ---------------------------------------------------------------------------
# Modrinth 资源站客户端
# 用于在启动器内搜索和下载 Mod / 插件
# 使用官方开放 API (https://api.modrinth.com), 无需注册密钥
# ---------------------------------------------------------------------------
class ModrinthClient:
BASE = "https://api.modrinth.com/v2"
def search(self, query="", game_versions=None, loaders=None, project_type=None, limit=20):
"""搜索 Mod/插件项目。query 为关键词, game_versions 过滤MC版本, loaders 过滤加载器, project_type 为 mod 或 plugin。"""
facets = []
if game_versions:
facets.append(["versions:%s" % game_versions])
@@ -156,6 +176,7 @@ class ModrinthClient:
return data.get("hits", [])
def project_versions(self, project_id, game_version=None, loader=None):
"""获取某个项目适配指定 MC 版本与加载器的版本列表。"""
url = "%s/project/%s/version" % (self.BASE, project_id)
params = []
if game_version:
@@ -167,6 +188,7 @@ class ModrinthClient:
return json.loads(http_get(url).decode("utf-8"))
def first_file(self, project_id, game_version, loader):
"""获取第一个可下载的 jar 文件 (含下载地址/文件名/大小)。"""
versions = self.project_versions(project_id, game_version, loader)
for v in versions:
for f in v.get("files", []):
@@ -174,6 +196,10 @@ class ModrinthClient:
return f
return None
# ---------------------------------------------------------------------------
# 第三方登录 (Yggdrasil / authlib-injector 协议)
# 支持 LittleSkin、Blessing Skin 等皮肤站, 登录获取正版角色信息
# ---------------------------------------------------------------------------
class YggdrasilAuth:
PRESETS = {
@@ -183,9 +209,11 @@ class YggdrasilAuth:
}
def __init__(self, server_url: str):
"""初始化窗口、配色与资源中心状态。"""
self.server_url = server_url.rstrip("/")
def authenticate(self, username: str, password: str) -> dict:
"""登录第三方皮肤站, 返回 {access_token, uuid, name} 账号信息。"""
client_token = str(uuid_mod.uuid4())
payload = {
"agent": {"name": "Minecraft", "version": 1},
@@ -206,6 +234,7 @@ class YggdrasilAuth:
}
def download_injector(self, game_dir: Path) -> Path:
"""下载最新版 authlib-injector.jar, 启动游戏时注入用。"""
lib_dir = game_dir / "libraries" / "authlib-injector"
lib_dir.mkdir(parents=True, exist_ok=True)
jar = lib_dir / "authlib-injector.jar"
@@ -225,6 +254,10 @@ class YggdrasilAuth:
# ---------------------------------------------------------------------------
# 核心启动器
# 负责: 版本解析 -> 文件下载 -> 启动参数构建 -> 拉起 Java 进程
# ---------------------------------------------------------------------------
class MinecraftLauncher:
def __init__(self, game_dir, version_id, username="Steve", java_path=None,
min_mem=1024, max_mem=2048, auth=None, yggdrasil=None,
@@ -251,9 +284,11 @@ class MinecraftLauncher:
def fetch_version_manifest(self) -> dict:
"""拉取 Mojang 官方版本清单。"""
return json.loads(http_get(VERSION_MANIFEST_URL).decode("utf-8"))
def resolve_version(self) -> None:
"""把版本号解析成版本元数据, 支持 latest-release / latest-snapshot。"""
manifest = self.fetch_version_manifest()
latest = manifest.get("latest", {})
versions = manifest.get("versions", [])
@@ -276,11 +311,13 @@ class MinecraftLauncher:
def _emit(self, msg: str) -> None:
"""输出日志: 同时打印到控制台和回调 GUI。"""
log(msg)
if self.progress_cb:
self.progress_cb(msg)
def download_client(self) -> Path:
"""下载客户端 jar 文件到 versions/<id>/。"""
vdir = self.versions_dir / self.version_id
vdir.mkdir(parents=True, exist_ok=True)
jar = vdir / ("%s.jar" % self.version_id)
@@ -293,6 +330,7 @@ class MinecraftLauncher:
return jar
def download_libraries(self) -> list:
"""下载全部依赖库与原生库, 返回 classpath 列表。"""
cp = []
total = sum(1 for lib in self.version_meta.get("libraries", []) if library_allowed(lib))
done = 0
@@ -322,12 +360,14 @@ class MinecraftLauncher:
return cp
def _download_one(self, artifact, path, tag, done, total):
"""下载单个库文件, 失败只警告不中断。"""
try:
download_file(artifact["url"], path, artifact.get("sha1"), artifact.get("size"))
except Exception as e:
self._emit(" [警告] %s 下载失败: %s" % (path.name, e))
def _extract_natives(self, native_jar: Path) -> None:
"""把原生库 (natives) 解压到 versions/<id>/natives 目录。"""
natives_dir = self.versions_dir / self.version_id / "natives"
natives_dir.mkdir(parents=True, exist_ok=True)
ext = {"linux": ".so", "osx": ".dylib", "windows": ".dll"}[OS_NAME]
@@ -340,6 +380,7 @@ class MinecraftLauncher:
shutil.copyfileobj(src, dst)
def download_assets(self) -> None:
"""下载资源索引与全部资源对象到 assets/。"""
index_info = self.version_meta.get("assetIndex")
if not index_info:
self._emit("[资源] 该版本无资源索引, 跳过")
@@ -368,6 +409,7 @@ class MinecraftLauncher:
def _resolve_argument(self, arg: str) -> str:
"""替换启动参数里的 ${变量} 占位符。"""
uuid = self.auth.get("uuid") or self._offline_uuid()
replace_map = {
"${auth_player_name}": self.auth.get("name") or self.username,
@@ -394,6 +436,7 @@ class MinecraftLauncher:
return arg
def _offline_uuid(self) -> str:
"""按官方离线模式规则, 由玩家名生成稳定 UUID。"""
name = self.auth.get("name") or self.username
digest = bytearray(hashlib.md5(("OfflinePlayer:" + name).encode("utf-8")).digest())
digest[6] = (digest[6] & 0x0F) | 0x30
@@ -403,6 +446,7 @@ class MinecraftLauncher:
hex_str[16:20], hex_str[20:32])
def _build_args(self) -> tuple:
"""拆分 JVM 参数与游戏参数, 兼容 1.13+ 与旧版两种格式。"""
jvm_args, game_args = [], []
arguments = self.version_meta.get("arguments")
if arguments:
@@ -424,6 +468,7 @@ class MinecraftLauncher:
return jvm_args, game_args
def _find_java(self) -> str:
"""查找 Java: 优先用户指定 -> JAVA_HOME -> PATH。"""
if self.java_path and os.path.isfile(self.java_path):
return self.java_path
java_home = os.environ.get("JAVA_HOME")
@@ -438,6 +483,7 @@ class MinecraftLauncher:
raise RuntimeError("未找到 Java, 请在设置中指定 java 可执行文件路径 (1.17+ 需要 Java 17+)")
def build_command(self, classpath: list) -> list:
"""组装完整 java 启动命令, 含第三方登录时注入 authlib-injector。"""
self._cp = classpath
java = self._find_java()
jvm_args, game_args = self._build_args()
@@ -461,6 +507,7 @@ class MinecraftLauncher:
def run(self) -> int:
"""主流程: 解析版本 -> 下载文件 -> 构建命令 -> 启动游戏。"""
self._emit("[启动] 游戏目录: %s" % self.game_dir)
self._emit("[启动] 版本: %s 玩家: %s" % (self.version_id,
self.auth.get("name") or self.username))
@@ -479,6 +526,7 @@ class MinecraftLauncher:
return subprocess.call(command, cwd=str(self.game_dir))
def list_versions(self) -> dict:
"""获取版本清单 (供 GUI 和 CLI 使用)。"""
manifest = self.fetch_version_manifest()
return {
"latest": manifest.get("latest", {}),
@@ -498,6 +546,10 @@ except Exception:
TK_OK = False
# ---------------------------------------------------------------------------
# 图形界面 (Tkinter)
# 两个标签页: [启动器] 登录/版本/设置/日志, [资源中心] 版本与Mod插件下载
# ---------------------------------------------------------------------------
class MeowLauncherApp:
@@ -529,6 +581,7 @@ class MeowLauncherApp:
def _setup_style(self):
"""配置 ttk 深色主题, 保证三端外观一致。"""
style = ttk.Style(self.root)
try:
style.theme_use("clam")
@@ -560,6 +613,7 @@ class MeowLauncherApp:
def _card(self, parent, title):
"""创建一个圆角卡片容器, 用于分组界面元素。"""
frame = ttk.Frame(parent, style="Card.TFrame", padding=14)
frame.pack(fill="x", padx=14, pady=(8, 4))
ttk.Label(frame, text=title, style="Card.TLabel",
@@ -567,6 +621,7 @@ class MeowLauncherApp:
return frame
def _build_ui(self):
"""构建主窗口: 标题 + 标签页(启动器/资源中心)。"""
ttk.Label(self.root, text="MeowLauncher", style="Title.TLabel").pack(pady=(14, 0))
ttk.Label(self.root, text="跨平台 Minecraft 启动器 v%s" % VERSION, style="Dim.TLabel").pack()
@@ -581,6 +636,7 @@ class MeowLauncherApp:
self._build_res_tab()
def _build_launch_tab(self):
"""构建[启动器]页: 登录、版本、设置、日志、启动按钮。"""
login = self._card(self.tab_launch, "账号登录")
self.login_mode = tk.StringVar(value="offline")
row = ttk.Frame(login, style="Card.TFrame")
@@ -659,6 +715,7 @@ class MeowLauncherApp:
self.launch_btn.pack(fill="x", padx=14, pady=14)
def _build_res_tab(self):
"""构建[资源中心]页: 版本一键下载 + Mod/插件搜索下载。"""
dver = self._card(self.tab_res, "游戏版本下载")
row = ttk.Frame(dver, style="Card.TFrame")
row.pack(fill="x")
@@ -705,12 +762,14 @@ class MeowLauncherApp:
self.res_progress.pack(fill="x", padx=14, pady=(0, 10))
def _res_append(self, msg):
"""向资源中心日志框追加一行。"""
self.res_log.config(state="normal")
self.res_log.insert("end", msg + "\n")
self.res_log.see("end")
self.res_log.config(state="disabled")
def _search_res(self):
"""后台线程搜索 Modrinth, 结果回填列表。"""
def worker():
try:
q = self.res_query.get().strip()
@@ -725,6 +784,7 @@ class MeowLauncherApp:
threading.Thread(target=worker, daemon=True).start()
def _download_version_only(self):
"""一键下载完整游戏版本 (不启动)。"""
def worker():
try:
version = self.res_version_var.get()
@@ -744,6 +804,7 @@ class MeowLauncherApp:
threading.Thread(target=worker, daemon=True).start()
def _download_selected(self):
"""下载列表中选中的 Mod/插件 jar 到 mods/ 或 plugins/ 目录。"""
sel = self.res_tree.selection()
if not sel:
return
@@ -774,6 +835,7 @@ class MeowLauncherApp:
threading.Thread(target=worker, daemon=True).start()
def _toggle_login(self):
"""切换离线/第三方登录表单的显示。"""
if self.login_mode.get() == "offline":
self.offline_frame.pack(fill="x")
self.ygg_frame.pack_forget()
@@ -782,16 +844,19 @@ class MeowLauncherApp:
self.ygg_frame.pack(fill="x")
def _pick_java(self):
"""弹出文件选择框, 选择 java 可执行文件。"""
path = filedialog.askopenfilename(title="选择 java 可执行文件")
if path:
self.java_var.set(path)
def _pick_dir(self):
"""弹出目录选择框, 选择游戏目录。"""
path = filedialog.askdirectory(title="选择游戏目录")
if path:
self.dir_var.set(path)
def refresh_versions(self):
"""后台线程拉取版本清单, 填充两个标签页的版本下拉框。"""
def worker():
try:
data = MinecraftLauncher(
@@ -803,6 +868,7 @@ class MeowLauncherApp:
threading.Thread(target=worker, daemon=True).start()
def _poll_queue(self):
"""定时轮询消息队列, 把后台线程的日志/结果刷到界面。"""
try:
while True:
kind, payload = self.msg_queue.get_nowait()
@@ -832,6 +898,7 @@ class MeowLauncherApp:
self.root.after(100, self._poll_queue)
def _fill_versions(self, latest):
"""根据快照开关过滤并填充版本下拉框。"""
show_snap = self.show_snapshot.get()
items = [v["id"] for v in self.versions_cache
if show_snap or v["type"] == "release"]
@@ -842,16 +909,19 @@ class MeowLauncherApp:
self.res_version_var.set(latest.get("release") or (items[0] if items else ""))
def _append_log(self, msg):
"""向[启动器]页日志框追加一行。"""
self.log_box.config(state="normal")
self.log_box.insert("end", msg + "\n")
self.log_box.see("end")
self.log_box.config(state="disabled")
def _on_launch(self):
"""点击启动游戏: 禁用按钮并开后台线程。"""
self.launch_btn.config(state="disabled")
threading.Thread(target=self._launch_worker, daemon=True).start()
def _launch_worker(self):
"""后台线程: 登录 -> 构建启动器 -> 下载 -> 启动游戏。"""
try:
if self.login_mode.get() == "yggdrasil":
server_name = self.server_var.get()
+5
View File
@@ -1,6 +1,11 @@
"""
MeowLauncher 命令行版 (旧版备份)
功能已被 launcher.py 的图形界面版完全覆盖, 保留此文件供命令行使用:
python launcher_cli.py --username Steve --version 1.20.4
"""
import argparse
import hashlib
import json